@rozek/nanoclaw 1.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/skills/add-compact/SKILL.md +135 -0
  3. package/.claude/skills/add-discord/SKILL.md +203 -0
  4. package/.claude/skills/add-gmail/SKILL.md +220 -0
  5. package/.claude/skills/add-image-vision/SKILL.md +94 -0
  6. package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
  7. package/.claude/skills/add-parallel/SKILL.md +290 -0
  8. package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
  9. package/.claude/skills/add-reactions/SKILL.md +117 -0
  10. package/.claude/skills/add-slack/SKILL.md +207 -0
  11. package/.claude/skills/add-telegram/SKILL.md +222 -0
  12. package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
  13. package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
  14. package/.claude/skills/add-whatsapp/SKILL.md +372 -0
  15. package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
  16. package/.claude/skills/customize/SKILL.md +110 -0
  17. package/.claude/skills/debug/SKILL.md +349 -0
  18. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  19. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  20. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  21. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  22. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  23. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  24. package/.claude/skills/setup/SKILL.md +218 -0
  25. package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
  26. package/.claude/skills/update-skills/SKILL.md +130 -0
  27. package/.claude/skills/use-local-whisper/SKILL.md +152 -0
  28. package/.claude/skills/x-integration/SKILL.md +417 -0
  29. package/.claude/skills/x-integration/agent.ts +243 -0
  30. package/.claude/skills/x-integration/host.ts +159 -0
  31. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  32. package/.claude/skills/x-integration/lib/config.ts +62 -0
  33. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  34. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  35. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  36. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  37. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  38. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  39. package/.env.example +1 -0
  40. package/.github/CODEOWNERS +10 -0
  41. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  42. package/.github/workflows/bump-version.yml +32 -0
  43. package/.github/workflows/ci.yml +25 -0
  44. package/.github/workflows/merge-forward-skills.yml +160 -0
  45. package/.github/workflows/update-tokens.yml +42 -0
  46. package/.husky/pre-commit +1 -0
  47. package/.mcp.json +3 -0
  48. package/.nvmrc +1 -0
  49. package/.prettierrc +3 -0
  50. package/CHANGELOG.md +8 -0
  51. package/CLAUDE.md +64 -0
  52. package/CONTRIBUTING.md +23 -0
  53. package/CONTRIBUTORS.md +15 -0
  54. package/LICENSE +21 -0
  55. package/NanoClaw_with_Web-Support.md +290 -0
  56. package/README.md +261 -0
  57. package/README_zh.md +200 -0
  58. package/assets/nanoclaw-favicon.png +0 -0
  59. package/assets/nanoclaw-icon.png +0 -0
  60. package/assets/nanoclaw-logo-dark.png +0 -0
  61. package/assets/nanoclaw-logo.png +0 -0
  62. package/assets/nanoclaw-profile.jpeg +0 -0
  63. package/assets/nanoclaw-sales.png +0 -0
  64. package/assets/social-preview.jpg +0 -0
  65. package/config-examples/mount-allowlist.json +25 -0
  66. package/container/Dockerfile +70 -0
  67. package/container/agent-runner/package-lock.json +1524 -0
  68. package/container/agent-runner/package.json +21 -0
  69. package/container/agent-runner/src/index.ts +558 -0
  70. package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
  71. package/container/agent-runner/tsconfig.json +15 -0
  72. package/container/build.sh +23 -0
  73. package/container/skills/agent-browser/SKILL.md +159 -0
  74. package/container/skills/capabilities/SKILL.md +100 -0
  75. package/container/skills/status/SKILL.md +104 -0
  76. package/dist/channels/index.d.ts +2 -0
  77. package/dist/channels/index.d.ts.map +1 -0
  78. package/dist/channels/index.js +9 -0
  79. package/dist/channels/index.js.map +1 -0
  80. package/dist/channels/registry.d.ts +13 -0
  81. package/dist/channels/registry.d.ts.map +1 -0
  82. package/dist/channels/registry.js +11 -0
  83. package/dist/channels/registry.js.map +1 -0
  84. package/dist/channels/registry.test.d.ts +2 -0
  85. package/dist/channels/registry.test.d.ts.map +1 -0
  86. package/dist/channels/registry.test.js +32 -0
  87. package/dist/channels/registry.test.js.map +1 -0
  88. package/dist/channels/web.d.ts +2 -0
  89. package/dist/channels/web.d.ts.map +1 -0
  90. package/dist/channels/web.js +1738 -0
  91. package/dist/channels/web.js.map +1 -0
  92. package/dist/cli.d.ts +11 -0
  93. package/dist/cli.d.ts.map +1 -0
  94. package/dist/cli.js +182 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/config.d.ts +19 -0
  97. package/dist/config.d.ts.map +1 -0
  98. package/dist/config.js +36 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/container-runner.d.ts +44 -0
  101. package/dist/container-runner.d.ts.map +1 -0
  102. package/dist/container-runner.js +467 -0
  103. package/dist/container-runner.js.map +1 -0
  104. package/dist/container-runner.test.d.ts +2 -0
  105. package/dist/container-runner.test.d.ts.map +1 -0
  106. package/dist/container-runner.test.js +150 -0
  107. package/dist/container-runner.test.js.map +1 -0
  108. package/dist/container-runtime.d.ts +22 -0
  109. package/dist/container-runtime.d.ts.map +1 -0
  110. package/dist/container-runtime.js +96 -0
  111. package/dist/container-runtime.js.map +1 -0
  112. package/dist/container-runtime.test.d.ts +2 -0
  113. package/dist/container-runtime.test.d.ts.map +1 -0
  114. package/dist/container-runtime.test.js +93 -0
  115. package/dist/container-runtime.test.js.map +1 -0
  116. package/dist/credential-proxy.d.ts +21 -0
  117. package/dist/credential-proxy.d.ts.map +1 -0
  118. package/dist/credential-proxy.js +95 -0
  119. package/dist/credential-proxy.js.map +1 -0
  120. package/dist/credential-proxy.test.d.ts +2 -0
  121. package/dist/credential-proxy.test.d.ts.map +1 -0
  122. package/dist/credential-proxy.test.js +134 -0
  123. package/dist/credential-proxy.test.js.map +1 -0
  124. package/dist/db.d.ts +115 -0
  125. package/dist/db.d.ts.map +1 -0
  126. package/dist/db.js +549 -0
  127. package/dist/db.js.map +1 -0
  128. package/dist/db.test.d.ts +2 -0
  129. package/dist/db.test.d.ts.map +1 -0
  130. package/dist/db.test.js +360 -0
  131. package/dist/db.test.js.map +1 -0
  132. package/dist/env.d.ts +8 -0
  133. package/dist/env.d.ts.map +1 -0
  134. package/dist/env.js +42 -0
  135. package/dist/env.js.map +1 -0
  136. package/dist/formatting.test.d.ts +2 -0
  137. package/dist/formatting.test.d.ts.map +1 -0
  138. package/dist/formatting.test.js +183 -0
  139. package/dist/formatting.test.js.map +1 -0
  140. package/dist/group-folder.d.ts +5 -0
  141. package/dist/group-folder.d.ts.map +1 -0
  142. package/dist/group-folder.js +44 -0
  143. package/dist/group-folder.js.map +1 -0
  144. package/dist/group-folder.test.d.ts +2 -0
  145. package/dist/group-folder.test.d.ts.map +1 -0
  146. package/dist/group-folder.test.js +29 -0
  147. package/dist/group-folder.test.js.map +1 -0
  148. package/dist/group-queue.d.ts +34 -0
  149. package/dist/group-queue.d.ts.map +1 -0
  150. package/dist/group-queue.js +263 -0
  151. package/dist/group-queue.js.map +1 -0
  152. package/dist/group-queue.test.d.ts +2 -0
  153. package/dist/group-queue.test.d.ts.map +1 -0
  154. package/dist/group-queue.test.js +341 -0
  155. package/dist/group-queue.test.js.map +1 -0
  156. package/dist/index.d.ts +12 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +518 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/ipc-auth.test.d.ts +2 -0
  161. package/dist/ipc-auth.test.d.ts.map +1 -0
  162. package/dist/ipc-auth.test.js +434 -0
  163. package/dist/ipc-auth.test.js.map +1 -0
  164. package/dist/ipc.d.ts +32 -0
  165. package/dist/ipc.d.ts.map +1 -0
  166. package/dist/ipc.js +311 -0
  167. package/dist/ipc.js.map +1 -0
  168. package/dist/logger.d.ts +3 -0
  169. package/dist/logger.d.ts.map +1 -0
  170. package/dist/logger.js +14 -0
  171. package/dist/logger.js.map +1 -0
  172. package/dist/mount-security.d.ts +34 -0
  173. package/dist/mount-security.d.ts.map +1 -0
  174. package/dist/mount-security.js +325 -0
  175. package/dist/mount-security.js.map +1 -0
  176. package/dist/remote-control.d.ts +32 -0
  177. package/dist/remote-control.d.ts.map +1 -0
  178. package/dist/remote-control.js +185 -0
  179. package/dist/remote-control.js.map +1 -0
  180. package/dist/remote-control.test.d.ts +2 -0
  181. package/dist/remote-control.test.d.ts.map +1 -0
  182. package/dist/remote-control.test.js +321 -0
  183. package/dist/remote-control.test.js.map +1 -0
  184. package/dist/router.d.ts +8 -0
  185. package/dist/router.d.ts.map +1 -0
  186. package/dist/router.js +37 -0
  187. package/dist/router.js.map +1 -0
  188. package/dist/routing.test.d.ts +2 -0
  189. package/dist/routing.test.d.ts.map +1 -0
  190. package/dist/routing.test.js +81 -0
  191. package/dist/routing.test.js.map +1 -0
  192. package/dist/sender-allowlist.d.ts +14 -0
  193. package/dist/sender-allowlist.d.ts.map +1 -0
  194. package/dist/sender-allowlist.js +79 -0
  195. package/dist/sender-allowlist.js.map +1 -0
  196. package/dist/sender-allowlist.test.d.ts +2 -0
  197. package/dist/sender-allowlist.test.d.ts.map +1 -0
  198. package/dist/sender-allowlist.test.js +186 -0
  199. package/dist/sender-allowlist.test.js.map +1 -0
  200. package/dist/session-commands.d.ts +47 -0
  201. package/dist/session-commands.d.ts.map +1 -0
  202. package/dist/session-commands.js +102 -0
  203. package/dist/session-commands.js.map +1 -0
  204. package/dist/session-commands.test.d.ts +2 -0
  205. package/dist/session-commands.test.d.ts.map +1 -0
  206. package/dist/session-commands.test.js +190 -0
  207. package/dist/session-commands.test.js.map +1 -0
  208. package/dist/task-scheduler.d.ts +22 -0
  209. package/dist/task-scheduler.d.ts.map +1 -0
  210. package/dist/task-scheduler.js +210 -0
  211. package/dist/task-scheduler.js.map +1 -0
  212. package/dist/task-scheduler.test.d.ts +2 -0
  213. package/dist/task-scheduler.test.d.ts.map +1 -0
  214. package/dist/task-scheduler.test.js +107 -0
  215. package/dist/task-scheduler.test.js.map +1 -0
  216. package/dist/timezone.d.ts +6 -0
  217. package/dist/timezone.d.ts.map +1 -0
  218. package/dist/timezone.js +17 -0
  219. package/dist/timezone.js.map +1 -0
  220. package/dist/timezone.test.d.ts +2 -0
  221. package/dist/timezone.test.d.ts.map +1 -0
  222. package/dist/timezone.test.js +23 -0
  223. package/dist/timezone.test.js.map +1 -0
  224. package/dist/types.d.ts +78 -0
  225. package/dist/types.d.ts.map +1 -0
  226. package/dist/types.js +2 -0
  227. package/dist/types.js.map +1 -0
  228. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  229. package/docs/DEBUG_CHECKLIST.md +143 -0
  230. package/docs/REQUIREMENTS.md +196 -0
  231. package/docs/SDK_DEEP_DIVE.md +643 -0
  232. package/docs/SECURITY.md +122 -0
  233. package/docs/SPEC.md +785 -0
  234. package/docs/docker-sandboxes.md +359 -0
  235. package/docs/nanoclaw-architecture-final.md +1063 -0
  236. package/docs/nanorepo-architecture.md +168 -0
  237. package/docs/skills-as-branches.md +662 -0
  238. package/groups/global/CLAUDE.md +58 -0
  239. package/groups/main/CLAUDE.md +246 -0
  240. package/launchd/com.nanoclaw.plist +32 -0
  241. package/package.json +45 -0
  242. package/repo-tokens/README.md +113 -0
  243. package/repo-tokens/action.yml +186 -0
  244. package/repo-tokens/badge.svg +23 -0
  245. package/repo-tokens/examples/green.svg +14 -0
  246. package/repo-tokens/examples/red.svg +14 -0
  247. package/repo-tokens/examples/yellow-green.svg +14 -0
  248. package/repo-tokens/examples/yellow.svg +14 -0
  249. package/scripts/run-migrations.ts +105 -0
  250. package/setup/container.ts +144 -0
  251. package/setup/environment.test.ts +121 -0
  252. package/setup/environment.ts +94 -0
  253. package/setup/groups.ts +229 -0
  254. package/setup/index.ts +58 -0
  255. package/setup/mounts.ts +115 -0
  256. package/setup/platform.test.ts +120 -0
  257. package/setup/platform.ts +132 -0
  258. package/setup/register.test.ts +257 -0
  259. package/setup/register.ts +177 -0
  260. package/setup/service.test.ts +187 -0
  261. package/setup/service.ts +362 -0
  262. package/setup/status.ts +16 -0
  263. package/setup/verify.ts +192 -0
  264. package/setup.sh +161 -0
  265. package/src/channels/index.ts +12 -0
  266. package/src/channels/registry.test.ts +42 -0
  267. package/src/channels/registry.ts +32 -0
  268. package/src/channels/web.ts +1856 -0
  269. package/src/cli.ts +209 -0
  270. package/src/config.ts +73 -0
  271. package/src/container-runner.test.ts +210 -0
  272. package/src/container-runner.ts +707 -0
  273. package/src/container-runtime.test.ts +149 -0
  274. package/src/container-runtime.ts +127 -0
  275. package/src/credential-proxy.test.ts +192 -0
  276. package/src/credential-proxy.ts +125 -0
  277. package/src/db.test.ts +484 -0
  278. package/src/db.ts +803 -0
  279. package/src/env.ts +42 -0
  280. package/src/formatting.test.ts +256 -0
  281. package/src/group-folder.test.ts +43 -0
  282. package/src/group-folder.ts +44 -0
  283. package/src/group-queue.test.ts +484 -0
  284. package/src/group-queue.ts +365 -0
  285. package/src/index.ts +731 -0
  286. package/src/ipc-auth.test.ts +679 -0
  287. package/src/ipc.ts +461 -0
  288. package/src/logger.ts +16 -0
  289. package/src/mount-security.ts +419 -0
  290. package/src/remote-control.test.ts +397 -0
  291. package/src/remote-control.ts +224 -0
  292. package/src/router.ts +52 -0
  293. package/src/routing.test.ts +170 -0
  294. package/src/sender-allowlist.test.ts +216 -0
  295. package/src/sender-allowlist.ts +128 -0
  296. package/src/session-commands.test.ts +247 -0
  297. package/src/session-commands.ts +163 -0
  298. package/src/task-scheduler.test.ts +129 -0
  299. package/src/task-scheduler.ts +295 -0
  300. package/src/timezone.test.ts +29 -0
  301. package/src/timezone.ts +16 -0
  302. package/src/types.ts +107 -0
  303. package/tsconfig.json +20 -0
  304. package/vitest.config.ts +7 -0
  305. package/vitest.skills.config.ts +7 -0
@@ -0,0 +1,58 @@
1
+ # Andy
2
+
3
+ You are Andy, a personal assistant. You help with tasks, answer questions, and can schedule reminders.
4
+
5
+ ## What You Can Do
6
+
7
+ - Answer questions and have conversations
8
+ - Search the web and fetch content from URLs
9
+ - **Browse the web** with `agent-browser` — open pages, click, fill forms, take screenshots, extract data (run `agent-browser open <url>` to start, then `agent-browser snapshot -i` to see interactive elements)
10
+ - Read and write files in your workspace
11
+ - Run bash commands in your sandbox
12
+ - Schedule tasks to run later or on a recurring basis
13
+ - Send messages back to the chat
14
+
15
+ ## Communication
16
+
17
+ Your output is sent to the user or group.
18
+
19
+ You also have `mcp__nanoclaw__send_message` which sends a message immediately while you're still working. This is useful when you want to acknowledge a request before starting longer work.
20
+
21
+ ### Internal thoughts
22
+
23
+ If part of your output is internal reasoning rather than something for the user, wrap it in `<internal>` tags:
24
+
25
+ ```
26
+ <internal>Compiled all three reports, ready to summarize.</internal>
27
+
28
+ Here are the key findings from the research...
29
+ ```
30
+
31
+ Text inside `<internal>` tags is logged but not sent to the user. If you've already sent the key information via `send_message`, you can wrap the recap in `<internal>` to avoid sending it again.
32
+
33
+ ### Sub-agents and teammates
34
+
35
+ When working as a sub-agent or teammate, only use `send_message` if instructed to by the main agent.
36
+
37
+ ## Your Workspace
38
+
39
+ Files you create are saved in `/workspace/group/`. Use this for notes, research, or anything that should persist.
40
+
41
+ ## Memory
42
+
43
+ The `conversations/` folder contains searchable history of past conversations. Use this to recall context from previous sessions.
44
+
45
+ When you learn something important:
46
+ - Create files for structured data (e.g., `customers.md`, `preferences.md`)
47
+ - Split files larger than 500 lines into folders
48
+ - Keep an index in your memory for the files you create
49
+
50
+ ## Message Formatting
51
+
52
+ NEVER use markdown. Only use WhatsApp/Telegram formatting:
53
+ - *single asterisks* for bold (NEVER **double asterisks**)
54
+ - _underscores_ for italic
55
+ - • bullet points
56
+ - ```triple backticks``` for code
57
+
58
+ No ## headings. No [links](url). No **double stars**.
@@ -0,0 +1,246 @@
1
+ # Andy
2
+
3
+ You are Andy, a personal assistant. You help with tasks, answer questions, and can schedule reminders.
4
+
5
+ ## What You Can Do
6
+
7
+ - Answer questions and have conversations
8
+ - Search the web and fetch content from URLs
9
+ - **Browse the web** with `agent-browser` — open pages, click, fill forms, take screenshots, extract data (run `agent-browser open <url>` to start, then `agent-browser snapshot -i` to see interactive elements)
10
+ - Read and write files in your workspace
11
+ - Run bash commands in your sandbox
12
+ - Schedule tasks to run later or on a recurring basis
13
+ - Send messages back to the chat
14
+
15
+ ## Communication
16
+
17
+ Your output is sent to the user or group.
18
+
19
+ You also have `mcp__nanoclaw__send_message` which sends a message immediately while you're still working. This is useful when you want to acknowledge a request before starting longer work.
20
+
21
+ ### Internal thoughts
22
+
23
+ If part of your output is internal reasoning rather than something for the user, wrap it in `<internal>` tags:
24
+
25
+ ```
26
+ <internal>Compiled all three reports, ready to summarize.</internal>
27
+
28
+ Here are the key findings from the research...
29
+ ```
30
+
31
+ Text inside `<internal>` tags is logged but not sent to the user. If you've already sent the key information via `send_message`, you can wrap the recap in `<internal>` to avoid sending it again.
32
+
33
+ ### Sub-agents and teammates
34
+
35
+ When working as a sub-agent or teammate, only use `send_message` if instructed to by the main agent.
36
+
37
+ ## Memory
38
+
39
+ The `conversations/` folder contains searchable history of past conversations. Use this to recall context from previous sessions.
40
+
41
+ When you learn something important:
42
+ - Create files for structured data (e.g., `customers.md`, `preferences.md`)
43
+ - Split files larger than 500 lines into folders
44
+ - Keep an index in your memory for the files you create
45
+
46
+ ## WhatsApp Formatting (and other messaging apps)
47
+
48
+ Do NOT use markdown headings (##) in WhatsApp messages. Only use:
49
+ - *Bold* (single asterisks) (NEVER **double asterisks**)
50
+ - _Italic_ (underscores)
51
+ - • Bullets (bullet points)
52
+ - ```Code blocks``` (triple backticks)
53
+
54
+ Keep messages clean and readable for WhatsApp.
55
+
56
+ ---
57
+
58
+ ## Admin Context
59
+
60
+ This is the **main channel**, which has elevated privileges.
61
+
62
+ ## Container Mounts
63
+
64
+ Main has read-only access to the project and read-write access to its group folder:
65
+
66
+ | Container Path | Host Path | Access |
67
+ |----------------|-----------|--------|
68
+ | `/workspace/project` | Project root | read-only |
69
+ | `/workspace/group` | `groups/main/` | read-write |
70
+
71
+ Key paths inside the container:
72
+ - `/workspace/project/store/messages.db` - SQLite database
73
+ - `/workspace/project/store/messages.db` (registered_groups table) - Group config
74
+ - `/workspace/project/groups/` - All group folders
75
+
76
+ ---
77
+
78
+ ## Managing Groups
79
+
80
+ ### Finding Available Groups
81
+
82
+ Available groups are provided in `/workspace/ipc/available_groups.json`:
83
+
84
+ ```json
85
+ {
86
+ "groups": [
87
+ {
88
+ "jid": "120363336345536173@g.us",
89
+ "name": "Family Chat",
90
+ "lastActivity": "2026-01-31T12:00:00.000Z",
91
+ "isRegistered": false
92
+ }
93
+ ],
94
+ "lastSync": "2026-01-31T12:00:00.000Z"
95
+ }
96
+ ```
97
+
98
+ Groups are ordered by most recent activity. The list is synced from WhatsApp daily.
99
+
100
+ If a group the user mentions isn't in the list, request a fresh sync:
101
+
102
+ ```bash
103
+ echo '{"type": "refresh_groups"}' > /workspace/ipc/tasks/refresh_$(date +%s).json
104
+ ```
105
+
106
+ Then wait a moment and re-read `available_groups.json`.
107
+
108
+ **Fallback**: Query the SQLite database directly:
109
+
110
+ ```bash
111
+ sqlite3 /workspace/project/store/messages.db "
112
+ SELECT jid, name, last_message_time
113
+ FROM chats
114
+ WHERE jid LIKE '%@g.us' AND jid != '__group_sync__'
115
+ ORDER BY last_message_time DESC
116
+ LIMIT 10;
117
+ "
118
+ ```
119
+
120
+ ### Registered Groups Config
121
+
122
+ Groups are registered in the SQLite `registered_groups` table:
123
+
124
+ ```json
125
+ {
126
+ "1234567890-1234567890@g.us": {
127
+ "name": "Family Chat",
128
+ "folder": "whatsapp_family-chat",
129
+ "trigger": "@Andy",
130
+ "added_at": "2024-01-31T12:00:00.000Z"
131
+ }
132
+ }
133
+ ```
134
+
135
+ Fields:
136
+ - **Key**: The chat JID (unique identifier — WhatsApp, Telegram, Slack, Discord, etc.)
137
+ - **name**: Display name for the group
138
+ - **folder**: Channel-prefixed folder name under `groups/` for this group's files and memory
139
+ - **trigger**: The trigger word (usually same as global, but could differ)
140
+ - **requiresTrigger**: Whether `@trigger` prefix is needed (default: `true`). Set to `false` for solo/personal chats where all messages should be processed
141
+ - **isMain**: Whether this is the main control group (elevated privileges, no trigger required)
142
+ - **added_at**: ISO timestamp when registered
143
+
144
+ ### Trigger Behavior
145
+
146
+ - **Main group** (`isMain: true`): No trigger needed — all messages are processed automatically
147
+ - **Groups with `requiresTrigger: false`**: No trigger needed — all messages processed (use for 1-on-1 or solo chats)
148
+ - **Other groups** (default): Messages must start with `@AssistantName` to be processed
149
+
150
+ ### Adding a Group
151
+
152
+ 1. Query the database to find the group's JID
153
+ 2. Use the `register_group` MCP tool with the JID, name, folder, and trigger
154
+ 3. Optionally include `containerConfig` for additional mounts
155
+ 4. The group folder is created automatically: `/workspace/project/groups/{folder-name}/`
156
+ 5. Optionally create an initial `CLAUDE.md` for the group
157
+
158
+ Folder naming convention — channel prefix with underscore separator:
159
+ - WhatsApp "Family Chat" → `whatsapp_family-chat`
160
+ - Telegram "Dev Team" → `telegram_dev-team`
161
+ - Discord "General" → `discord_general`
162
+ - Slack "Engineering" → `slack_engineering`
163
+ - Use lowercase, hyphens for the group name part
164
+
165
+ #### Adding Additional Directories for a Group
166
+
167
+ Groups can have extra directories mounted. Add `containerConfig` to their entry:
168
+
169
+ ```json
170
+ {
171
+ "1234567890@g.us": {
172
+ "name": "Dev Team",
173
+ "folder": "dev-team",
174
+ "trigger": "@Andy",
175
+ "added_at": "2026-01-31T12:00:00Z",
176
+ "containerConfig": {
177
+ "additionalMounts": [
178
+ {
179
+ "hostPath": "~/projects/webapp",
180
+ "containerPath": "webapp",
181
+ "readonly": false
182
+ }
183
+ ]
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ The directory will appear at `/workspace/extra/webapp` in that group's container.
190
+
191
+ #### Sender Allowlist
192
+
193
+ After registering a group, explain the sender allowlist feature to the user:
194
+
195
+ > This group can be configured with a sender allowlist to control who can interact with me. There are two modes:
196
+ >
197
+ > - **Trigger mode** (default): Everyone's messages are stored for context, but only allowed senders can trigger me with @{AssistantName}.
198
+ > - **Drop mode**: Messages from non-allowed senders are not stored at all.
199
+ >
200
+ > For closed groups with trusted members, I recommend setting up an allow-only list so only specific people can trigger me. Want me to configure that?
201
+
202
+ If the user wants to set up an allowlist, edit `~/.config/nanoclaw/sender-allowlist.json` on the host:
203
+
204
+ ```json
205
+ {
206
+ "default": { "allow": "*", "mode": "trigger" },
207
+ "chats": {
208
+ "<chat-jid>": {
209
+ "allow": ["sender-id-1", "sender-id-2"],
210
+ "mode": "trigger"
211
+ }
212
+ },
213
+ "logDenied": true
214
+ }
215
+ ```
216
+
217
+ Notes:
218
+ - Your own messages (`is_from_me`) explicitly bypass the allowlist in trigger checks. Bot messages are filtered out by the database query before trigger evaluation, so they never reach the allowlist.
219
+ - If the config file doesn't exist or is invalid, all senders are allowed (fail-open)
220
+ - The config file is on the host at `~/.config/nanoclaw/sender-allowlist.json`, not inside the container
221
+
222
+ ### Removing a Group
223
+
224
+ 1. Read `/workspace/project/data/registered_groups.json`
225
+ 2. Remove the entry for that group
226
+ 3. Write the updated JSON back
227
+ 4. The group folder and its files remain (don't delete them)
228
+
229
+ ### Listing Groups
230
+
231
+ Read `/workspace/project/data/registered_groups.json` and format it nicely.
232
+
233
+ ---
234
+
235
+ ## Global Memory
236
+
237
+ You can read and write to `/workspace/project/groups/global/CLAUDE.md` for facts that should apply to all groups. Only update global memory when explicitly asked to "remember this globally" or similar.
238
+
239
+ ---
240
+
241
+ ## Scheduling for Other Groups
242
+
243
+ When scheduling tasks for other groups, use the `target_group_jid` parameter with the group's JID from `registered_groups.json`:
244
+ - `schedule_task(prompt: "...", schedule_type: "cron", schedule_value: "0 9 * * 1", target_group_jid: "120363336345536173@g.us")`
245
+
246
+ The task will run in that group's context with access to their files and memory.
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.nanoclaw</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>{{NODE_PATH}}</string>
10
+ <string>{{PROJECT_ROOT}}/dist/index.js</string>
11
+ </array>
12
+ <key>WorkingDirectory</key>
13
+ <string>{{PROJECT_ROOT}}</string>
14
+ <key>RunAtLoad</key>
15
+ <true/>
16
+ <key>KeepAlive</key>
17
+ <true/>
18
+ <key>EnvironmentVariables</key>
19
+ <dict>
20
+ <key>PATH</key>
21
+ <string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
22
+ <key>HOME</key>
23
+ <string>{{HOME}}</string>
24
+ <key>ASSISTANT_NAME</key>
25
+ <string>Andy</string>
26
+ </dict>
27
+ <key>StandardOutPath</key>
28
+ <string>{{PROJECT_ROOT}}/logs/nanoclaw.log</string>
29
+ <key>StandardErrorPath</key>
30
+ <string>{{PROJECT_ROOT}}/logs/nanoclaw.error.log</string>
31
+ </dict>
32
+ </plist>
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@rozek/nanoclaw",
3
+ "version": "1.2.17",
4
+ "description": "Personal Claude assistant. Lightweight, secure, customizable.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "nanoclaw": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts",
14
+ "typecheck": "tsc --noEmit",
15
+ "format": "prettier --write \"src/**/*.ts\"",
16
+ "format:fix": "prettier --write \"src/**/*.ts\"",
17
+ "format:check": "prettier --check \"src/**/*.ts\"",
18
+ "prepare": "husky",
19
+ "setup": "tsx setup/index.ts",
20
+ "auth": "tsx src/whatsapp-auth.ts",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest"
23
+ },
24
+ "dependencies": {
25
+ "better-sqlite3": "^11.8.1",
26
+ "cron-parser": "^5.5.0",
27
+ "pino": "^9.6.0",
28
+ "pino-pretty": "^13.0.0",
29
+ "yaml": "^2.8.2",
30
+ "zod": "^4.3.6"
31
+ },
32
+ "devDependencies": {
33
+ "@types/better-sqlite3": "^7.6.12",
34
+ "@types/node": "^22.10.0",
35
+ "@vitest/coverage-v8": "^4.0.18",
36
+ "husky": "^9.1.7",
37
+ "prettier": "^3.8.1",
38
+ "tsx": "^4.19.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^4.0.18"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ }
45
+ }
@@ -0,0 +1,113 @@
1
+ # Repo Tokens
2
+
3
+ A GitHub Action that calculates the size of your codebase in terms of tokens and updates a badge in your README.
4
+
5
+ <p>
6
+ <img src="examples/green.svg" alt="tokens 12.4k">&nbsp;
7
+ <img src="examples/yellow-green.svg" alt="tokens 74.8k">&nbsp;
8
+ <img src="examples/yellow.svg" alt="tokens 120k">&nbsp;
9
+ <img src="examples/red.svg" alt="tokens 158k">
10
+ </p>
11
+
12
+ ## Usage
13
+
14
+ ```yaml
15
+ - uses: qwibitai/nanoclaw/repo-tokens@v1
16
+ with:
17
+ include: 'src/**/*.ts'
18
+ exclude: 'src/**/*.test.ts'
19
+ ```
20
+
21
+ This counts tokens using [tiktoken](https://github.com/openai/tiktoken) and writes the result between HTML comment markers in your README:
22
+
23
+ The badge color reflects what percentage of an LLMs context window the codebase fills (context window size is configurable, defaults to 200k which is the size of Claude Opus). Green for under 30%, yellow-green for 30%-50%, yellow for 50%-70%, red for 70%+.
24
+
25
+ ## Why
26
+
27
+ Small codebases were always a good thing. With coding agents, there's now a huge advantage to having a codebase small enough that an agent can hold the full thing in context.
28
+
29
+ This badge gives some indication of how easy it will be to work with an agent on the codebase, and will hopefully be a visual reminder to avoid bloat.
30
+
31
+ ## Examples
32
+
33
+ Repos using repo-tokens:
34
+
35
+ | Repo | Badge |
36
+ |------|-------|
37
+ | [NanoClaw](https://github.com/qwibitai/NanoClaw) | ![tokens](https://raw.githubusercontent.com/qwibitai/NanoClaw/main/repo-tokens/badge.svg) |
38
+
39
+ ### Full workflow example
40
+
41
+ ```yaml
42
+ name: Update token count
43
+
44
+ on:
45
+ push:
46
+ branches: [main]
47
+ paths: ['src/**']
48
+
49
+ permissions:
50
+ contents: write
51
+
52
+ jobs:
53
+ update-tokens:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: '3.12'
61
+
62
+ - uses: qwibitai/nanoclaw/repo-tokens@v1
63
+ id: tokens
64
+ with:
65
+ include: 'src/**/*.ts'
66
+ exclude: 'src/**/*.test.ts'
67
+ badge-path: '.github/badges/tokens.svg'
68
+
69
+ - name: Commit if changed
70
+ run: |
71
+ git add README.md .github/badges/tokens.svg
72
+ git diff --cached --quiet && exit 0
73
+ git config user.name "github-actions[bot]"
74
+ git config user.email "github-actions[bot]@users.noreply.github.com"
75
+ git commit -m "docs: update token count to ${{ steps.tokens.outputs.badge }}"
76
+ git push
77
+ ```
78
+
79
+ ### README setup
80
+
81
+ Add markers where you want the token count text to appear:
82
+
83
+ ```html
84
+ <!-- token-count --><!-- /token-count -->
85
+ ```
86
+
87
+ The action replaces everything between the markers with the token count.
88
+
89
+ ## Inputs
90
+
91
+ | Input | Default | Description |
92
+ |-------|---------|-------------|
93
+ | `include` | *required* | Glob patterns for files to count (space-separated) |
94
+ | `exclude` | `''` | Glob patterns to exclude (space-separated) |
95
+ | `context-window` | `200000` | Context window size for percentage calculation |
96
+ | `readme` | `README.md` | Path to README file |
97
+ | `encoding` | `cl100k_base` | Tiktoken encoding name |
98
+ | `marker` | `token-count` | HTML comment marker name |
99
+ | `badge-path` | `''` | Path to write SVG badge (empty = no SVG) |
100
+
101
+ ## Outputs
102
+
103
+ | Output | Description |
104
+ |--------|-------------|
105
+ | `tokens` | Total token count (e.g., `34940`) |
106
+ | `percentage` | Percentage of context window (e.g., `17`) |
107
+ | `badge` | The formatted text that was inserted (e.g., `34.9k tokens · 17% of context window`) |
108
+
109
+ ## How it works
110
+
111
+ Composite GitHub Action. Installs tiktoken, runs ~60 lines of inline Python. Takes about 10 seconds.
112
+
113
+ The action counts tokens and updates the README but does not commit. Your workflow decides the git strategy.
@@ -0,0 +1,186 @@
1
+ name: Repo Tokens
2
+ description: Count codebase tokens with tiktoken and update a README badge
3
+
4
+ inputs:
5
+ include:
6
+ description: 'Glob patterns for files to count (space-separated)'
7
+ required: true
8
+ exclude:
9
+ description: 'Glob patterns to exclude (space-separated)'
10
+ required: false
11
+ default: ''
12
+ context-window:
13
+ description: 'Context window size for percentage calculation'
14
+ required: false
15
+ default: '200000'
16
+ readme:
17
+ description: 'Path to README file'
18
+ required: false
19
+ default: 'README.md'
20
+ encoding:
21
+ description: 'Tiktoken encoding name'
22
+ required: false
23
+ default: 'cl100k_base'
24
+ marker:
25
+ description: 'HTML comment marker name'
26
+ required: false
27
+ default: 'token-count'
28
+ badge-path:
29
+ description: 'Path to write SVG badge (empty = no SVG)'
30
+ required: false
31
+ default: ''
32
+
33
+ outputs:
34
+ tokens:
35
+ description: 'Total token count'
36
+ value: ${{ steps.count.outputs.tokens }}
37
+ percentage:
38
+ description: 'Percentage of context window'
39
+ value: ${{ steps.count.outputs.percentage }}
40
+ badge:
41
+ description: 'Badge text that was inserted'
42
+ value: ${{ steps.count.outputs.badge }}
43
+
44
+ runs:
45
+ using: composite
46
+ steps:
47
+ - name: Install tiktoken
48
+ shell: bash
49
+ run: pip install tiktoken
50
+
51
+ - name: Count tokens and update README
52
+ id: count
53
+ shell: python
54
+ env:
55
+ INPUT_INCLUDE: ${{ inputs.include }}
56
+ INPUT_EXCLUDE: ${{ inputs.exclude }}
57
+ INPUT_CONTEXT_WINDOW: ${{ inputs.context-window }}
58
+ INPUT_README: ${{ inputs.readme }}
59
+ INPUT_ENCODING: ${{ inputs.encoding }}
60
+ INPUT_MARKER: ${{ inputs.marker }}
61
+ INPUT_BADGE_PATH: ${{ inputs.badge-path }}
62
+ run: |
63
+ import glob, os, re, tiktoken
64
+
65
+ include_patterns = os.environ["INPUT_INCLUDE"].split()
66
+ exclude_patterns = os.environ["INPUT_EXCLUDE"].split()
67
+ context_window = int(os.environ["INPUT_CONTEXT_WINDOW"])
68
+ readme_path = os.environ["INPUT_README"]
69
+ encoding_name = os.environ["INPUT_ENCODING"]
70
+ marker = os.environ["INPUT_MARKER"]
71
+ badge_path = os.environ.get("INPUT_BADGE_PATH", "").strip()
72
+
73
+ # Expand globs
74
+ included = set()
75
+ for pattern in include_patterns:
76
+ included.update(glob.glob(pattern, recursive=True))
77
+
78
+ excluded = set()
79
+ for pattern in exclude_patterns:
80
+ excluded.update(glob.glob(pattern, recursive=True))
81
+
82
+ files = sorted(included - excluded)
83
+ files = [f for f in files if os.path.isfile(f)]
84
+
85
+ # Count tokens
86
+ enc = tiktoken.get_encoding(encoding_name)
87
+ total = 0
88
+ for path in files:
89
+ try:
90
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
91
+ total += len(enc.encode(f.read()))
92
+ except Exception as e:
93
+ print(f"Skipping {path}: {e}")
94
+
95
+ # Format
96
+ if total >= 100000:
97
+ display = f"{round(total / 1000)}k"
98
+ elif total >= 1000:
99
+ display = f"{total / 1000:.1f}k"
100
+ else:
101
+ display = str(total)
102
+
103
+ pct = round(total / context_window * 100)
104
+ badge = f"{display} tokens \u00b7 {pct}% of context window"
105
+
106
+ print(f"Files: {len(files)}, Tokens: {total}, Badge: {badge}")
107
+
108
+ # Update README (text between markers)
109
+ marker_re = re.compile(
110
+ rf"(<!--\s*{re.escape(marker)}\s*-->).*?(<!--\s*/{re.escape(marker)}\s*-->)",
111
+ re.DOTALL,
112
+ )
113
+
114
+ with open(readme_path, "r", encoding="utf-8") as f:
115
+ content = f.read()
116
+
117
+ repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
118
+ linked_badge = f'<a href="{repo_tokens_url}">{badge}</a>'
119
+ new_content = marker_re.sub(rf"\1{linked_badge}\2", content)
120
+
121
+ if new_content != content:
122
+ with open(readme_path, "w", encoding="utf-8") as f:
123
+ f.write(new_content)
124
+ print("README updated")
125
+ else:
126
+ print("No change to README")
127
+
128
+ # Generate SVG badge
129
+ if badge_path:
130
+ label_text = "tokens"
131
+ value_text = display
132
+ full_desc = f"{display} tokens, {pct}% of context window"
133
+
134
+ cw = 7.0
135
+ label_w = round(len(label_text) * cw) + 10
136
+ value_w = round(len(value_text) * cw) + 10
137
+ total_w = label_w + value_w
138
+
139
+ if pct < 30:
140
+ color = "#4c1"
141
+ elif pct < 50:
142
+ color = "#97ca00"
143
+ elif pct < 70:
144
+ color = "#dfb317"
145
+ else:
146
+ color = "#e05d44"
147
+
148
+ lx = label_w // 2
149
+ vx = label_w + value_w // 2
150
+
151
+ repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
152
+
153
+ svg = f'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{total_w}" height="20" role="img" aria-label="{full_desc}">
154
+ <title>{full_desc}</title>
155
+ <linearGradient id="s" x2="0" y2="100%">
156
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
157
+ <stop offset="1" stop-opacity=".1"/>
158
+ </linearGradient>
159
+ <clipPath id="r">
160
+ <rect width="{total_w}" height="20" rx="3" fill="#fff"/>
161
+ </clipPath>
162
+ <a xlink:href="{repo_tokens_url}">
163
+ <g clip-path="url(#r)">
164
+ <rect width="{label_w}" height="20" fill="#555"/>
165
+ <rect x="{label_w}" width="{value_w}" height="20" fill="{color}"/>
166
+ <rect width="{total_w}" height="20" fill="url(#s)"/>
167
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
168
+ <text aria-hidden="true" x="{lx}" y="15" fill="#010101" fill-opacity=".3">{label_text}</text>
169
+ <text x="{lx}" y="14">{label_text}</text>
170
+ <text aria-hidden="true" x="{vx}" y="15" fill="#010101" fill-opacity=".3">{value_text}</text>
171
+ <text x="{vx}" y="14">{value_text}</text>
172
+ </g>
173
+ </g>
174
+ </a>
175
+ </svg>'''
176
+
177
+ os.makedirs(os.path.dirname(badge_path) or ".", exist_ok=True)
178
+ with open(badge_path, "w", encoding="utf-8") as f:
179
+ f.write(svg)
180
+ print(f"Badge SVG written to {badge_path}")
181
+
182
+ # Set outputs
183
+ with open(os.environ["GITHUB_OUTPUT"], "a") as f:
184
+ f.write(f"tokens={total}\n")
185
+ f.write(f"percentage={pct}\n")
186
+ f.write(f"badge={badge}\n")