Merge branch 'main' into fix/update-nanoclaw-skill-v2
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user