create-ironclaws 1.0.0

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 (80) hide show
  1. package/README.md +101 -0
  2. package/bin/create.js +394 -0
  3. package/package.json +33 -0
  4. package/template/.env.example +38 -0
  5. package/template/CLAUDE.md +104 -0
  6. package/template/agent-credentials.yaml +33 -0
  7. package/template/agents.yaml +22 -0
  8. package/template/container/Dockerfile +70 -0
  9. package/template/container/Dockerfile.argus +34 -0
  10. package/template/container/agent-runner/package-lock.json +1524 -0
  11. package/template/container/agent-runner/package.json +23 -0
  12. package/template/container/agent-runner/src/index.ts +630 -0
  13. package/template/container/agent-runner/src/ipc-mcp-stdio.ts +339 -0
  14. package/template/container/agent-runner/tsconfig.json +15 -0
  15. package/template/container/build-argus.sh +25 -0
  16. package/template/container/build.sh +23 -0
  17. package/template/container/skills/agent-browser/SKILL.md +159 -0
  18. package/template/container/skills/agent-status/SKILL.md +69 -0
  19. package/template/container/skills/capabilities/SKILL.md +100 -0
  20. package/template/container/skills/edit-agent/SKILL.md +93 -0
  21. package/template/container/skills/slack-formatting/SKILL.md +92 -0
  22. package/template/container/skills/status/SKILL.md +104 -0
  23. package/template/container/tools/elastic_query.py +161 -0
  24. package/template/container/tools/gdrive_tool.py +185 -0
  25. package/template/container/tools/jira_tool.py +433 -0
  26. package/template/container/tools/slack_history_tool.py +144 -0
  27. package/template/container/tools/youtube_tool.py +174 -0
  28. package/template/docker-compose.yml +54 -0
  29. package/template/docs/how-it-works.md +496 -0
  30. package/template/eslint.config.js +32 -0
  31. package/template/groups/forge/CLAUDE.md +107 -0
  32. package/template/package-lock.json +5278 -0
  33. package/template/package.json +52 -0
  34. package/template/scripts/github-app-token.py +58 -0
  35. package/template/scripts/register-expense-agent.sh +121 -0
  36. package/template/scripts/run-migrations.ts +105 -0
  37. package/template/scripts/setup-onecli-secrets.sh +252 -0
  38. package/template/setup-agents.sh +142 -0
  39. package/template/src/channels/index.ts +13 -0
  40. package/template/src/channels/registry.test.ts +42 -0
  41. package/template/src/channels/registry.ts +28 -0
  42. package/template/src/channels/slack.test.ts +859 -0
  43. package/template/src/channels/slack.ts +373 -0
  44. package/template/src/claw-skill.test.ts +45 -0
  45. package/template/src/config.ts +94 -0
  46. package/template/src/container-runner.test.ts +221 -0
  47. package/template/src/container-runner.ts +1029 -0
  48. package/template/src/container-runtime.test.ts +149 -0
  49. package/template/src/container-runtime.ts +124 -0
  50. package/template/src/db-migration.test.ts +67 -0
  51. package/template/src/db.test.ts +484 -0
  52. package/template/src/db.ts +837 -0
  53. package/template/src/env.ts +42 -0
  54. package/template/src/formatting.test.ts +294 -0
  55. package/template/src/github-token.ts +48 -0
  56. package/template/src/google-token.ts +75 -0
  57. package/template/src/group-folder.test.ts +43 -0
  58. package/template/src/group-folder.ts +44 -0
  59. package/template/src/group-queue.test.ts +484 -0
  60. package/template/src/group-queue.ts +363 -0
  61. package/template/src/http-server.ts +343 -0
  62. package/template/src/index.ts +960 -0
  63. package/template/src/ipc-auth.test.ts +679 -0
  64. package/template/src/ipc.ts +548 -0
  65. package/template/src/logger.ts +16 -0
  66. package/template/src/mount-security.ts +421 -0
  67. package/template/src/network-policy.ts +119 -0
  68. package/template/src/remote-control.test.ts +397 -0
  69. package/template/src/remote-control.ts +224 -0
  70. package/template/src/router.ts +52 -0
  71. package/template/src/routing.test.ts +170 -0
  72. package/template/src/sender-allowlist.test.ts +216 -0
  73. package/template/src/sender-allowlist.ts +128 -0
  74. package/template/src/task-scheduler.test.ts +129 -0
  75. package/template/src/task-scheduler.ts +290 -0
  76. package/template/src/timezone.test.ts +73 -0
  77. package/template/src/timezone.ts +37 -0
  78. package/template/src/types.ts +114 -0
  79. package/template/src/worktree.ts +206 -0
  80. package/template/tsconfig.json +20 -0
@@ -0,0 +1,339 @@
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
+ /** Wrap writeIpcFile and return an MCP content array — success or error. */
38
+ function ipcResult(
39
+ dir: string,
40
+ data: object,
41
+ successText: string,
42
+ ): { content: Array<{ type: 'text'; text: string }>; isError?: true } {
43
+ try {
44
+ writeIpcFile(dir, data);
45
+ return { content: [{ type: 'text' as const, text: successText }] };
46
+ } catch (err) {
47
+ const msg = err instanceof Error ? err.message : String(err);
48
+ return { content: [{ type: 'text' as const, text: `IPC write failed: ${msg}` }], isError: true };
49
+ }
50
+ }
51
+
52
+ const server = new McpServer({
53
+ name: 'nanoclaw',
54
+ version: '1.0.0',
55
+ });
56
+
57
+ server.tool(
58
+ 'send_message',
59
+ "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.",
60
+ {
61
+ text: z.string().describe('The message text to send'),
62
+ sender: z.string().optional().describe('Your role/identity name (e.g. "Researcher"). When set, messages appear from a dedicated bot in Telegram.'),
63
+ },
64
+ async (args) => {
65
+ const data: Record<string, string | undefined> = {
66
+ type: 'message',
67
+ chatJid,
68
+ text: args.text,
69
+ sender: args.sender || undefined,
70
+ groupFolder,
71
+ timestamp: new Date().toISOString(),
72
+ };
73
+
74
+ return ipcResult(MESSAGES_DIR, data, 'Message sent.');
75
+ },
76
+ );
77
+
78
+ server.tool(
79
+ 'schedule_task',
80
+ `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.
81
+
82
+ CONTEXT MODE - Choose based on task type:
83
+ \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.
84
+ \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.
85
+
86
+ If unsure which mode to use, you can ask the user. Examples:
87
+ - "Remind me about our discussion" \u2192 group (needs conversation context)
88
+ - "Check the weather every morning" \u2192 isolated (self-contained task)
89
+ - "Follow up on my request" \u2192 group (needs to know what was requested)
90
+ - "Generate a daily report" \u2192 isolated (just needs instructions in prompt)
91
+
92
+ 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:
93
+ \u2022 Always send a message (e.g., reminders, daily briefings)
94
+ \u2022 Only send a message when there's something to report (e.g., "notify me if...")
95
+ \u2022 Never send a message (background maintenance tasks)
96
+
97
+ SCHEDULE VALUE FORMAT (all times are LOCAL timezone):
98
+ \u2022 cron: Standard cron expression (e.g., "*/5 * * * *" for every 5 minutes, "0 9 * * *" for daily at 9am LOCAL time)
99
+ \u2022 interval: Milliseconds between runs (e.g., "300000" for 5 minutes, "3600000" for 1 hour)
100
+ \u2022 once: Local time WITHOUT "Z" suffix (e.g., "2026-02-01T15:30:00"). Do NOT use UTC/Z suffix.`,
101
+ {
102
+ prompt: z.string().describe('What the agent should do when the task runs. For isolated mode, include all necessary context here.'),
103
+ schedule_type: z.enum(['cron', 'interval', 'once']).describe('cron=recurring at specific times, interval=recurring every N ms, once=run once at specific time'),
104
+ schedule_value: z.string().describe('cron: "*/5 * * * *" | interval: milliseconds like "300000" | once: local timestamp like "2026-02-01T15:30:00" (no Z suffix!)'),
105
+ context_mode: z.enum(['group', 'isolated']).default('group').describe('group=runs with chat history and memory, isolated=fresh session (include context in prompt)'),
106
+ target_group_jid: z.string().optional().describe('(Main group only) JID of the group to schedule the task for. Defaults to the current group.'),
107
+ script: z.string().optional().describe('Optional bash script to run before waking the agent. Script must output JSON on the last line of stdout: { "wakeAgent": boolean, "data"?: any }. If wakeAgent is false, the agent is not called. Test your script with bash -c "..." before scheduling.'),
108
+ },
109
+ async (args) => {
110
+ // Validate schedule_value before writing IPC
111
+ if (args.schedule_type === 'cron') {
112
+ try {
113
+ CronExpressionParser.parse(args.schedule_value);
114
+ } catch {
115
+ return {
116
+ content: [{ type: 'text' as const, text: `Invalid cron: "${args.schedule_value}". Use format like "0 9 * * *" (daily 9am) or "*/5 * * * *" (every 5 min).` }],
117
+ isError: true,
118
+ };
119
+ }
120
+ } else if (args.schedule_type === 'interval') {
121
+ const ms = parseInt(args.schedule_value, 10);
122
+ if (isNaN(ms) || ms <= 0) {
123
+ return {
124
+ content: [{ type: 'text' as const, text: `Invalid interval: "${args.schedule_value}". Must be positive milliseconds (e.g., "300000" for 5 min).` }],
125
+ isError: true,
126
+ };
127
+ }
128
+ } else if (args.schedule_type === 'once') {
129
+ if (/[Zz]$/.test(args.schedule_value) || /[+-]\d{2}:\d{2}$/.test(args.schedule_value)) {
130
+ return {
131
+ 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".` }],
132
+ isError: true,
133
+ };
134
+ }
135
+ const date = new Date(args.schedule_value);
136
+ if (isNaN(date.getTime())) {
137
+ return {
138
+ content: [{ type: 'text' as const, text: `Invalid timestamp: "${args.schedule_value}". Use local time format like "2026-02-01T15:30:00".` }],
139
+ isError: true,
140
+ };
141
+ }
142
+ }
143
+
144
+ // Non-main groups can only schedule for themselves
145
+ const targetJid = isMain && args.target_group_jid ? args.target_group_jid : chatJid;
146
+
147
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
148
+
149
+ const data = {
150
+ type: 'schedule_task',
151
+ taskId,
152
+ prompt: args.prompt,
153
+ script: args.script || undefined,
154
+ schedule_type: args.schedule_type,
155
+ schedule_value: args.schedule_value,
156
+ context_mode: args.context_mode || 'group',
157
+ targetJid,
158
+ createdBy: groupFolder,
159
+ timestamp: new Date().toISOString(),
160
+ };
161
+
162
+ return ipcResult(TASKS_DIR, data, `Task ${taskId} scheduled: ${args.schedule_type} - ${args.schedule_value}`);
163
+ },
164
+ );
165
+
166
+ server.tool(
167
+ 'list_tasks',
168
+ "List all scheduled tasks. From main: shows all tasks. From other groups: shows only that group's tasks.",
169
+ {},
170
+ async () => {
171
+ const tasksFile = path.join(IPC_DIR, 'current_tasks.json');
172
+
173
+ try {
174
+ if (!fs.existsSync(tasksFile)) {
175
+ return { content: [{ type: 'text' as const, text: 'No scheduled tasks found.' }] };
176
+ }
177
+
178
+ const allTasks = JSON.parse(fs.readFileSync(tasksFile, 'utf-8'));
179
+
180
+ const tasks = isMain
181
+ ? allTasks
182
+ : allTasks.filter((t: { groupFolder: string }) => t.groupFolder === groupFolder);
183
+
184
+ if (tasks.length === 0) {
185
+ return { content: [{ type: 'text' as const, text: 'No scheduled tasks found.' }] };
186
+ }
187
+
188
+ const formatted = tasks
189
+ .map(
190
+ (t: { id: string; prompt: string; schedule_type: string; schedule_value: string; status: string; next_run: string }) =>
191
+ `- [${t.id}] ${t.prompt.slice(0, 50)}... (${t.schedule_type}: ${t.schedule_value}) - ${t.status}, next: ${t.next_run || 'N/A'}`,
192
+ )
193
+ .join('\n');
194
+
195
+ return { content: [{ type: 'text' as const, text: `Scheduled tasks:\n${formatted}` }] };
196
+ } catch (err) {
197
+ return {
198
+ content: [{ type: 'text' as const, text: `Error reading tasks: ${err instanceof Error ? err.message : String(err)}` }],
199
+ };
200
+ }
201
+ },
202
+ );
203
+
204
+ server.tool(
205
+ 'pause_task',
206
+ 'Pause a scheduled task. It will not run until resumed.',
207
+ { task_id: z.string().describe('The task ID to pause') },
208
+ async (args) => {
209
+ const data = {
210
+ type: 'pause_task',
211
+ taskId: args.task_id,
212
+ groupFolder,
213
+ isMain,
214
+ timestamp: new Date().toISOString(),
215
+ };
216
+
217
+ return ipcResult(TASKS_DIR, data, `Task ${args.task_id} pause requested.`);
218
+ },
219
+ );
220
+
221
+ server.tool(
222
+ 'resume_task',
223
+ 'Resume a paused task.',
224
+ { task_id: z.string().describe('The task ID to resume') },
225
+ async (args) => {
226
+ const data = {
227
+ type: 'resume_task',
228
+ taskId: args.task_id,
229
+ groupFolder,
230
+ isMain,
231
+ timestamp: new Date().toISOString(),
232
+ };
233
+
234
+ return ipcResult(TASKS_DIR, data, `Task ${args.task_id} resume requested.`);
235
+ },
236
+ );
237
+
238
+ server.tool(
239
+ 'cancel_task',
240
+ 'Cancel and delete a scheduled task.',
241
+ { task_id: z.string().describe('The task ID to cancel') },
242
+ async (args) => {
243
+ const data = {
244
+ type: 'cancel_task',
245
+ taskId: args.task_id,
246
+ groupFolder,
247
+ isMain,
248
+ timestamp: new Date().toISOString(),
249
+ };
250
+
251
+ return ipcResult(TASKS_DIR, data, `Task ${args.task_id} cancellation requested.`);
252
+ },
253
+ );
254
+
255
+ server.tool(
256
+ 'update_task',
257
+ 'Update an existing scheduled task. Only provided fields are changed; omitted fields stay the same.',
258
+ {
259
+ task_id: z.string().describe('The task ID to update'),
260
+ prompt: z.string().optional().describe('New prompt for the task'),
261
+ schedule_type: z.enum(['cron', 'interval', 'once']).optional().describe('New schedule type'),
262
+ schedule_value: z.string().optional().describe('New schedule value (see schedule_task for format)'),
263
+ script: z.string().optional().describe('New script for the task. Set to empty string to remove the script.'),
264
+ },
265
+ async (args) => {
266
+ // Validate schedule_value if provided
267
+ if (args.schedule_type === 'cron' || (!args.schedule_type && args.schedule_value)) {
268
+ if (args.schedule_value) {
269
+ try {
270
+ CronExpressionParser.parse(args.schedule_value);
271
+ } catch {
272
+ return {
273
+ content: [{ type: 'text' as const, text: `Invalid cron: "${args.schedule_value}".` }],
274
+ isError: true,
275
+ };
276
+ }
277
+ }
278
+ }
279
+ if (args.schedule_type === 'interval' && args.schedule_value) {
280
+ const ms = parseInt(args.schedule_value, 10);
281
+ if (isNaN(ms) || ms <= 0) {
282
+ return {
283
+ content: [{ type: 'text' as const, text: `Invalid interval: "${args.schedule_value}".` }],
284
+ isError: true,
285
+ };
286
+ }
287
+ }
288
+
289
+ const data: Record<string, string | undefined> = {
290
+ type: 'update_task',
291
+ taskId: args.task_id,
292
+ groupFolder,
293
+ isMain: String(isMain),
294
+ timestamp: new Date().toISOString(),
295
+ };
296
+ if (args.prompt !== undefined) data.prompt = args.prompt;
297
+ if (args.script !== undefined) data.script = args.script;
298
+ if (args.schedule_type !== undefined) data.schedule_type = args.schedule_type;
299
+ if (args.schedule_value !== undefined) data.schedule_value = args.schedule_value;
300
+
301
+ return ipcResult(TASKS_DIR, data, `Task ${args.task_id} update requested.`);
302
+ },
303
+ );
304
+
305
+ server.tool(
306
+ 'register_group',
307
+ `Register a new chat/group so the agent can respond to messages there. Main group only.
308
+
309
+ 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.`,
310
+ {
311
+ jid: z.string().describe('The chat JID (e.g., "120363336345536173@g.us", "tg:-1001234567890", "dc:1234567890123456")'),
312
+ name: z.string().describe('Display name for the group'),
313
+ folder: z.string().describe('Channel-prefixed folder name (e.g., "whatsapp_family-chat", "telegram_dev-team")'),
314
+ trigger: z.string().describe('Trigger word (e.g., "@Andy")'),
315
+ },
316
+ async (args) => {
317
+ if (!isMain) {
318
+ return {
319
+ content: [{ type: 'text' as const, text: 'Only the main group can register new groups.' }],
320
+ isError: true,
321
+ };
322
+ }
323
+
324
+ const data = {
325
+ type: 'register_group',
326
+ jid: args.jid,
327
+ name: args.name,
328
+ folder: args.folder,
329
+ trigger: args.trigger,
330
+ timestamp: new Date().toISOString(),
331
+ };
332
+
333
+ return ipcResult(TASKS_DIR, data, `Group "${args.name}" registered. It will start receiving messages immediately.`);
334
+ },
335
+ );
336
+
337
+ // Start the stdio transport
338
+ const transport = new StdioServerTransport();
339
+ 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,25 @@
1
+ #!/bin/bash
2
+ # Build the Argus Claude agent container image.
3
+ # This extends nanoclaw-agent:latest with Python, Poetry, gh CLI, and moon.
4
+ #
5
+ # Run from the nanoclaw project root:
6
+ # ./container/build.sh # build base image first
7
+ # ./container/build-argus.sh # then build the argus image
8
+
9
+ set -e
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ cd "$SCRIPT_DIR"
13
+
14
+ IMAGE_NAME="argus-claude"
15
+ TAG="${1:-latest}"
16
+ CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
17
+
18
+ echo "Building Argus Claude agent container image..."
19
+ echo "Image: ${IMAGE_NAME}:${TAG}"
20
+
21
+ ${CONTAINER_RUNTIME} build -f Dockerfile.argus -t "${IMAGE_NAME}:${TAG}" .
22
+
23
+ echo ""
24
+ echo "Build complete: ${IMAGE_NAME}:${TAG}"
25
+ echo "Set CONTAINER_IMAGE=${IMAGE_NAME}:${TAG} in your .env to use this image."
@@ -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,69 @@
1
+ ---
2
+ name: agent-status
3
+ description: Dashboard of all registered NanoClaw agents — shows online/disabled status, last activity. Use when asked about agent health, who's online, or which agents exist.
4
+ ---
5
+
6
+ # Agent Status Dashboard
7
+
8
+ Generate a live status report of all NanoClaw agents.
9
+
10
+ ## Step 1 — Get registered agents from SQLite
11
+
12
+ ```bash
13
+ python3 -c "
14
+ import sqlite3, json
15
+ db = sqlite3.connect('/workspace/project/store/messages.db')
16
+ rows = db.execute('SELECT name, folder, is_main, requires_trigger FROM registered_groups ORDER BY name').fetchall()
17
+ for r in rows:
18
+ print(json.dumps({'name': r[0], 'folder': r[1], 'is_main': bool(r[2]), 'requires_trigger': bool(r[3])}))
19
+ db.close()
20
+ "
21
+ ```
22
+
23
+ ## Step 2 — Check STATUS.md for each agent
24
+
25
+ For each folder returned above:
26
+
27
+ ```bash
28
+ cat /workspace/project/groups/<folder>/STATUS.md 2>/dev/null || echo "online"
29
+ ```
30
+
31
+ STATUS.md contains either `online` or `disabled` with a reason.
32
+
33
+ ## Step 3 — Get last activity for each agent
34
+
35
+ ```bash
36
+ python3 -c "
37
+ import sqlite3, json
38
+ db = sqlite3.connect('/workspace/project/store/messages.db')
39
+ rows = db.execute('''
40
+ SELECT m.chat_jid, MAX(m.timestamp) as last_active
41
+ FROM messages m
42
+ GROUP BY m.chat_jid
43
+ ''').fetchall()
44
+ for r in rows:
45
+ print(json.dumps({'jid': r[0], 'last_active': r[1]}))
46
+ db.close()
47
+ "
48
+ ```
49
+
50
+ ## Step 4 — Format the report
51
+
52
+ Present as a clean Slack-formatted message:
53
+
54
+ ```
55
+ *NanoClaw Agent Status*
56
+
57
+ • *Argus Alert* — 🟢 online — last active: 2 hours ago
58
+ • *Argus Deps* — 🟢 online — last active: 1 day ago
59
+ • *Byte* — 🟢 online — last active: 30 min ago
60
+ • *Jira Worker* — 🔴 disabled — reason: Too noisy, paused until Monday
61
+ • *Ticket Creator* — 🟢 online — last active: 3 hours ago
62
+ • *Global Claw* — 🟢 online (that's me)
63
+ ```
64
+
65
+ Rules:
66
+ - 🟢 = STATUS.md says `online` or file doesn't exist
67
+ - 🔴 = STATUS.md says `disabled` — always show the reason
68
+ - Last active: format as relative time (e.g. "2 hours ago", "3 days ago")
69
+ - If no messages ever: "never"