migrate-v2.sh probes ${ONECLI_URL_CHECK}/health (with ONECLI_URL_CHECK
defaulting to http://127.0.0.1:10254, the OneCLI web port). That path
returns 404, so the detection branch never matches an already-running
OneCLI instance and the script falls through to the install path.
The web app's health endpoint is /api/health
(apps/web/src/app/api/health/route.ts) and has been since the OneCLI
repo was made public. /health was never exposed by the web on :10254
nor by the gateway on :10255 (the gateway uses /healthz).
Verified against a running OneCLI v1.21.0:
GET :10254/api/health -> 200 {"status":"ok","version":"1.21.0",...}
GET :10254/health -> 404 (Next.js fallback HTML)
GET :10255/healthz -> 200
GET :10255/health -> 400 (gateway parses non-/healthz as CONNECT)
Closes#2285
Resource-first CLI: `nc groups list`, `nc wirings get <id>`, etc.
Seven resources defined (groups, messaging-groups, wirings, users,
roles, members, sessions) with full column documentation that serves
as the single source of truth for help output and arg validation.
- CRUD helper auto-registers list/get/create/update/delete from
declarative resource definitions with generic SQL
- Custom operations for composite-PK resources (roles grant/revoke,
members add/remove)
- Access model: open (reads) / approval (writes) / hidden
- `nc help` lists resources; `nc <resource> help` shows fields
- Positional target IDs: `nc groups get <id>`
- Removed unused priority column from wirings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop the nc MCP tool in favor of a standalone Bun CLI script at
container/agent-runner/src/cli/nc.ts. Same interface as host-side
bin/nc — all three callers (operator, Claude on host, agent in
container) now use the same nc CLI.
Container transport: writes cli_request to outbound.db (BEGIN
IMMEDIATE for seq safety), polls inbound.db for response, acks via
processing_ack. Dockerfile adds a /usr/local/bin/nc wrapper that
execs the mounted source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add delivery action handler (cli_request) so the host dispatches CLI
commands arriving from container agents via outbound.db and writes
responses back to inbound.db. Add nc MCP tool in the agent-runner
following the ask_user_question blocking pattern.
Co-Authored-By: Claude Opus 4.6 (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>
Resolve conflict in src/index.ts shutdown sequence — keep both
stopCliServer() from nc-cli and try/finally + resetCircuitBreaker()
from main.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
openInboundDb() always opened /workspace/inbound.db which doesn't exist
in CI. In test mode, return a thin wrapper over the in-memory singleton
that delegates prepare/exec but no-ops close(), so callers' try/finally
cleanup doesn't destroy the shared DB mid-test.
One flag (_testMode), no monkey-patching, no saved-close bookkeeping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test wrapper forwards the in-memory outDb as the writable handle,
avoiding the filesystem reopen that fails in CI. The function stays
private — the optional writableOutDb param is an internal detail, not
a public API.
Co-Authored-By: Claude Opus 4.6 (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>
Stacked on #2269 (back-nav scaffolding) plus the Telegram and Slack
PRs. They share the same scaffolding file from #2269 — they don't
compile without it, so they have to stack.
Both Teams paths already had a brightSelect at the right place, so we
just extend each with a Back option — no new prompts:
- Existing-credentials path: Yes/No confirm becomes Yes/No/Back
- Fresh-setup path: the very first stepGate ("How did that go?") gets
a 4th option. Subsequent stepGates keep the original 3 options so
we never lose mid-flow state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked on the back-nav scaffolding from #2269 and the Telegram PR.
Slack's first prompt was already a single-purpose "Press Enter to open
Slack app settings" confirm. Replacing it with a 2-option brightSelect
(Open / ← Back) folds the Back gate into the existing screen — net
same number of prompts as before, just with a way out. The redundant
confirmThenOpen Press-Enter step is dropped; openUrl is called inline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked on the back-nav scaffolding from the Discord/WhatsApp/iMessage
PR — depends on setup/lib/back-nav.ts and the auto.ts loop.
Telegram's "no existing token" path adds one extra prompt — a
brightSelect "Ready to paste your bot token?" between the BotFather
instructions and the token paste. Clack's p.password prompt doesn't
support menu options so we can't fold Back into the paste itself; the
cleanest fix is a separate gate immediately before. The "existing
token" path doesn't add noise — the Yes/No confirm becomes Yes/No/Back.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picking the wrong messaging channel during setup left users with no way
to bail out — they had to either complete the chosen flow or kill setup
and start over. This adds a Back option to the first prompt of three
channel sub-flows that share the same simple shape (one leading
brightSelect that's easy to extend).
Mechanics:
- New `setup/lib/back-nav.ts` exports a BACK_TO_CHANNEL_SELECTION
sentinel and ChannelFlowResult type.
- `setup/auto.ts` wraps the channel dispatch in a while-loop; channels
return BACK_TO_CHANNEL_SELECTION to bounce back to the chooser
without restarting setup. Channels not yet wired return void and the
loop exits after one pass, so the change is backwards compatible.
- Discord, WhatsApp, iMessage each add a `← Back to channel selection`
option to their first prompt.
Telegram, Slack, Teams, and Signal will follow as separate PRs — they
each need a slightly different shape (extra prompt insertions, gating
inside multi-step flows, etc.) and are easier to review independently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nodeenv doesn't support major-only version specifiers. Use lts
which resolves to the latest LTS release.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The disk threshold was unreliable on hosts with separate /home or /var
mounts where df underreports free space. Simplify the pre-flight to a
RAM-only check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chat-adapter/discord@4.27.0 includes vercel/chat#256, which fixes the
Discord adapter unconditionally setting payload.content alongside
payload.embeds when posting a card. In 4.26.0 every Discord card
appeared twice (text content above the embed, identical content inside
the embed) — every new install reproduced this on the welcome tour and
on every approval card.
The other 7 skills bump in lockstep because @chat-adapter/discord@4.27.0
depends on chat@4.27.0 while @chat-adapter/<other>@4.26.0 depend on
chat@4.26.0. Mixing the cohort produces a TypeScript dual-version
conflict between the bridge and adapter ChatInstance types.
Files updated (one line per file in each pnpm install command):
- add-discord (the user-visible bug fix)
- add-gchat, add-github, add-linear, add-slack, add-teams, add-telegram,
add-whatsapp-cloud (cohort consistency)
Out of scope: add-imessage, add-matrix, add-webex, add-resend use
third-party packages with independent versioning.
Closes#2264
The send_card MCP tool wrote outbound rows with type='card' but the
chat-sdk-bridge deliver() had no branch for them, so the payload fell
through to the text fallback (where text is undefined) and silently
returned without calling the adapter. delivery.ts then marked the
message delivered with platformMsgId=undefined and the user saw nothing.
Add a dedicated card branch mirroring the ask_question structure:
- Build Card from title, description, and string-or-{text} children
- Render only URL actions as LinkButtons (send_card is fire-and-forget
per its docstring, so callback buttons would have nowhere to land)
- Drop empty cards with a warn log instead of posting blank
- Fall back text: content.fallbackText > description > title
Affects every Chat SDK adapter that goes through the bridge: Discord,
Telegram, Slack, Teams, GChat, GitHub, Linear, WhatsApp Cloud, iMessage,
Matrix, Webex, Resend.
Tests: adds five cases covering normal render, action filtering,
link-button rendering, empty-card skip, and a regression check that
non-card chat-sdk payloads still flow through the text branch.
Closes#2263
Remove step 2d (whatsapp-resolve-lids.ts) which pre-created duplicate
messaging_groups rows keyed by @lid alongside the phone-keyed rows.
This caused split sessions — the same contact got separate sessions
depending on which JID format arrived.
With the Baileys v7 upgrade (PR #2259 on channels), the adapter
resolves every LID to a phone JID via extractAddressingContext before
the message reaches the router, making dual rows unnecessary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two friction points in the Telegram channel's "Open Telegram" card,
both surfaced when running setup on a VM-via-SSH where the user's
local laptop has no Telegram client installed:
1. The opening sentence read "Opening @yourbot in Telegram so it's
ready when the pairing code shows up." On a headless device that's
misleading — nothing is auto-opened, the user has to click the
link or use their phone. Rewrite as a direct, action-led
instruction on the headless flow only:
Open @yourbot in Telegram now — the pairing code is coming next,
and that's where you'll send it.
Plus a "Get started: <url>" line and a full-strength mobile
fallback hint inside the card so headless users have all
self-serve options visible.
On non-headless the original status-style line stays accurate
(`xdg-open` / `open` does fire for users with Telegram desktop
installed), so the card stays a single line.
2. Clicking `https://t.me/yourbot` silently fails when the user's
local device has no Telegram client. Non-headless gains:
- a "(must be installed here)" qualifier on the confirm prompt
so users without Telegram desktop know up-front;
- a single combined dim fallback line below the prompt:
"If browser does not appear, please visit: <url> — or
search for @yourbot on your mobile."
Direct `p.confirm` + `openUrl` instead of `confirmThenOpen` for
the non-headless branch so we control the dim line fully (single
combined line vs the helper's default URL-only line).
Headless layout drives the same self-serve content via the card body
itself; no confirm prompt fires there.