Commit Graph

307 Commits

Author SHA1 Message Date
github-actions[bot]
c1a2491e77 docs: update token count to 49.9k tokens · 25% of context window 2026-02-22 16:25:24 +00:00
Daniel M
8fc1c23925 Migrate setup from bash scripts to cross-platform Node.js modules (#382)
* refactor: migrate setup from bash scripts to cross-platform Node.js modules

Replace 9 bash scripts + qr-auth.html with a two-phase setup system:
a bash bootstrap (setup.sh) for Node.js/npm verification, and TypeScript
modules (src/setup/) for everything else. Resolves cross-platform issues:
sed -i replaced with fs operations, sqlite3 CLI replaced with better-sqlite3,
browser opening made cross-platform, service management supports launchd/
systemd/WSL nohup fallback, SQL injection prevented with parameterized queries.

Add Linux systemctl equivalents alongside macOS launchctl commands in 8 skill
files and CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: setup migration issues — pairing code, systemd fallback, nohup escaping

- Emit WhatsApp pairing code immediately when received, before polling
  for auth completion. Previously the code was only shown in the final
  status block after auth succeeded — a catch-22 since the user needs
  the code to authenticate. (whatsapp-auth.ts)

- Add systemd user session pre-check before attempting to write the
  user-level service unit. Falls back to nohup wrapper when user-level
  systemd is unavailable (e.g. su session without login/D-Bus). (service.ts)

- Rewrite nohup wrapper template using array join instead of template
  literal to fix shell variable escaping (\\$ → $). (service.ts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: detect stale docker group and kill
  orphaned processes on Linux systemd

* fix: remove redundant shell option from execSync to fix TS2769

execSync already runs in a shell by default; the explicit `shell: true`
caused a type error with @types/node which expects string, not boolean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: hide QR browser auth option on headless Linux

Emit IS_HEADLESS from environment step and condition SKILL.md to
only show pairing code + QR terminal when no display server is
available (headless Linux without WSL). WSL is excluded from the
headless gate because browser opening works via Windows interop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:25:11 +02:00
Lawyered
ccef3bb8ab fix: block symlink escapes in skills file ops 2026-02-22 00:00:13 -05:00
github-actions[bot]
1980d97d90 docs: update token count to 36.8k tokens · 18% of context window 2026-02-21 21:23:05 +00:00
gavrielc
5f58941db2 fix: add .catch() handlers to fire-and-forget async calls (#221) (#355)
Several async calls in the message loop and group queue are
fire-and-forget without .catch() handlers. When WhatsApp disconnects
or containers fail unexpectedly, these produce unhandled rejections
that can crash the process.

Add explicit .catch() at each call site so errors are logged with
full context (groupJid, taskId) instead of crashing:

- channel.setTyping() in message loop (adapted for channel abstraction)
- startMessageLoop() in main()
- runForGroup() and runTask() in group-queue (5 call sites)

Closes #221

Co-authored-by: Naveen Jain <1779929+naveenspark@users.noreply.github.com>
Co-authored-by: Skip Potter <skip.potter.va@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:22:51 +02:00
gavrielc
cb294405a5 fix: update voice note test to match empty-content skip behavior
The test expected voice notes (audioMessage with no caption) to be
delivered with empty content, but 6f177ad added a guard that skips
messages with no text content. Update assertion accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:13:43 +02:00
vaibhav
6e22abb1ba fix: replace hardcoded /Users/user fallback with os.homedir()
The HOME_DIR fallback '/Users/user' is macOS-specific and incorrect.
Use os.homedir() from Node's os module which works cross-platform
and returns the actual home directory from /etc/passwd on Linux.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 22:40:57 +02:00
github-actions[bot]
9003259675 docs: update token count to 36.6k tokens · 18% of context window 2026-02-21 20:19:38 +00:00
Gavriel Cohen
3d8c0d1c0d test: add coverage for isTaskContainer and idleWaiting reset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:19:24 +02:00
Gavriel Cohen
c6b69e87a9 fix: correctly trigger idle preemption in streaming input mode
The original notifyIdle condition (!result.result) never fired in
streaming input mode because every result has non-null text content.
This caused due tasks to wait up to 30 minutes for the idle timer.

- Call notifyIdle for ALL successful results (not just null ones)
- Add isTaskContainer flag so user messages queue instead of being
  forwarded to task containers (which blocked notifyIdle from the
  message container's onOutput path)
- Reset idleWaiting in sendMessage so containers aren't preempted
  while actively working on a new incoming message
- Replace 30-min IDLE_TIMEOUT with 10s close timer for task containers
  since they are single-turn and should exit promptly after their result

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:19:24 +02:00
gavrielc
93bb94ff55 fix: only preempt idle containers when scheduled tasks enqueue
Containers that finish work but stay alive in waitForIpcMessage() block
queued scheduled tasks. Previous approaches killed active containers
mid-work. This fix tracks idle state via the session-update marker
(status: success, result: null) and only preempts when the container
is idle-waiting, not actively working.

Closes #293

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:19:24 +02:00
Peyton-Spencer
6f177adafe fix: skip empty WhatsApp protocol messages
WhatsApp protocol messages (encryption key distribution, read receipts,
ephemeral settings, senderKeyDistributionMessage) have a message envelope
but no text content. These were being stored in messages.db and processed
by the agent, causing:

1. Agent responds to empty messages, wasting API tokens
2. Container stays alive indefinitely (idle timer resets)
3. Scheduled tasks blocked (queue slot occupied)

This fix skips messages with empty content before calling onMessage,
preventing protocol messages from being stored and processed.

Fixes #250

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-21 17:27:07 +02:00
Stefan Gasser
d336b32460 fix: copy skill subdirectories recursively (#175)
copyFileSync crashes with EISDIR when a skill contains subdirectories
like scripts/. Skills support nested folders (scripts/, examples/,
templates/) per the Claude Code spec. Use fs.cpSync to handle the
complete skill structure.
2026-02-21 17:23:19 +02:00
Gio Lodi
94ba537310 Decouple formatting test from @Andy (#329)
* Fix trigger pattern tests to use config name

Tests hardcoded "Andy" but the pattern is built from
`ASSISTANT_NAME` which comes from `.env`.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>

* Restore usage comment in trim test

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Code Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:18:08 +02:00
gavrielc
3c79c61f67 docs: fix README_zh consistency and remove Skills System CLI section
- Fix 你→您 inconsistency in README_zh.md
- Add missing nanoclaw.dev link to README_zh.md header
- Remove Skills System CLI section from both README.md and README_zh.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 17:16:28 +02:00
jiakeboge
3d1859f919 docs(zh): Apply stylistic and consistency improvements to README_zh.md (#328) 2026-02-21 17:15:47 +02:00
gavrielc
646411ff03 docs: add nanoclaw.dev link to README header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:49:20 +02:00
Gavriel Cohen
41e54a9460 fix: pass filePath in setupRerereAdapter stale MERGE_HEAD cleanup
cleanupMergeState() without a filePath runs bare `git reset`, which
resets the entire index and can stage deletions of unrelated files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 15:14:00 +02:00
gavrielc
7181c49ada feat: add /convert-to-apple-container skill, remove /convert-to-docker (#324)
Docker is now the default runtime. The /convert-to-apple-container skill
uses the new skills engine format (manifest.yaml, modify/, intent files,
tests/) to switch to Apple Container on macOS.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:57:05 +02:00
gavrielc
a4072162b7 feat: add voice transcription as nanorepo skill (#326)
Add voice transcription skill package at
.claude/skills/add-voice-transcription/ so it can be applied via the
skills engine. Skill adds src/transcription.ts (OpenAI Whisper), modifies
whatsapp.ts to detect/transcribe voice notes, and includes intent files,
3 test cases, and 8 skill validation tests.

Also fixes skills engine runNpmInstall() to use --legacy-peer-deps,
needed for any skill adding deps with Zod v3 peer requirements.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:18:54 +02:00
gavrielc
6b9b3a12c9 docs: update skills to use Docker commands after runtime migration (#325)
All skills now reference Docker CLI instead of Apple Container CLI.
Setup skill defaults to Docker with optional /convert-to-apple-container.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:06:31 +02:00
github-actions[bot]
a7faac664a docs: update token count to 36.3k tokens · 18% of context window 2026-02-20 11:20:28 +00:00
gavrielc
607623aa59 feat: convert container runtime from Apple Container to Docker (#323)
Swap container-runtime.ts to the Docker variant:
- CONTAINER_RUNTIME_BIN: 'container' → 'docker'
- readonlyMountArgs: --mount bind,readonly → -v host:container:ro
- ensureContainerRuntimeRunning: container system status → docker info
- cleanupOrphans: Apple Container JSON format → docker ps --filter
- build.sh default: container → docker

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:20:13 +02:00
github-actions[bot]
51a50d402b docs: update token count to 36.4k tokens · 18% of context window 2026-02-20 11:14:09 +00:00
gavrielc
c6e1bfecc6 refactor: extract runtime-specific code into src/container-runtime.ts (#321)
Move all container-runtime-specific logic (binary name, mount args,
stop command, startup check, orphan cleanup) into a single file so
swapping runtimes only requires replacing this one file.

Neutralize "Apple Container" references in comments and docs that
would become incorrect after a runtime swap. References that list
both runtimes as options are left unchanged.

No behavior change — Apple Container remains the default runtime.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:13:55 +02:00
gavrielc
8fd67916b3 Update README.md (#316) 2026-02-19 20:49:11 +02:00
gavrielc
5a16a9db9f Documentation improvements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:26:57 +02:00
github-actions[bot]
646491ce5c docs: update token count to 36.3k tokens · 18% of context window 2026-02-18 23:55:13 +00:00
gavrielc
51788de3b9 Skills engine v0.1 + multi-channel infrastructure (#307)
* refactor: multi-channel infrastructure with explicit channel/is_group tracking

- Add channels[] array and findChannel() routing in index.ts, replacing
  hardcoded whatsapp.* calls with channel-agnostic callbacks
- Add channel TEXT and is_group INTEGER columns to chats table with
  COALESCE upsert to protect existing values from null overwrites
- is_group defaults to 0 (safe: unknown chats excluded from groups)
- WhatsApp passes explicit channel='whatsapp' and isGroup to onChatMetadata
- getAvailableGroups filters on is_group instead of JID pattern matching
- findChannel logs warnings instead of silently dropping unroutable JIDs
- Migration backfills channel/is_group from JID patterns for existing DBs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: skills engine v0.1 — deterministic skill packages with rerere resolution

Three-way merge engine for applying skill packages on top of a core
codebase. Skills declare which files they add/modify, and the engine
uses git merge-file for conflict detection with git rerere for
automatic resolution of previously-seen conflicts.

Key components:
- apply: three-way merge with backup/rollback safety net
- replay: clean-slate replay for uninstall and rebase
- update: core version updates with deletion detection
- rebase: bake applied skills into base (one-way)
- manifest: validation with path traversal protection
- resolution-cache: pre-computed rerere resolutions
- structured: npm deps, env vars, docker-compose merging
- CI: per-skill test matrix with conflict detection

151 unit tests covering merge, rerere, backup, replay, uninstall,
update, rebase, structured ops, and edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Discord and Telegram skill packages

Skill packages for adding Discord and Telegram channels to NanoClaw.
Each package includes:
- Channel implementation (add/src/channels/)
- Three-way merge targets for index.ts, config.ts, routing.test.ts
- Intent docs explaining merge invariants
- Standalone integration tests
- manifest.yaml with dependency/conflict declarations

Applied via: npx tsx scripts/apply-skill.ts .claude/skills/add-discord
These are inert until applied — no runtime impact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* remove unused docs (skills-system-status, implementation-guide)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 01:55:00 +02:00
gavrielc
a689f8b3fa fix: quote ASSISTANT_NAME in .env to handle special characters
Use pipe delimiter in sed and quote the value to prevent breakage
from names containing /, &, \, or spaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:31:02 +02:00
github-actions[bot]
6f719879aa docs: update token count to 35.6k tokens · 18% of context window 2026-02-18 08:30:11 +00:00
Koshkoshinsk
802805d2ec Fix/WA reconnect, container perms, assist name in env (#297)
* fix: WA 515 stream error reconnect exiting early before key sync

Pass isReconnect flag on 515 reconnect so the registered-creds check
doesn't bail out before the handshake completes (caused "logging in..."
hang after successful pairing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: container permission errors on Docker with non-default uid

Make /home/node world-writable in the Dockerfile so the SDK can write
.claude.json. Add --user flag matching host uid/gid in container-runner
so bind-mounted files are accessible. Skip when running as root (uid 0),
as the container's node user (uid 1000), or on native Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: write ASSISTANT_NAME to .env during setup

When a custom assistant name is chosen, persist it to .env so config.ts
picks it up at runtime. Uses temp file for cross-platform sed
compatibility (macOS/Linux/WSL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:29:55 +02:00
gavrielc
f257b93d34 chore: update Discord invite link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 07:17:01 +02:00
gavrielc
b7c9d98ec2 fix: ensure setup skill runs Docker conversion before building containers
The setup skill would skip the /convert-to-docker step because:
1. The conversion instructions were buried inline in runtime choice bullets
2. The convert-to-docker skill had disable-model-invocation: true

Restructured step 3 into explicit sub-steps with a hard gate (3b) that
checks source files for Apple Container references before allowing the
build to proceed. Enabled model invocation on the convert-to-docker skill.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 22:17:51 +02:00
gavrielc
ff574a2f8b chore: update social preview with new subtitle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:10:17 +02:00
gavrielc
b125cb17cc chore: add nanoclaw profile and sales images
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 00:30:26 +02:00
gavrielc
88140ec1bb feat: add setup skill with scripted steps (#258)
Replace inline SKILL.md instructions with executable shell scripts
for each setup phase (environment check, deps, container, auth,
groups, channels, mounts, service, verify). Scripts emit structured
status blocks for reliable parsing.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 00:23:49 +02:00
github-actions[bot]
5694ac9b87 docs: update token count to 35.5k tokens · 18% of context window 2026-02-15 15:34:07 +00:00
gavrielc
5031d0f0d8 ci: add workflow_dispatch trigger to token count workflow (#254)
Allows manual triggering for testing and on-demand badge updates.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:33:45 +02:00
gavrielc
c467941804 fix: add git pull --rebase before push in token count workflow (#253)
Prevents push failures when remote has newer commits since checkout.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:31:57 +02:00
gavrielc
e4d77cdba0 fix: use GitHub App token for token count workflow
Switches from default GITHUB_TOKEN to a scoped GitHub App token
so the workflow can push to the protected main branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:11:48 +02:00
gavrielc
9261a25531 feat: add is_bot_message column and support dedicated phone numbers (#235)
* feat: add is_bot_message column and support dedicated phone numbers

Replace fragile content-prefix bot detection with an explicit
is_bot_message database column. The old prefix check (content NOT LIKE
'Andy:%') is kept as a backstop for pre-migration messages.

- Add is_bot_message column with automatic backfill migration
- Add ASSISTANT_HAS_OWN_NUMBER env var to skip name prefix when the
  assistant has its own WhatsApp number
- Move prefix logic into WhatsApp channel (no longer a router concern)
- Remove prefixAssistantName from Channel interface
- Load .env via dotenv so launchd-managed processes pick up config
- WhatsApp bot detection: fromMe for own number, prefix match for shared

Based on #160 and #173.

Co-Authored-By: Stefan Gasser <stefan@stefangasser.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared .env parser and remove dotenv dependency

Extract .env parsing into src/env.ts, used by both config.ts and
container-runner.ts. Reads only requested keys without loading secrets
into process.env, avoiding leaking API keys to child processes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Stefan Gasser <stefan@stefangasser.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:31:57 +02:00
gavrielc
c8ab3d95e1 feat: add repo-tokens GitHub Action with token count badge
Reusable composite action that counts codebase tokens using tiktoken
and generates a shields.io-style SVG badge. Color reflects context
window usage: green (<30%), yellow-green (30-50%), yellow (50-70%),
red (70%+). Badge includes hardcoded link back to repo-tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:41:14 +02:00
Gavriel Cohen
6f2e10f0c3 fix: typing indicator now shows on every message, not just the first
Two issues fixed:
- Use 'paused' instead of 'available' to stop typing. Baileys'
  sendPresenceUpdate('available') sends a global <presence> stanza and
  ignores the JID, so chatstate never left 'composing' and WhatsApp
  suppressed duplicate composing notifications per XEP-0085.
- Add setTyping call when piping messages to an already-running
  container. Previously only the first message (which spawns a new
  container) triggered the typing indicator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 14:28:08 +02:00
gavrielc
5c68deef76 fix: repair WhatsApp channel tests (missing Browsers mock and async flush)
Added missing Browsers mock to the Baileys vi.mock and made
triggerMessages async to flush microtasks before assertions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:08:34 +02:00
gavrielc
ae474fd344 fix: use available instead of paused when stopping typing indicator
Sending 'paused' after the first response caused WhatsApp to stop
relaying subsequent 'composing' presence updates. Using 'available'
keeps the bot in a state where typing indicators work consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:05:36 +02:00
gavrielc
658f6b02d3 fix: send available presence on connect so typing indicators work consistently
Without announcing 'available' after connecting, WhatsApp stops relaying
composing/paused presence updates after the first message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:53:03 +02:00
gavrielc
1549ad503e security: pass secrets via SDK env option and delete temp file (#213)
Pass secrets to the SDK via the `env` query option instead of setting
process.env, so Bash subprocesses never inherit API keys. Delete
/tmp/input.json immediately after reading to remove secrets from disk.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:46:42 +02:00
Cole
1a07869329 security: sanitize env vars from agent Bash subprocesses (#171)
Use a PreToolUse SDK hook to prepend `unset ANTHROPIC_API_KEY
CLAUDE_CODE_OAUTH_TOKEN` to every Bash command Kit runs, preventing
secret leakage via env/printenv/echo/$PROC. Secrets are now passed
via stdin JSON instead of mounted env files, closing all known
exfiltration vectors.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:33:39 +02:00
gavrielc
c30bd62417 docs: update Chinese README and move language link to badge row
Move the language switcher from above the logo into the badge row
alongside Discord. Update README_zh.md to match current English
README: add Agent Swarms announcement and feature, update
architecture section with all key files, add Community section,
and fix file descriptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:35:24 +02:00