Commit Graph

180 Commits

Author SHA1 Message Date
gavrielc
f59ca7cd6d docs: make /update skill discoverable, add auto version bumping
Add /update to skills tables in CLAUDE.md and REQUIREMENTS.md. Add
"Updating" section to README. Remove /add-telegram and /add-discord
from RFS (already exist). Add CI workflow to bump patch version on
source/container changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 02:04:55 +02:00
gavrielc
1216b5b99c feat: add /update skill for pulling upstream changes (#372)
Interactive skill that guides Claude through fetching upstream NanoClaw,
previewing changes, merging with customizations, running migrations, and
verifying the result. Includes:

- SKILL.md with 9-step update flow
- fetch-upstream.sh: detects remote, fetches, extracts tracked paths
- run-migrations.ts: discovers and runs version-ordered migrations
- post-update.ts: clears backup after conflict resolution
- update-core.ts: adds --json and --preview-only flags
- BASE_INCLUDES moved to constants.ts as single source of truth
- 16 new tests covering fetch, migrations, and CLI flags

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:03:13 +02:00
github-actions[bot]
628d434799 docs: update token count to 38.0k tokens · 19% of context window 2026-02-22 22:12:05 +00:00
Chujiang
9fb1790e12 fix: improve type safety and add error logging (#378)
- Replace 'as any' with proper type definition for error status code access
- Add error logging to sendPresenceUpdate() catch block
- Add debug logging when .env file is not found

These changes improve type safety and visibility into potential failures
without changing any core functionality.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: gavrielc <gabicohen22@yahoo.com>
2026-02-23 00:11:52 +02:00
Lawyered
856f98023c Fix critical skills path-remap root escape (including symlink traversal) (#367)
* Block skills path-remap escapes outside project root

* Harden path remap against symlink-based root escape

* test: isolate update tests from real git index
2026-02-23 00:10:45 +02:00
Tom Sella
264f855566 Replace 'ask the user' with AskUserQuestion tool in skills (#389)
- Updated add-voice-transcription to use AskUserQuestion for API key prompt
- Updated add-gmail to use AskUserQuestion for mode selection and channel config
- Updated add-discord to use AskUserQuestion for mode and token prompts
- Updated add-parallel to use AskUserQuestion for API key and permission prompts
- Updated add-telegram to use AskUserQuestion for mode and token prompts
- Updated setup to use AskUserQuestion for Node.js, Docker, and container runtime prompts

The AskUserQuestion tool provides a structured way to collect user input during
skill execution, making the interaction pattern consistent across all skills.
2026-02-23 00:08:14 +02:00
Daniel M
e59856fbec Fix: filter empty messages from polling queries (#383)
* fix: filter empty messages from polling queries

WhatsApp history sync writes empty protocol artifacts (delivery receipts,
status updates) to the database. On fresh installs, the main channel
(no trigger required) picks these up and spawns a container agent
unnecessarily, causing a message loop on first startup.

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

* fix: update test to match empty-content filtering in queries

The getMessagesSince query now filters out empty messages, so the test
should expect 0 results instead of 1.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:06:08 +02:00
github-actions[bot]
f5b2068852 docs: update token count to 37.9k tokens · 19% of context window 2026-02-22 22:04:36 +00:00
gavrielc
5958175ba1 fix: use 'Assistant' as fallback name instead of 'AssistantNameMissing'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:04:16 +02:00
gavrielc
495b7df5fc merge: resolve conflict with origin/main
Keep ASSISTANT_NAME import, drop removed GROUPS_DIR import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:03:20 +02:00
github-actions[bot]
1046a79682 docs: update token count to 37.8k tokens · 19% of context window 2026-02-22 21:23:47 +00:00
gavrielc
77f7423172 fix: pass host timezone to container and reject UTC-suffixed timestamps (#371)
Containers had no TZ set, so any time-aware code inside ran in UTC while
the host interpreted bare timestamps as local time. Now TIMEZONE from
config.ts is passed via -e TZ= to the container args.

Also rejects Z-suffixed or offset-suffixed timestamps in the container's
schedule_task validation, since bare timestamps are expected to be local
time and silently accepting UTC suffixes would cause an offset mismatch.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:23:34 +02:00
Dan Shapiro
107aff850c fix: pass assistantName to container agent instead of hardcoding 'Andy'
The container agent-runner had 'Andy' hardcoded as the sender name in
archived conversation transcripts. This ignored the configurable
ASSISTANT_NAME setting, so users who changed their assistant's name
(via .env or config) would still see 'Andy' in transcripts.

- Add assistantName field to ContainerInput interface (both host and
  container copies)
- Pass ASSISTANT_NAME from config through to container in index.ts
  and task-scheduler.ts
- Thread assistantName through createPreCompactHook and
  formatTranscriptMarkdown in the agent-runner
- Use 'AssistantNameMissing' as fallback instead of 'Andy' so a
  missing name is visible rather than silently wrong

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:22:07 -08:00
github-actions[bot]
6b85ac59bd docs: update token count to 37.7k tokens · 19% of context window 2026-02-22 19:02:53 +00:00
gavrielc
2e1c768207 fix: block group folder path escapes (#387) 2026-02-22 21:02:27 +02:00
Lawyered
02d8528684 fix: pause malformed scheduled tasks 2026-02-22 21:01:53 +02:00
Lawyered
c6391cceb1 fix: block group folder path escapes 2026-02-22 21:01:53 +02:00
gavrielc
de64dab3e9 Merge pull request #369 from lawyered0/codex/fix-file-ops-symlink-escape
Fix critical symlink escape in skills file operations
2026-02-22 21:01:11 +02:00
github-actions[bot]
6d4277f669 docs: update token count to 37.0k tokens · 18% of context window 2026-02-22 18:58:12 +00:00
gavrielc
5fb10645cd fix: mount project root read-only to prevent container escape (#392)
The main group's project root was mounted read-write, allowing the
container agent to modify host application code (e.g. dist/container-runner.js)
to inject arbitrary mounts on next restart — a full sandbox escape.

Fix: mount the project root read-only. Writable paths the agent needs
(group folder, IPC, .claude/) are already mounted separately. The
agent-runner source is now copied into a per-group writable location
so agents can still customize container-side behavior without affecting
host code or other groups.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:57:57 +02:00
gavrielc
ef00320018 Update README.md 2026-02-22 18:47:01 +02:00
github-actions[bot]
e8d1d1ee55 docs: update token count to 36.9k tokens · 18% of context window 2026-02-22 16:43:42 +00:00
gavrielc
92d14405c5 refactor: move setup scripts out of src/ to reduce build token count
Setup scripts are standalone CLI tools run via tsx with no runtime
imports from the main app. Moving them out of src/ excludes them from
the tsc build output and reduces the compiled bundle size.

- git mv src/setup/ setup/
- Fix imports to use ../src/logger.js and ../src/config.js
- Update package.json, vitest.config.ts, SKILL.md references
- Fix platform tests to be cross-platform (macOS + Linux)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:43:22 +02:00
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