setup: auto-install signal-cli when missing
When a user picks Signal in setup and signal-cli isn't on PATH, today NanoClaw bails with a GitHub releases link and tells them to re-run. That's a hard wall for non-technical users — GitHub releases pages are intimidating, and the Linux native build / Java decision isn't obvious. Replace the bail-out with a real install: a new install-signal-cli.sh script that does `brew install signal-cli` on macOS or downloads the native Linux release into ~/.local/bin (no Java, no sudo). Wired into ensureSignalCli with a spinner; probe again after, fall back to the original manual-install copy if anything fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,42 +134,74 @@ export async function runSignalChannel(displayName: string): Promise<void> {
|
|||||||
|
|
||||||
async function ensureSignalCli(): Promise<void> {
|
async function ensureSignalCli(): Promise<void> {
|
||||||
const cli = process.env.SIGNAL_CLI_PATH || 'signal-cli';
|
const cli = process.env.SIGNAL_CLI_PATH || 'signal-cli';
|
||||||
const probe = spawnSync(cli, ['--version'], {
|
const probeFor = (): boolean => {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
const r = spawnSync(cli, ['--version'], {
|
||||||
});
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
if (!probe.error && probe.status === 0) return;
|
});
|
||||||
|
return !r.error && r.status === 0;
|
||||||
|
};
|
||||||
|
if (probeFor()) return;
|
||||||
|
|
||||||
|
note(
|
||||||
|
[
|
||||||
|
"NanoClaw talks to Signal through signal-cli, which isn't installed yet.",
|
||||||
|
"We'll install it for you now — about 30 seconds, one-time only.",
|
||||||
|
'',
|
||||||
|
process.platform === 'darwin'
|
||||||
|
? "On this Mac we'll use Homebrew (no admin password needed)."
|
||||||
|
: "On Linux we'll grab the native release binary (no Java needed) and install it to ~/.local/bin.",
|
||||||
|
].join('\n'),
|
||||||
|
'Setting up signal-cli',
|
||||||
|
);
|
||||||
|
|
||||||
|
const install = await runQuietChild(
|
||||||
|
'install-signal-cli',
|
||||||
|
'bash',
|
||||||
|
['setup/install-signal-cli.sh'],
|
||||||
|
{
|
||||||
|
running: 'Installing signal-cli…',
|
||||||
|
done: 'signal-cli installed.',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (install.ok && probeFor()) return;
|
||||||
|
|
||||||
|
const reason = install.terminal?.fields.ERROR;
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
note(
|
note(
|
||||||
[
|
[
|
||||||
"NanoClaw talks to Signal through signal-cli, which isn't installed yet.",
|
"We couldn't install signal-cli automatically.",
|
||||||
|
reason === 'homebrew_not_installed'
|
||||||
|
? ' Reason: Homebrew is not installed.'
|
||||||
|
: ` Reason: ${reason ?? 'unknown'}.`,
|
||||||
'',
|
'',
|
||||||
'The quickest way on macOS is Homebrew:',
|
'You can install it manually:',
|
||||||
'',
|
'',
|
||||||
k.cyan(' brew install signal-cli'),
|
k.cyan(' brew install signal-cli'),
|
||||||
'',
|
'',
|
||||||
"Install it in another terminal, then re-run setup.",
|
'Then re-run setup.',
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
'signal-cli not found',
|
"Couldn't install signal-cli",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
note(
|
note(
|
||||||
[
|
[
|
||||||
"NanoClaw talks to Signal through signal-cli, which isn't installed yet.",
|
"We couldn't install signal-cli automatically.",
|
||||||
|
` Reason: ${reason ?? 'unknown'}.`,
|
||||||
'',
|
'',
|
||||||
'Grab the latest release from GitHub:',
|
'You can install it manually from GitHub:',
|
||||||
'',
|
'',
|
||||||
k.cyan(' https://github.com/AsamK/signal-cli/releases'),
|
k.cyan(' https://github.com/AsamK/signal-cli/releases'),
|
||||||
'',
|
'',
|
||||||
"Install it, make sure `signal-cli --version` works, then re-run setup.",
|
'Then re-run setup.',
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
'signal-cli not found',
|
"Couldn't install signal-cli",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await fail(
|
await fail(
|
||||||
'signal-install',
|
'install-signal-cli',
|
||||||
'signal-cli is required but not installed.',
|
'signal-cli is required but the auto-install failed.',
|
||||||
'Install it and re-run setup.',
|
'Install it manually and re-run setup.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
78
setup/install-signal-cli.sh
Executable file
78
setup/install-signal-cli.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# install-signal-cli.sh — auto-install signal-cli on the host.
|
||||||
|
#
|
||||||
|
# NanoClaw needs `signal-cli` on PATH to talk to Signal. Picks the right
|
||||||
|
# install method per platform:
|
||||||
|
# macOS → `brew install signal-cli` (bottled, no Java needed)
|
||||||
|
# Linux → download latest native binary from GitHub releases to
|
||||||
|
# ~/.local/bin/signal-cli (no Java, no sudo)
|
||||||
|
#
|
||||||
|
# Emits the standard NanoClaw STATUS block on success or failure so the
|
||||||
|
# `runQuietChild` driver can parse the outcome.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="0.14.3"
|
||||||
|
INSTALL_DIR="${HOME}/.local/bin"
|
||||||
|
|
||||||
|
emit_status() {
|
||||||
|
local status=$1 error=${2:-}
|
||||||
|
echo "=== NANOCLAW SETUP: INSTALL_SIGNAL_CLI ==="
|
||||||
|
echo "STATUS: ${status}"
|
||||||
|
[ -n "$error" ] && echo "ERROR: ${error}"
|
||||||
|
echo "=== END ==="
|
||||||
|
}
|
||||||
|
|
||||||
|
log() { echo "[install-signal-cli] $*" >&2; }
|
||||||
|
|
||||||
|
uname_s=$(uname)
|
||||||
|
|
||||||
|
if [[ "${uname_s}" == "Darwin" ]]; then
|
||||||
|
if ! command -v brew >/dev/null 2>&1; then
|
||||||
|
emit_status failed "homebrew_not_installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "Installing signal-cli via Homebrew…"
|
||||||
|
brew install signal-cli >&2 || {
|
||||||
|
emit_status failed "brew_install_failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
emit_status success
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${uname_s}" != "Linux" ]]; then
|
||||||
|
emit_status failed "unsupported_platform_${uname_s}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Linux native build (no Java required) → ~/.local/bin/signal-cli.
|
||||||
|
URL="https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz"
|
||||||
|
TARBALL=$(mktemp -t signal-cli.XXXXXX.tar.gz)
|
||||||
|
|
||||||
|
log "Downloading signal-cli v${VERSION} (~96MB)…"
|
||||||
|
if ! curl -fLsS -o "${TARBALL}" "${URL}"; then
|
||||||
|
rm -f "${TARBALL}"
|
||||||
|
emit_status failed "download_failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Extracting…"
|
||||||
|
EXTRACT_DIR=$(mktemp -d)
|
||||||
|
if ! tar -xzf "${TARBALL}" -C "${EXTRACT_DIR}"; then
|
||||||
|
rm -rf "${TARBALL}" "${EXTRACT_DIR}"
|
||||||
|
emit_status failed "extract_failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${INSTALL_DIR}"
|
||||||
|
log "Installing to ${INSTALL_DIR}/signal-cli…"
|
||||||
|
if ! mv "${EXTRACT_DIR}/signal-cli" "${INSTALL_DIR}/signal-cli"; then
|
||||||
|
rm -rf "${TARBALL}" "${EXTRACT_DIR}"
|
||||||
|
emit_status failed "install_failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
chmod +x "${INSTALL_DIR}/signal-cli"
|
||||||
|
rm -rf "${TARBALL}" "${EXTRACT_DIR}"
|
||||||
|
|
||||||
|
emit_status success
|
||||||
Reference in New Issue
Block a user