The v2 DB seed queried `is_main` from the v1 `registered_groups` table, but
that column was a later v1 addition — older v1 installs (e.g. 1.1.0) don't have
it, so the migration's `1b-db` step crashes with `no such column: is_main` and
v2.db is never created, cascading into the sessions and tasks steps failing.
`is_main` was selected into the V1Group interface but never read anywhere, so
this just drops it from the SELECT and the interface. The accompanying comment
already states the intent ("Query only the columns we know exist in all v1
installs") — the code now matches it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prior commit moves `chat` to 4.29.0, but main's own install pins were left
behind — and were inconsistent: the 8 /add-<channel> SKILL.md steps pinned
@chat-adapter/*@4.27.0 while the 12 setup/*.sh scripts pinned @4.26.0. Unify all
to @4.29.0 so `/add-<channel>` (and setup:auto) on a main install fetch an
adapter whose ChatInstance matches the bridge.
20 files, version-string only. Shell scripts pass `bash -n`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The setup preflight unloads *crash-looping* peers but ignores a more common
leftover: a launchd plist (or systemd unit) whose program no longer exists,
left behind when a NanoClaw checkout is deleted without running the
uninstaller. The health probe can't see these because an unloaded/inactive job
doesn't report via `launchctl print` / `systemctl show`, so they accumulate —
the OS keeps retrying a missing binary forever.
Detect a registration as dead when its `dist/index.js` target is absent on
disk, then unload (best-effort) and delete the orphaned config file. Own-label
and still-valid registrations are never touched.
Adds peer-cleanup.test.ts (the file previously had no tests) covering both
platforms: dead target removed, live target kept, own registration spared,
unrecognized config ignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`claude setup-token` runs under script(1) so the browser OAuth flow keeps a
TTY while we capture the printed token. On terminals that wrap long lines
(e.g. sbx), the token lands split across lines with padding spaces, and the
old parser — which stripped only ANSI codes and newlines — matched just the
first fragment and failed the trailing `AA` check. Login succeeded; only our
parse of the human-oriented output failed (`No sk-ant-oat…AA token found`).
Add setup/lib/captured-token.ts: normalize the capture (strip ANSI/control
bytes and all whitespace, un-wrapping the token) then extract. The TS caller
(claude-assist.ts) and the bash registration script now share it, so the
normalization rules can't drift. Placeholder lines like
`export CLAUDE_CODE_OAUTH_TOKEN=<token>` are ignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
adfae67 moved the agent's global Node CLIs into container/cli-tools.json so a
skill adds one with a json-merge instead of editing the Dockerfile. The Codex
provider install was left behind — add-codex.sh still awk'd an ARG + RUN into
the Dockerfile and its test guarded that shape.
Migrate add-codex to the seam:
- add-codex.sh appends { name: "@openai/codex", version } to cli-tools.json
(idempotent json-merge); install/idempotency gates read the manifest.
- SKILL.md / REMOVE.md document the manifest append/removal, not Dockerfile edits.
- codex-dockerfile.test.ts -> codex-cli-tools.test.ts, asserting the manifest
entry (skips when the manifest is absent, e.g. the bare providers branch).
Pairs with the providers-branch commit that drops the codex Dockerfile lines,
renames the payload test, and points the setup install-check at the manifest.
Verified end-to-end: full add-codex install into a clean worktree leaves the
Dockerfile codex-free, the manifest correctly appended and idempotent; vitest
cli-tools.test.ts (6) and bun codex-cli-tools.test.ts (2) green; host tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the agent provider a first-class, operator-chosen property instead of a
Claude-only assumption. Trunk gains the seams; the actual non-default payloads
(Codex first) install from the `providers` branch.
Setup
- A provider registry feeds a hard-wired setup picker (Claude | Codex). Picking
a non-default provider installs its payload (setup/add-codex.sh, channel-style),
runs a vault-only auth walkthrough (--step provider-auth), and records the pick
on the first agent before its first spawn.
- Picking Claude changes nothing — default installs are byte-for-byte unaffected.
Provider as a DB property
- Provider lives on container_configs.provider (materialized to container.json,
read by resolveProviderName). Creation stays provider-agnostic; the picked
provider is applied via the picked-provider seam. The deprecated
agent_groups.agent_provider path is not used.
Switching + memory
- Switch a live group with `ncl groups config update --provider` + restart.
- Memory never migrates at runtime — each provider keeps its own store. The
/migrate-memory skill carries a group's memory across a switch in either
direction (flat CLAUDE.local.md <-> memory/ scaffold). group-init seeds an
imported-agent-memory note for non-default providers; the runner's memory
definition reads it first turn. See docs/provider-migration.md.
No install-wide default, no runtime provider guard — switching is operator-by-
convention, consistent with the no-install-gating posture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Injects credentials as request-time stubs so no credential is ever written
into a container or to disk. Gateway and CLI versions move to versions.json
(machine-checkable pins); breaking upgrades are documented in
docs/onecli-upgrades.md as an agent-executable runbook (detect / why / fix /
verify / rollback), and the update flow follows linked docs and diffs the
pins.
BREAKING: requires a gateway upgrade; the doc carries the steps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Interactive setup handoffs (mid-flow `?` escape and on-failure) spawned
claude with all context in --append-system-prompt and no user message,
so Claude sat at an empty REPL until the user re-explained themselves.
Move the context into a positional prompt that auto-submits as the
first user message: Claude starts orienting immediately, the context
stays visible in the transcript, and it survives --resume.
Also:
- Share one session across all handoffs in a setup run: pin a
generated UUID via --session-id on the first spawn, --resume it on
later ones (stdio is inherited, so Claude's own id is never visible).
- Switch --permission-mode from acceptEdits to auto.
- Dedupe the two spawn blocks into spawnInteractiveClaude().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .env backup and removal are now one atomic action: a failed backup
throws into executePlan's catch and the deletion never runs (the bash
original's set -e gave the same guarantee; the port had lost it)
- containers are re-listed by install label at removal time instead of
removed from scan-time ids — the live host can spawn containers during
the confirm phase
- uninstall telemetry no longer creates data/install-id (persistId:false
on emit), so --dry-run truly changes nothing and the already-clean
exit can fire
- runtime-tail failure notes are printed before the Done line instead
of being discarded
- uninstall.sh translates the old short flags (-n/-y) instead of
silently dropping them (-n used to fall through to a real interactive
uninstall)
- nanoclaw.sh gates the TS uninstaller on node (tsx's interpreter), not
pnpm, which the direct-exec path never uses
- detectExistingInstall also checks the system-level systemd unit
- a delete-onecli-agent spawn failure now notes the manual command
instead of claiming the agent was already gone
- setupLog.userInput is skipped when logs/ is absent so the uninstall
doesn't recreate it
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the standalone bash uninstall.sh with a TypeScript flow inside the
setup driver (setup/uninstall/): scan (slug-scoped inventory), plan (pure
ordered removal actions), remove (per-action executor that absorbs failures
into notes), and flow (clack UI). uninstall.sh is now a 3-line pointer that
execs nanoclaw.sh --uninstall.
- nanoclaw.sh --uninstall short-circuits before diagnostics/bootstrap; with
no node_modules it prints manual cleanup commands and exits 1
- setup:auto routes --uninstall before initProgressionLog so an uninstall
never resets logs/setup.log
- fresh setup runs detect an existing install (service registration or
data/v2.db) and offer keep-and-continue (default) or uninstall-and-exit;
suppressed on fail()-retry and sg re-exec resumes
- self-deletion safety: static imports only, dist/ + node_modules/ removed
dead last, nothing but console.log after the runtime tail
- --yes never deletes orphan ag-* vault agents; their manual delete
commands (by vault uuid) are printed instead
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refuse to start unless this install reached the current version through a
sanctioned path (setup / update / migrate). A raw `git pull` that skips
migrations now fails loudly with a self-healing message instead of
silently breaking.
- src/upgrade-state.ts: marker at data/upgrade-state.json, getCodeVersion,
isUpgradeCurrent, enforceUpgradeTripwire (fails closed on missing /
corrupt / mismatched marker)
- src/index.ts: gate wired in at startup step 0.5, before DB init
- scripts/upgrade-state.ts: get/set CLI (also the override / recovery cmd)
- setup/service.ts, /update-nanoclaw, /migrate-nanoclaw: stamp on success;
update/migrate also self-update their own skill first
- CHANGELOG [BREAKING] entry bridges existing installs via the skills'
breaking-change check
- docs/upgrade-recovery.md: clearing the tripwire
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The chat-adapter-imessage docs use photon.codes — our setup flow
and skill had the wrong domain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
signal-cli >= 0.13 emits the account identifier as `number` in JSON
output, not `account`. The skip-if-already-linked path in signal-auth
always returned an empty list, so re-runs of setup unconditionally
tried `signal-cli link`, which fails when the data directory already
exists.
Read `number` first, fall back to `account` for older signal-cli.
Previously, passing --assistant-name <Name> when registering an agent
did a project-wide find-replace of "Andy" → <Name> across every
groups/*/CLAUDE.md file, and overwrote .env's ASSISTANT_NAME.
Two unintended consequences:
- Registering a second agent (e.g. "Homie") clobbered an unrelated
primary agent's CLAUDE.md. Real-world hit when wiring Homie's
Signal group on an install that already had Diddyclaw set up —
groups/diddyclaw/CLAUDE.md ended up with "Homie" references it
shouldn't have had.
- The install-wide .env ASSISTANT_NAME flipped to the most recently-
registered name, becoming the default trigger pattern for any
subsequent group registered without an explicit --assistant-name.
Both were a per-agent operation accidentally exercising project-wide
state. Now only groups/<folder>/CLAUDE.md of the agent being
registered is touched. .env is left alone — it represents the
install-wide default and shouldn't be flipped by per-agent registers.
If the install's primary-default name needs to change, that's an
explicit one-line .env edit, not a side-effect of registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without files:read, @chat-adapter/slack cannot download attachments —
Slack returns an HTML login page in place of file bytes and the adapter
throws a NetworkError. Bundles files:write for symmetric outbound
(files.uploadV2).
Closes#2457
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The upstream install script supports ONECLI_VERSION; use it to avoid
pulling an untested gateway release during setup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sweep of outbound strings, doc URLs, comments, and clone instructions
that were missed in the original org rename. One both-match case in
setup/lib/channels-remote.sh (URL detection) accepts either name so
existing forks with a `qwibitai` remote continue to resolve cleanly;
everywhere else is a straight rename.
Historical mentions left intact:
- CHANGELOG.md (v2.0.0 entry, frozen history)
- .claude/skills/add-gmail-tool/SKILL.md (pre-v2 qwibitai skill — historical)
- repo-tokens/badge.svg (auto-regenerated by update-tokens.yml)
Rename the CLI binary, socket path, container wrapper, error prefixes,
and all references from `nc` to `ncl`. Add ~/.local/bin symlink during
setup and pnpm script alias.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Failures now launch an interactive Claude session instead of the
non-interactive assist (REASON/COMMAND parser). The user debugs
with full terminal access and types /exit to return to setup.
The original assist mode is available via --assist-mode flag or
NANOCLAW_SETUP_ASSIST_MODE=1 env var.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Today the Claude auth picker has only three real-auth options. A user
without a Pro/Max subscription, an OAuth token, or an API key has no
graceful escape — Ctrl-C kills setup entirely.
Add a fourth option that confirms the trade-off (no agent runtime + no
Claude debug help during setup) and, on Yes, marks auth skipped and
lets setup continue. On No, loop back to the picker. Existing
NANOCLAW_SKIP=auth env hatch is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace "full E.164, e.g. +15551234567" with plain-language guidance
mirroring the WhatsApp setup card: "start with + and your country code,
no spaces or dashes" plus a worked example. "E.164" is the technical
name for the format and means nothing to non-telecom users; the
explanation it stands in for is one sentence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After picking "Other…" from the channel picker, today's flow drops the
user straight into a free-text prompt with no way back. Replace it with
a brightSelect that offers either "Type the channel name" (existing
behavior) or "← Back to channel selection" — same back-affording pattern
the channel sub-flows already use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Teams setup is 6+ Azure steps over 30+ minutes. Today, every
"Done / Stuck / Show again" gate forces continuation; the only escape
is Ctrl-C, which kills setup entirely. Add a fourth option at each gate
that returns to the channel picker so a stuck operator can pick a
different channel without losing the rest of setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slack's profile button is in the bottom-left of the desktop sidebar (not
the top-right), and the "More" overflow icon next to "Copy member ID" is
the vertical kebab `⋮`, not the horizontal `⋯`. Match what users actually
see in Slack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move the "Get started: …" URL above the numbered instructions and
render it in bright white so it pops against the brand-cyan body.
(Headless-only — interactive runs still auto-open the URL in a
browser, no card line.)
- Group the OAuth scope list vertically by family (im, channels,
groups, chat, users, reactions) instead of one comma-run wall.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #2259 (Baileys v6→v7) was merged into the channels branch instead of
main. PR #2260 was merged into main 28s later assuming v7 was already
in place. The v6 pin survived in three sites while the WhatsApp adapter
copied from origin/channels at install time was already on the v7 LID
API, breaking every fresh migrate-v2.sh run at 2c-install-whatsapp with
TS errors on remoteJidAlt/participantAlt/lid-mapping.update.
Bumps the pin to 7.0.0-rc.9 (the version v1 has been running on for
months) in:
- setup/install-whatsapp.sh
- setup/add-whatsapp.sh
- .claude/skills/add-whatsapp/SKILL.md (install instruction)
package.json + pnpm-lock.yaml are not touched here — install-whatsapp.sh
mutates them at runtime via pnpm install with the corrected pin.
Closes#2283
Today's copy says "Check that signal-cli is installed (we'll guide
you if not)" but the auto-install PR (#2281) makes that misleading —
we don't guide, we just install. Update the intro list to match what
will actually happen, and add a "no input needed for any of it" lead
so users know to expect a hands-off run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
WhatsApp's mobile UI calls the menu "You" on iOS and "Settings" on
Android (depending on platform/version). Both QR-scan and pairing-code
captions only mentioned "Settings", so iOS users had to figure out the
iOS-specific path on their own.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked on #2269 (back-nav scaffolding) plus the Telegram, Slack, and
Teams PRs. They share the same scaffolding file from #2269 — they
don't compile without it, so they have to stack.
Signal had no user-facing prompt before the install kicked off, so
there was nothing to attach a Back option to. This adds a brief "Set
up Signal" info card (what's about to happen, no new phone number
needed) followed by a Continue/Back brightSelect. The card serves
double duty — context for the install plus the Back gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>