@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,338 @@
1
+ /**
2
+ * Stdio MCP Server for NanoClaw
3
+ * Standalone process that agent teams subagents can inherit.
4
+ * Reads context from environment variables, writes IPC files for the host.
5
+ */
6
+
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { z } from 'zod';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { CronExpressionParser } from 'cron-parser';
13
+
14
+ const IPC_DIR = '/workspace/ipc';
15
+ const MESSAGES_DIR = path.join(IPC_DIR, 'messages');
16
+ const TASKS_DIR = path.join(IPC_DIR, 'tasks');
17
+
18
+ // Context from environment variables (set by the agent runner)
19
+ const chatJid = process.env.NANOCLAW_CHAT_JID!;
20
+ const groupFolder = process.env.NANOCLAW_GROUP_FOLDER!;
21
+ const isMain = process.env.NANOCLAW_IS_MAIN === '1';
22
+
23
+ function writeIpcFile(dir: string, data: object): string {
24
+ fs.mkdirSync(dir, { recursive: true });
25
+
26
+ const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
27
+ const filepath = path.join(dir, filename);
28
+
29
+ // Atomic write: temp file then rename
30
+ const tempPath = `${filepath}.tmp`;
31
+ fs.writeFileSync(tempPath, JSON.stringify(data, null, 2));
32
+ fs.renameSync(tempPath, filepath);
33
+
34
+ return filename;
35
+ }
36
+
37
+ const server = new McpServer({
38
+ name: 'nanoclaw',
39
+ version: '1.0.0',
40
+ });
41
+
42
+ server.tool(
43
+ 'send_message',
44
+ "Send a message to the user or group immediately while you're still running. Use this for progress updates or to send multiple messages. You can call this multiple times.",
45
+ {
46
+ text: z.string().describe('The message text to send'),
47
+ sender: z.string().optional().describe('Your role/identity name (e.g. "Researcher"). When set, messages appear from a dedicated bot in Telegram.'),
48
+ },
49
+ async (args) => {
50
+ const data: Record<string, string | undefined> = {
51
+ type: 'message',
52
+ chatJid,
53
+ text: args.text,
54
+ sender: args.sender || undefined,
55
+ groupFolder,
56
+ timestamp: new Date().toISOString(),
57
+ };
58
+
59
+ writeIpcFile(MESSAGES_DIR, data);
60
+
61
+ return { content: [{ type: 'text' as const, text: 'Message sent.' }] };
62
+ },
63
+ );
64
+
65
+ server.tool(
66
+ 'schedule_task',
67
+ `Schedule a recurring or one-time task. The task will run as a full agent with access to all tools. Returns the task ID for future reference. To modify an existing task, use update_task instead.
68
+
69
+ CONTEXT MODE - Choose based on task type:
70
+ \u2022 "group": Task runs in the group's conversation context, with access to chat history. Use for tasks that need context about ongoing discussions, user preferences, or recent interactions.
71
+ \u2022 "isolated": Task runs in a fresh session with no conversation history. Use for independent tasks that don't need prior context. When using isolated mode, include all necessary context in the prompt itself.
72
+
73
+ If unsure which mode to use, you can ask the user. Examples:
74
+ - "Remind me about our discussion" \u2192 group (needs conversation context)
75
+ - "Check the weather every morning" \u2192 isolated (self-contained task)
76
+ - "Follow up on my request" \u2192 group (needs to know what was requested)
77
+ - "Generate a daily report" \u2192 isolated (just needs instructions in prompt)
78
+
79
+ MESSAGING BEHAVIOR - The task agent's output is sent to the user or group. It can also use send_message for immediate delivery, or wrap output in <internal> tags to suppress it. Include guidance in the prompt about whether the agent should:
80
+ \u2022 Always send a message (e.g., reminders, daily briefings)
81
+ \u2022 Only send a message when there's something to report (e.g., "notify me if...")
82
+ \u2022 Never send a message (background maintenance tasks)
83
+
84
+ SCHEDULE VALUE FORMAT (all times are LOCAL timezone):
85
+ \u2022 cron: Standard cron expression (e.g., "*/5 * * * *" for every 5 minutes, "0 9 * * *" for daily at 9am LOCAL time)
86
+ \u2022 interval: Milliseconds between runs (e.g., "300000" for 5 minutes, "3600000" for 1 hour)
87
+ \u2022 once: Local time WITHOUT "Z" suffix (e.g., "2026-02-01T15:30:00"). Do NOT use UTC/Z suffix.`,
88
+ {
89
+ prompt: z.string().describe('What the agent should do when the task runs. For isolated mode, include all necessary context here.'),
90
+ schedule_type: z.enum(['cron', 'interval', 'once']).describe('cron=recurring at specific times, interval=recurring every N ms, once=run once at specific time'),
91
+ schedule_value: z.string().describe('cron: "*/5 * * * *" | interval: milliseconds like "300000" | once: local timestamp like "2026-02-01T15:30:00" (no Z suffix!)'),
92
+ context_mode: z.enum(['group', 'isolated']).default('group').describe('group=runs with chat history and memory, isolated=fresh session (include context in prompt)'),
93
+ target_group_jid: z.string().optional().describe('(Main group only) JID of the group to schedule the task for. Defaults to the current group.'),
94
+ },
95
+ async (args) => {
96
+ // Validate schedule_value before writing IPC
97
+ if (args.schedule_type === 'cron') {
98
+ try {
99
+ CronExpressionParser.parse(args.schedule_value);
100
+ } catch {
101
+ return {
102
+ content: [{ type: 'text' as const, text: `Invalid cron: "${args.schedule_value}". Use format like "0 9 * * *" (daily 9am) or "*/5 * * * *" (every 5 min).` }],
103
+ isError: true,
104
+ };
105
+ }
106
+ } else if (args.schedule_type === 'interval') {
107
+ const ms = parseInt(args.schedule_value, 10);
108
+ if (isNaN(ms) || ms <= 0) {
109
+ return {
110
+ content: [{ type: 'text' as const, text: `Invalid interval: "${args.schedule_value}". Must be positive milliseconds (e.g., "300000" for 5 min).` }],
111
+ isError: true,
112
+ };
113
+ }
114
+ } else if (args.schedule_type === 'once') {
115
+ if (/[Zz]$/.test(args.schedule_value) || /[+-]\d{2}:\d{2}$/.test(args.schedule_value)) {
116
+ return {
117
+ content: [{ type: 'text' as const, text: `Timestamp must be local time without timezone suffix. Got "${args.schedule_value}" — use format like "2026-02-01T15:30:00".` }],
118
+ isError: true,
119
+ };
120
+ }
121
+ const date = new Date(args.schedule_value);
122
+ if (isNaN(date.getTime())) {
123
+ return {
124
+ content: [{ type: 'text' as const, text: `Invalid timestamp: "${args.schedule_value}". Use local time format like "2026-02-01T15:30:00".` }],
125
+ isError: true,
126
+ };
127
+ }
128
+ }
129
+
130
+ // Non-main groups can only schedule for themselves
131
+ const targetJid = isMain && args.target_group_jid ? args.target_group_jid : chatJid;
132
+
133
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
134
+
135
+ const data = {
136
+ type: 'schedule_task',
137
+ taskId,
138
+ prompt: args.prompt,
139
+ schedule_type: args.schedule_type,
140
+ schedule_value: args.schedule_value,
141
+ context_mode: args.context_mode || 'group',
142
+ targetJid,
143
+ createdBy: groupFolder,
144
+ timestamp: new Date().toISOString(),
145
+ };
146
+
147
+ writeIpcFile(TASKS_DIR, data);
148
+
149
+ return {
150
+ content: [{ type: 'text' as const, text: `Task ${taskId} scheduled: ${args.schedule_type} - ${args.schedule_value}` }],
151
+ };
152
+ },
153
+ );
154
+
155
+ server.tool(
156
+ 'list_tasks',
157
+ "List all scheduled tasks. From main: shows all tasks. From other groups: shows only that group's tasks.",
158
+ {},
159
+ async () => {
160
+ const tasksFile = path.join(IPC_DIR, 'current_tasks.json');
161
+
162
+ try {
163
+ if (!fs.existsSync(tasksFile)) {
164
+ return { content: [{ type: 'text' as const, text: 'No scheduled tasks found.' }] };
165
+ }
166
+
167
+ const allTasks = JSON.parse(fs.readFileSync(tasksFile, 'utf-8'));
168
+
169
+ const tasks = isMain
170
+ ? allTasks
171
+ : allTasks.filter((t: { groupFolder: string }) => t.groupFolder === groupFolder);
172
+
173
+ if (tasks.length === 0) {
174
+ return { content: [{ type: 'text' as const, text: 'No scheduled tasks found.' }] };
175
+ }
176
+
177
+ const formatted = tasks
178
+ .map(
179
+ (t: { id: string; prompt: string; schedule_type: string; schedule_value: string; status: string; next_run: string }) =>
180
+ `- [${t.id}] ${t.prompt.slice(0, 50)}... (${t.schedule_type}: ${t.schedule_value}) - ${t.status}, next: ${t.next_run || 'N/A'}`,
181
+ )
182
+ .join('\n');
183
+
184
+ return { content: [{ type: 'text' as const, text: `Scheduled tasks:\n${formatted}` }] };
185
+ } catch (err) {
186
+ return {
187
+ content: [{ type: 'text' as const, text: `Error reading tasks: ${err instanceof Error ? err.message : String(err)}` }],
188
+ };
189
+ }
190
+ },
191
+ );
192
+
193
+ server.tool(
194
+ 'pause_task',
195
+ 'Pause a scheduled task. It will not run until resumed.',
196
+ { task_id: z.string().describe('The task ID to pause') },
197
+ async (args) => {
198
+ const data = {
199
+ type: 'pause_task',
200
+ taskId: args.task_id,
201
+ groupFolder,
202
+ isMain,
203
+ timestamp: new Date().toISOString(),
204
+ };
205
+
206
+ writeIpcFile(TASKS_DIR, data);
207
+
208
+ return { content: [{ type: 'text' as const, text: `Task ${args.task_id} pause requested.` }] };
209
+ },
210
+ );
211
+
212
+ server.tool(
213
+ 'resume_task',
214
+ 'Resume a paused task.',
215
+ { task_id: z.string().describe('The task ID to resume') },
216
+ async (args) => {
217
+ const data = {
218
+ type: 'resume_task',
219
+ taskId: args.task_id,
220
+ groupFolder,
221
+ isMain,
222
+ timestamp: new Date().toISOString(),
223
+ };
224
+
225
+ writeIpcFile(TASKS_DIR, data);
226
+
227
+ return { content: [{ type: 'text' as const, text: `Task ${args.task_id} resume requested.` }] };
228
+ },
229
+ );
230
+
231
+ server.tool(
232
+ 'cancel_task',
233
+ 'Cancel and delete a scheduled task.',
234
+ { task_id: z.string().describe('The task ID to cancel') },
235
+ async (args) => {
236
+ const data = {
237
+ type: 'cancel_task',
238
+ taskId: args.task_id,
239
+ groupFolder,
240
+ isMain,
241
+ timestamp: new Date().toISOString(),
242
+ };
243
+
244
+ writeIpcFile(TASKS_DIR, data);
245
+
246
+ return { content: [{ type: 'text' as const, text: `Task ${args.task_id} cancellation requested.` }] };
247
+ },
248
+ );
249
+
250
+ server.tool(
251
+ 'update_task',
252
+ 'Update an existing scheduled task. Only provided fields are changed; omitted fields stay the same.',
253
+ {
254
+ task_id: z.string().describe('The task ID to update'),
255
+ prompt: z.string().optional().describe('New prompt for the task'),
256
+ schedule_type: z.enum(['cron', 'interval', 'once']).optional().describe('New schedule type'),
257
+ schedule_value: z.string().optional().describe('New schedule value (see schedule_task for format)'),
258
+ },
259
+ async (args) => {
260
+ // Validate schedule_value if provided
261
+ if (args.schedule_type === 'cron' || (!args.schedule_type && args.schedule_value)) {
262
+ if (args.schedule_value) {
263
+ try {
264
+ CronExpressionParser.parse(args.schedule_value);
265
+ } catch {
266
+ return {
267
+ content: [{ type: 'text' as const, text: `Invalid cron: "${args.schedule_value}".` }],
268
+ isError: true,
269
+ };
270
+ }
271
+ }
272
+ }
273
+ if (args.schedule_type === 'interval' && args.schedule_value) {
274
+ const ms = parseInt(args.schedule_value, 10);
275
+ if (isNaN(ms) || ms <= 0) {
276
+ return {
277
+ content: [{ type: 'text' as const, text: `Invalid interval: "${args.schedule_value}".` }],
278
+ isError: true,
279
+ };
280
+ }
281
+ }
282
+
283
+ const data: Record<string, string | undefined> = {
284
+ type: 'update_task',
285
+ taskId: args.task_id,
286
+ groupFolder,
287
+ isMain: String(isMain),
288
+ timestamp: new Date().toISOString(),
289
+ };
290
+ if (args.prompt !== undefined) data.prompt = args.prompt;
291
+ if (args.schedule_type !== undefined) data.schedule_type = args.schedule_type;
292
+ if (args.schedule_value !== undefined) data.schedule_value = args.schedule_value;
293
+
294
+ writeIpcFile(TASKS_DIR, data);
295
+
296
+ return { content: [{ type: 'text' as const, text: `Task ${args.task_id} update requested.` }] };
297
+ },
298
+ );
299
+
300
+ server.tool(
301
+ 'register_group',
302
+ `Register a new chat/group so the agent can respond to messages there. Main group only.
303
+
304
+ Use available_groups.json to find the JID for a group. The folder name must be channel-prefixed: "{channel}_{group-name}" (e.g., "whatsapp_family-chat", "telegram_dev-team", "discord_general"). Use lowercase with hyphens for the group name part.`,
305
+ {
306
+ jid: z.string().describe('The chat JID (e.g., "120363336345536173@g.us", "tg:-1001234567890", "dc:1234567890123456")'),
307
+ name: z.string().describe('Display name for the group'),
308
+ folder: z.string().describe('Channel-prefixed folder name (e.g., "whatsapp_family-chat", "telegram_dev-team")'),
309
+ trigger: z.string().describe('Trigger word (e.g., "@Andy")'),
310
+ },
311
+ async (args) => {
312
+ if (!isMain) {
313
+ return {
314
+ content: [{ type: 'text' as const, text: 'Only the main group can register new groups.' }],
315
+ isError: true,
316
+ };
317
+ }
318
+
319
+ const data = {
320
+ type: 'register_group',
321
+ jid: args.jid,
322
+ name: args.name,
323
+ folder: args.folder,
324
+ trigger: args.trigger,
325
+ timestamp: new Date().toISOString(),
326
+ };
327
+
328
+ writeIpcFile(TASKS_DIR, data);
329
+
330
+ return {
331
+ content: [{ type: 'text' as const, text: `Group "${args.name}" registered. It will start receiving messages immediately.` }],
332
+ };
333
+ },
334
+ );
335
+
336
+ // Start the stdio transport
337
+ const transport = new StdioServerTransport();
338
+ await server.connect(transport);
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ # Build the NanoClaw agent container image
3
+
4
+ set -e
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ cd "$SCRIPT_DIR"
8
+
9
+ IMAGE_NAME="nanoclaw-agent"
10
+ TAG="${1:-latest}"
11
+ CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
12
+
13
+ echo "Building NanoClaw agent container image..."
14
+ echo "Image: ${IMAGE_NAME}:${TAG}"
15
+
16
+ ${CONTAINER_RUNTIME} build -t "${IMAGE_NAME}:${TAG}" .
17
+
18
+ echo ""
19
+ echo "Build complete!"
20
+ echo "Image: ${IMAGE_NAME}:${TAG}"
21
+ echo ""
22
+ echo "Test with:"
23
+ echo " echo '{\"prompt\":\"What is 2+2?\",\"groupFolder\":\"test\",\"chatJid\":\"test@g.us\",\"isMain\":false}' | ${CONTAINER_RUNTIME} run -i ${IMAGE_NAME}:${TAG}"
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: agent-browser
3
+ description: Browse the web for any task — research topics, read articles, interact with web apps, fill forms, take screenshots, extract data, and test web pages. Use whenever a browser would be useful, not just when the user explicitly asks.
4
+ allowed-tools: Bash(agent-browser:*)
5
+ ---
6
+
7
+ # Browser Automation with agent-browser
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ agent-browser open <url> # Navigate to page
13
+ agent-browser snapshot -i # Get interactive elements with refs
14
+ agent-browser click @e1 # Click element by ref
15
+ agent-browser fill @e2 "text" # Fill input by ref
16
+ agent-browser close # Close browser
17
+ ```
18
+
19
+ ## Core workflow
20
+
21
+ 1. Navigate: `agent-browser open <url>`
22
+ 2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)
23
+ 3. Interact using refs from the snapshot
24
+ 4. Re-snapshot after navigation or significant DOM changes
25
+
26
+ ## Commands
27
+
28
+ ### Navigation
29
+
30
+ ```bash
31
+ agent-browser open <url> # Navigate to URL
32
+ agent-browser back # Go back
33
+ agent-browser forward # Go forward
34
+ agent-browser reload # Reload page
35
+ agent-browser close # Close browser
36
+ ```
37
+
38
+ ### Snapshot (page analysis)
39
+
40
+ ```bash
41
+ agent-browser snapshot # Full accessibility tree
42
+ agent-browser snapshot -i # Interactive elements only (recommended)
43
+ agent-browser snapshot -c # Compact output
44
+ agent-browser snapshot -d 3 # Limit depth to 3
45
+ agent-browser snapshot -s "#main" # Scope to CSS selector
46
+ ```
47
+
48
+ ### Interactions (use @refs from snapshot)
49
+
50
+ ```bash
51
+ agent-browser click @e1 # Click
52
+ agent-browser dblclick @e1 # Double-click
53
+ agent-browser fill @e2 "text" # Clear and type
54
+ agent-browser type @e2 "text" # Type without clearing
55
+ agent-browser press Enter # Press key
56
+ agent-browser hover @e1 # Hover
57
+ agent-browser check @e1 # Check checkbox
58
+ agent-browser uncheck @e1 # Uncheck checkbox
59
+ agent-browser select @e1 "value" # Select dropdown option
60
+ agent-browser scroll down 500 # Scroll page
61
+ agent-browser upload @e1 file.pdf # Upload files
62
+ ```
63
+
64
+ ### Get information
65
+
66
+ ```bash
67
+ agent-browser get text @e1 # Get element text
68
+ agent-browser get html @e1 # Get innerHTML
69
+ agent-browser get value @e1 # Get input value
70
+ agent-browser get attr @e1 href # Get attribute
71
+ agent-browser get title # Get page title
72
+ agent-browser get url # Get current URL
73
+ agent-browser get count ".item" # Count matching elements
74
+ ```
75
+
76
+ ### Screenshots & PDF
77
+
78
+ ```bash
79
+ agent-browser screenshot # Save to temp directory
80
+ agent-browser screenshot path.png # Save to specific path
81
+ agent-browser screenshot --full # Full page
82
+ agent-browser pdf output.pdf # Save as PDF
83
+ ```
84
+
85
+ ### Wait
86
+
87
+ ```bash
88
+ agent-browser wait @e1 # Wait for element
89
+ agent-browser wait 2000 # Wait milliseconds
90
+ agent-browser wait --text "Success" # Wait for text
91
+ agent-browser wait --url "**/dashboard" # Wait for URL pattern
92
+ agent-browser wait --load networkidle # Wait for network idle
93
+ ```
94
+
95
+ ### Semantic locators (alternative to refs)
96
+
97
+ ```bash
98
+ agent-browser find role button click --name "Submit"
99
+ agent-browser find text "Sign In" click
100
+ agent-browser find label "Email" fill "user@test.com"
101
+ agent-browser find placeholder "Search" type "query"
102
+ ```
103
+
104
+ ### Authentication with saved state
105
+
106
+ ```bash
107
+ # Login once
108
+ agent-browser open https://app.example.com/login
109
+ agent-browser snapshot -i
110
+ agent-browser fill @e1 "username"
111
+ agent-browser fill @e2 "password"
112
+ agent-browser click @e3
113
+ agent-browser wait --url "**/dashboard"
114
+ agent-browser state save auth.json
115
+
116
+ # Later: load saved state
117
+ agent-browser state load auth.json
118
+ agent-browser open https://app.example.com/dashboard
119
+ ```
120
+
121
+ ### Cookies & Storage
122
+
123
+ ```bash
124
+ agent-browser cookies # Get all cookies
125
+ agent-browser cookies set name value # Set cookie
126
+ agent-browser cookies clear # Clear cookies
127
+ agent-browser storage local # Get localStorage
128
+ agent-browser storage local set k v # Set value
129
+ ```
130
+
131
+ ### JavaScript
132
+
133
+ ```bash
134
+ agent-browser eval "document.title" # Run JavaScript
135
+ ```
136
+
137
+ ## Example: Form submission
138
+
139
+ ```bash
140
+ agent-browser open https://example.com/form
141
+ agent-browser snapshot -i
142
+ # Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3]
143
+
144
+ agent-browser fill @e1 "user@example.com"
145
+ agent-browser fill @e2 "password123"
146
+ agent-browser click @e3
147
+ agent-browser wait --load networkidle
148
+ agent-browser snapshot -i # Check result
149
+ ```
150
+
151
+ ## Example: Data extraction
152
+
153
+ ```bash
154
+ agent-browser open https://example.com/products
155
+ agent-browser snapshot -i
156
+ agent-browser get text @e1 # Get product title
157
+ agent-browser get attr @e2 href # Get link URL
158
+ agent-browser screenshot products.png
159
+ ```
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: capabilities
3
+ description: Show what this NanoClaw instance can do — installed skills, available tools, and system info. Read-only. Use when the user asks what the bot can do, what's installed, or runs /capabilities.
4
+ ---
5
+
6
+ # /capabilities — System Capabilities Report
7
+
8
+ Generate a structured read-only report of what this NanoClaw instance can do.
9
+
10
+ **Main-channel check:** Only the main channel has `/workspace/project` mounted. Run:
11
+
12
+ ```bash
13
+ test -d /workspace/project && echo "MAIN" || echo "NOT_MAIN"
14
+ ```
15
+
16
+ If `NOT_MAIN`, respond with:
17
+ > This command is available in your main chat only. Send `/capabilities` there to see what I can do.
18
+
19
+ Then stop — do not generate the report.
20
+
21
+ ## How to gather the information
22
+
23
+ Run these commands and compile the results into the report format below.
24
+
25
+ ### 1. Installed skills
26
+
27
+ List skill directories available to you:
28
+
29
+ ```bash
30
+ ls -1 /home/node/.claude/skills/ 2>/dev/null || echo "No skills found"
31
+ ```
32
+
33
+ Each directory is an installed skill. The directory name is the skill name (e.g., `agent-browser` → `/agent-browser`).
34
+
35
+ ### 2. Available tools
36
+
37
+ Read the allowed tools from your SDK configuration. You always have access to:
38
+ - **Core:** Bash, Read, Write, Edit, Glob, Grep
39
+ - **Web:** WebSearch, WebFetch
40
+ - **Orchestration:** Task, TaskOutput, TaskStop, TeamCreate, TeamDelete, SendMessage
41
+ - **Other:** TodoWrite, ToolSearch, Skill, NotebookEdit
42
+ - **MCP:** mcp__nanoclaw__* (messaging, tasks, group management)
43
+
44
+ ### 3. MCP server tools
45
+
46
+ The NanoClaw MCP server exposes these tools (via `mcp__nanoclaw__*` prefix):
47
+ - `send_message` — send a message to the user/group
48
+ - `schedule_task` — schedule a recurring or one-time task
49
+ - `list_tasks` — list scheduled tasks
50
+ - `pause_task` — pause a scheduled task
51
+ - `resume_task` — resume a paused task
52
+ - `cancel_task` — cancel and delete a task
53
+ - `update_task` — update an existing task
54
+ - `register_group` — register a new chat/group (main only)
55
+
56
+ ### 4. Container skills (Bash tools)
57
+
58
+ Check for executable tools in the container:
59
+
60
+ ```bash
61
+ which agent-browser 2>/dev/null && echo "agent-browser: available" || echo "agent-browser: not found"
62
+ ```
63
+
64
+ ### 5. Group info
65
+
66
+ ```bash
67
+ ls /workspace/group/CLAUDE.md 2>/dev/null && echo "Group memory: yes" || echo "Group memory: no"
68
+ ls /workspace/extra/ 2>/dev/null && echo "Extra mounts: $(ls /workspace/extra/ 2>/dev/null | wc -l | tr -d ' ')" || echo "Extra mounts: none"
69
+ ```
70
+
71
+ ## Report format
72
+
73
+ Present the report as a clean, readable message. Example:
74
+
75
+ ```
76
+ 📋 *NanoClaw Capabilities*
77
+
78
+ *Installed Skills:*
79
+ • /agent-browser — Browse the web, fill forms, extract data
80
+ • /capabilities — This report
81
+ (list all found skills)
82
+
83
+ *Tools:*
84
+ • Core: Bash, Read, Write, Edit, Glob, Grep
85
+ • Web: WebSearch, WebFetch
86
+ • Orchestration: Task, TeamCreate, SendMessage
87
+ • MCP: send_message, schedule_task, list_tasks, pause/resume/cancel/update_task, register_group
88
+
89
+ *Container Tools:*
90
+ • agent-browser: ✓
91
+
92
+ *System:*
93
+ • Group memory: yes/no
94
+ • Extra mounts: N directories
95
+ • Main channel: yes
96
+ ```
97
+
98
+ Adapt the output based on what you actually find — don't list things that aren't installed.
99
+
100
+ **See also:** `/status` for a quick health check of session, workspace, and tasks.