Merge branch 'main' into fix/update-nanoclaw-skill-v2

This commit is contained in:
gavrielc
2026-05-04 10:08:43 +03:00
committed by GitHub
5 changed files with 107 additions and 8 deletions

View File

@@ -6,8 +6,8 @@ This is NanoClaw v2, a ground-up rewrite with breaking changes throughout. It ca
**Do this instead:** **Do this instead:**
1. Abort the merge: `git merge --abort` (or `git reset --hard ORIG_HEAD` if the merge already completed). 1. Abort the merge: `git merge --abort` (or `git reset --hard ORIG_HEAD` if the merge already completed).
2. Tell the user, verbatim: *"This is the v2 rewrite — it can't be merged into your existing install. Run `bash migrate-v2.sh` to migrate instead."* 2. Tell the user, verbatim: *"This is the v2 rewrite — it can't be merged into your existing install. Exit Claude Code (or open a separate terminal) and run `bash migrate-v2.sh` from the shell."*
3. Wait for the user to confirm before doing anything else. Do not attempt the migration yourself. 3. Wait for the user to confirm before doing anything else. Do not run the migration script yourself — it requires an interactive terminal and cannot be run from within Claude Code.
If you are a fresh install (you ran `git clone`, not `git pull`) and there are no conflicts, ignore this banner and continue below. If you are a fresh install (you ran `git clone`, not `git pull`) and there are no conflicts, ignore this banner and continue below.

View File

@@ -5,6 +5,8 @@
# Run from the v2 directory: # Run from the v2 directory:
# bash migrate-v2.sh # bash migrate-v2.sh
# #
# If you're in Claude Code, exit first or open a separate terminal.
#
# Finds v1 automatically (sibling directory, or $NANOCLAW_V1_PATH). # Finds v1 automatically (sibling directory, or $NANOCLAW_V1_PATH).
# Installs prerequisites (Node, pnpm, deps) via the existing setup.sh # Installs prerequisites (Node, pnpm, deps) via the existing setup.sh
# bootstrap, then runs the migration steps. # bootstrap, then runs the migration steps.
@@ -17,6 +19,19 @@ set -uo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
# This script has interactive prompts (channel selection, service switchover)
# and streams progress output — it must run in a real terminal, not inside
# a tool subprocess (e.g. Claude Code's Bash tool, which collapses output).
if ! [ -t 0 ] || ! [ -t 1 ]; then
echo "This script requires an interactive terminal."
echo ""
echo "If you're in Claude Code, exit first or open a separate terminal,"
echo "then run:"
echo " bash migrate-v2.sh"
echo ""
exit 1
fi
LOGS_DIR="$PROJECT_ROOT/logs" LOGS_DIR="$PROJECT_ROOT/logs"
STEPS_DIR="$LOGS_DIR/migrate-steps" STEPS_DIR="$LOGS_DIR/migrate-steps"
MIGRATE_LOG="$LOGS_DIR/migrate-v2.log" MIGRATE_LOG="$LOGS_DIR/migrate-v2.log"
@@ -547,6 +562,26 @@ echo
echo "$(bold 'Service switchover')" echo "$(bold 'Service switchover')"
echo echo
# Disable the v1 service so it doesn't auto-start, but leave the unit file
# on disk so the user can rollback with: systemctl --user start nanoclaw
# Idempotent — safe to call multiple times.
disable_v1_service() {
if [ "$PLATFORM_SERVICE" = "systemd" ]; then
local v1_file="$HOME/.config/systemd/user/${V1_SERVICE}.service"
if [ -f "$v1_file" ] || [ -L "$v1_file" ]; then
systemctl --user stop "$V1_SERVICE" 2>/dev/null || true
systemctl --user disable "$V1_SERVICE" 2>/dev/null || true
step_ok "Disabled $V1_SERVICE (unit file kept for rollback)"
fi
elif [ "$PLATFORM_SERVICE" = "launchd" ]; then
local v1_plist="$HOME/Library/LaunchAgents/${V1_SERVICE}.plist"
if [ -f "$v1_plist" ] || [ -L "$v1_plist" ]; then
launchctl unload "$v1_plist" 2>/dev/null || true
step_ok "Unloaded $V1_SERVICE (plist kept for rollback)"
fi
fi
}
# Detect platform and service names # Detect platform and service names
V1_SERVICE="" V1_SERVICE=""
V2_SERVICE="" V2_SERVICE=""
@@ -635,16 +670,14 @@ if [ "$V1_RUNNING" = "true" ]; then
SERVICE_SWITCHED=false SERVICE_SWITCHED=false
else else
step_ok "Keeping v2 service" step_ok "Keeping v2 service"
# Disable v1 from auto-starting disable_v1_service
if [ "$PLATFORM_SERVICE" = "systemd" ]; then
systemctl --user disable "$V1_SERVICE" 2>/dev/null || true
fi
fi fi
else else
step_skip "Service switchover skipped" step_skip "Service switchover skipped"
fi fi
else else
step_skip "v1 service not running — nothing to switch" step_skip "v1 service not running — nothing to switch"
disable_v1_service
fi fi
echo echo
@@ -676,6 +709,16 @@ echo " $(green '✓') Channels installed: ${SELECTED_CHANNELS[*]}"
fi fi
echo " $(green '✓') Container skills copied" echo " $(green '✓') Container skills copied"
echo " $(green '✓') Container image built" echo " $(green '✓') Container image built"
if [ "$SERVICE_SWITCHED" = "true" ] && [ -n "$V2_SERVICE" ]; then
echo " $(green '✓') Service switched to v2 $(dim "($V2_SERVICE)")"
echo
echo " $(bold 'Rollback to v1:')"
if [ "$PLATFORM_SERVICE" = "systemd" ]; then
echo " $(dim '$') systemctl --user stop $V2_SERVICE && systemctl --user start $V1_SERVICE"
elif [ "$PLATFORM_SERVICE" = "launchd" ]; then
echo " $(dim '$') launchctl unload ~/Library/LaunchAgents/${V2_SERVICE}.plist && launchctl load ~/Library/LaunchAgents/${V1_SERVICE}.plist"
fi
fi
echo echo
echo " $(bold 'What still needs a human:')" echo " $(bold 'What still needs a human:')"
if [ "$ONECLI_OK" = "false" ]; then if [ "$ONECLI_OK" = "false" ]; then

View File

@@ -12,6 +12,7 @@
import fs from 'fs'; import fs from 'fs';
import * as p from '@clack/prompts'; import * as p from '@clack/prompts';
import { styleText } from 'node:util';
const CHANNELS = [ const CHANNELS = [
{ value: 'telegram', label: 'Telegram' }, { value: 'telegram', label: 'Telegram' },
@@ -47,7 +48,7 @@ async function main(): Promise<void> {
} }
const selected = await p.multiselect({ const selected = await p.multiselect({
message: 'Which channels do you want to set up?', message: 'Which channels do you want to set up?\n' + styleText('dim', ' space to select, enter to confirm') + '\n',
options: CHANNELS, options: CHANNELS,
required: false, required: false,
}); });

View File

@@ -115,9 +115,43 @@ function installOnecliCliOnly(): { stdout: string; ok: boolean } {
return { stdout: upstream.stdout + (upstream.stderr ?? '') + '\n' + fallback.stdout, ok: fallback.ok }; return { stdout: upstream.stdout + (upstream.stderr ?? '') + '\n' + fallback.stdout, ok: fallback.ok };
} }
// Remove containers in the "onecli" compose project whose service name isn't
// in the v2 set. Pre-v2 OneCLI used service "app" (container onecli-app-1);
// v2 uses "onecli". Compose flags the old container as an orphan but won't
// stop it without --remove-orphans, leaving port 10254 bound and crashing
// the new bring-up. Filed upstream; this is the downstream workaround.
function removeLegacyOnecliContainers(): string {
const out: string[] = [];
let list = '';
try {
list = execSync(
`docker ps -a --filter "label=com.docker.compose.project=onecli" --format '{{.Names}}|{{.Label "com.docker.compose.service"}}'`,
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] },
).trim();
} catch {
return '';
}
if (!list) return '';
const v2Services = new Set(['onecli', 'postgres']);
for (const line of list.split('\n')) {
const [name, service] = line.split('|');
if (!name || !service || v2Services.has(service)) continue;
out.push(`Removing legacy OneCLI container: ${name} (service=${service})`);
try {
execSync(`docker rm -f ${JSON.stringify(name)}`, { stdio: ['ignore', 'pipe', 'pipe'] });
} catch (err) {
out.push(` rm failed (continuing): ${(err as Error).message}`);
}
}
return out.join('\n');
}
function installOnecli(): { stdout: string; ok: boolean } { function installOnecli(): { stdout: string; ok: boolean } {
let stdout = ''; let stdout = '';
const cleanup = removeLegacyOnecliContainers();
if (cleanup) stdout += cleanup + '\n';
// Gateway install (docker-compose based, no rate-limit concerns). // Gateway install (docker-compose based, no rate-limit concerns).
const gw = runInstall('curl -fsSL onecli.sh/install | sh'); const gw = runInstall('curl -fsSL onecli.sh/install | sh');
stdout += gw.stdout; stdout += gw.stdout;

View File

@@ -51,13 +51,34 @@ command -v script >/dev/null \
tmpfile=$(mktemp -t claude-setup-token.XXXXXX) tmpfile=$(mktemp -t claude-setup-token.XXXXXX)
trap 'rm -f "$tmpfile"' EXIT trap 'rm -f "$tmpfile"' EXIT
cat <<'EOF' # Detect headless. Mirrors `isHeadless()` in setup/platform.ts: on Linux
# with neither DISPLAY nor WAYLAND_DISPLAY set, no graphical session
# exists, so `claude setup-token` won't be able to auto-open a browser
# and the user will need to copy the printed sign-in URL by hand. The
# pre-message copy below is swapped accordingly so we don't promise a
# browser pop that will never happen.
is_headless=0
if [ "$(uname -s)" = "Linux" ] && [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
is_headless=1
fi
if [ "$is_headless" = "1" ]; then
cat <<'EOF'
A sign-in link will appear for you to sign in with your Claude account.
When you finish, we'll save the token to your OneCLI vault automatically.
Press Enter to continue, or edit the command first.
EOF
else
cat <<'EOF'
A browser window will open for you to sign in with your Claude account. A browser window will open for you to sign in with your Claude account.
When you finish, we'll save the token to your OneCLI vault automatically. When you finish, we'll save the token to your OneCLI vault automatically.
Press Enter to continue, or edit the command first. Press Enter to continue, or edit the command first.
EOF EOF
fi
cmd="claude setup-token" cmd="claude setup-token"
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then