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
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # IronClaws Kit
2
+
3
+ Scaffold an IronClaws AI agent platform in under 5 minutes.
4
+
5
+ ```bash
6
+ npx create-ironclaws my-workspace
7
+ ```
8
+
9
+ The wizard walks you through Slack tokens, LiteLLM gateway, and your first agent's channel. It then starts OneCLI, registers credentials, builds the container image, and optionally starts IronClaws. Send `@Forge hello` and your guide agent responds.
10
+
11
+ ---
12
+
13
+ ## What you get
14
+
15
+ Your first agent is **Forge** — a built-in guide that knows how IronClaws works and can help you build new agents directly from Slack. Ask it anything:
16
+
17
+ > `@Forge how do I add a new agent?`
18
+ > `@Forge explain how credentials work`
19
+ > `@Forge build me an agent that monitors Jira`
20
+
21
+ ---
22
+
23
+ ## Adding more agents
24
+
25
+ ### Via Forge (recommended)
26
+ Just describe what you want in Slack. Forge will draft the CLAUDE.md, show you the `agents.yaml` and `.env` entries, and tell you to restart.
27
+
28
+ ### Manually
29
+
30
+ **1. Write the agent's identity** — create `groups/my-agent/CLAUDE.md`:
31
+ ```markdown
32
+ # My Agent
33
+
34
+ What this agent does in one sentence.
35
+
36
+ ## What you do
37
+ Responsibilities.
38
+
39
+ ## How to respond
40
+ Tone, format, constraints.
41
+ ```
42
+
43
+ **2. Register it** — add to `agents.yaml`:
44
+ ```yaml
45
+ - folder: my-agent
46
+ name: "My Agent"
47
+ trigger: "@Argus"
48
+ channel_env: MY_AGENT_CHANNEL_ID
49
+ requires_trigger: false
50
+ onecli_secrets: [litellm]
51
+ onecli_id: <python3 -c "import uuid; print(uuid.uuid4())">
52
+ ```
53
+
54
+ **3. Set the channel** — add to `.env`:
55
+ ```
56
+ MY_AGENT_CHANNEL_ID=C...
57
+ ```
58
+
59
+ **4. Restart IronClaws** — the agent auto-registers, its channel is added to the allowlist, and it's live.
60
+
61
+ ---
62
+
63
+ ## Giving agents tools and credentials
64
+
65
+ Edit `agent-credentials.yaml` to control what each agent can access:
66
+
67
+ ```yaml
68
+ agents:
69
+ my-agent:
70
+ skills: []
71
+ tools: [jira_tool.py] # Python tools from container/tools/
72
+ config:
73
+ - JIRA_BASE_URL # non-secret config from .env
74
+ ```
75
+
76
+ For service credentials (Jira, Slack, etc.) add them to `onecli_secrets` in `agents.yaml`. OneCLI injects them as Authorization headers — agents never see the raw keys.
77
+
78
+ ---
79
+
80
+ ## Requirements
81
+
82
+ - Node.js 18+
83
+ - Docker
84
+ - A Slack app with Socket Mode and a bot token
85
+ - A LiteLLM gateway (or Anthropic API access)
86
+
87
+ ## Manual setup (without the wizard)
88
+
89
+ ```bash
90
+ git clone https://github.com/ardoq/ironclaws-kit my-workspace
91
+ cd my-workspace
92
+ cp .env.example .env # fill in your values
93
+ docker compose up -d # start OneCLI infrastructure
94
+ npm ci
95
+ cd container && ./build.sh && ./build-argus.sh && cd ..
96
+ npm run dev
97
+ ```
98
+
99
+ ## Production note
100
+
101
+ Network enforcement (iptables) is Linux-only. On macOS/Windows dev machines, agents have unrestricted outbound access. Deploy on Linux for production.
package/bin/create.js ADDED
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-ironclaws — Interactive setup wizard for IronClaws
4
+ *
5
+ * Usage:
6
+ * npx create-ironclaws [project-name]
7
+ * npx create-ironclaws@latest my-it-ops
8
+ */
9
+
10
+ import prompts from 'prompts';
11
+ import kleur from 'kleur';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { execSync, spawnSync } from 'child_process';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const TEMPLATE_DIR = path.join(__dirname, '..', 'template');
19
+
20
+ // ── Utilities ──────────────────────────────────────────────────────────────────
21
+
22
+ function log(msg) { console.log(msg); }
23
+ function success(msg) { console.log(kleur.green(' ✓ ') + msg); }
24
+ function info(msg) { console.log(kleur.cyan(' → ') + msg); }
25
+ function warn(msg) { console.log(kleur.yellow(' ⚠ ') + msg); }
26
+ function fatal(msg) { console.error(kleur.red(' ✗ ') + msg); process.exit(1); }
27
+
28
+ function hasCommand(cmd) {
29
+ try { execSync(`which ${cmd}`, { stdio: 'pipe' }); return true; } catch { return false; }
30
+ }
31
+
32
+ function copyDir(src, dest) {
33
+ fs.mkdirSync(dest, { recursive: true });
34
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
35
+ const srcPath = path.join(src, entry.name);
36
+ const destPath = path.join(dest, entry.name);
37
+ if (entry.isDirectory()) {
38
+ copyDir(srcPath, destPath);
39
+ } else {
40
+ fs.copyFileSync(srcPath, destPath);
41
+ }
42
+ }
43
+ }
44
+
45
+ function waitForOneCLI(maxWaitMs = 60000) {
46
+ const start = Date.now();
47
+ info('Waiting for OneCLI to be ready...');
48
+ while (Date.now() - start < maxWaitMs) {
49
+ try {
50
+ execSync('curl -sf http://localhost:10254/api/secrets > /dev/null 2>&1', { stdio: 'pipe' });
51
+ return true;
52
+ } catch {
53
+ execSync('sleep 2', { stdio: 'pipe' });
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+
59
+ function registerOneCLISecret(name, hostPattern, headerName, value, apiKey) {
60
+ const authHeader = apiKey ? `-H "Authorization: Bearer ${apiKey}"` : '';
61
+
62
+ // Delete existing
63
+ try {
64
+ const existing = execSync(
65
+ `curl -sf ${authHeader} http://localhost:10254/api/secrets`,
66
+ { encoding: 'utf-8', stdio: 'pipe' }
67
+ );
68
+ const secrets = JSON.parse(existing);
69
+ const match = secrets.find(s => s.name === name);
70
+ if (match) {
71
+ execSync(`curl -sf -X DELETE ${authHeader} http://localhost:10254/api/secrets/${match.id}`, { stdio: 'pipe' });
72
+ }
73
+ } catch { /* ok */ }
74
+
75
+ // Create
76
+ const body = JSON.stringify({
77
+ name,
78
+ type: 'generic',
79
+ value,
80
+ hostPattern,
81
+ injectionConfig: { headerName },
82
+ });
83
+
84
+ execSync(
85
+ `curl -sf -X POST ${authHeader} http://localhost:10254/api/secrets -H "Content-Type: application/json" -d '${body}'`,
86
+ { stdio: 'pipe' }
87
+ );
88
+ }
89
+
90
+ // ── Main ───────────────────────────────────────────────────────────────────────
91
+
92
+ async function main() {
93
+ console.log();
94
+ console.log(kleur.bold().white(' ✦ IronClaws Setup'));
95
+ console.log(kleur.gray(' AI agents over Slack for IT operations'));
96
+ console.log();
97
+
98
+ // Check prerequisites
99
+ if (!hasCommand('node')) fatal('Node.js is required. Install from nodejs.org');
100
+ if (!hasCommand('docker')) fatal('Docker is required. Install from docs.docker.com');
101
+
102
+ // Project directory
103
+ const projectArg = process.argv[2];
104
+ let projectDir;
105
+
106
+ if (projectArg) {
107
+ projectDir = path.resolve(process.cwd(), projectArg);
108
+ } else {
109
+ const { dir } = await prompts({
110
+ type: 'text',
111
+ name: 'dir',
112
+ message: 'Project directory',
113
+ initial: './my-ironclaws',
114
+ });
115
+ if (!dir) process.exit(0);
116
+ projectDir = path.resolve(process.cwd(), dir);
117
+ }
118
+
119
+ if (fs.existsSync(projectDir) && fs.readdirSync(projectDir).length > 0) {
120
+ const { ok } = await prompts({
121
+ type: 'confirm',
122
+ name: 'ok',
123
+ message: `${projectDir} already exists and is not empty. Continue?`,
124
+ initial: false,
125
+ });
126
+ if (!ok) process.exit(0);
127
+ }
128
+
129
+ console.log();
130
+
131
+ // Core config
132
+ const core = await prompts([
133
+ {
134
+ type: 'text',
135
+ name: 'assistantName',
136
+ message: 'What should your assistant be called?',
137
+ initial: 'Argus',
138
+ },
139
+ {
140
+ type: 'text',
141
+ name: 'slackBotToken',
142
+ message: 'Slack Bot Token (xoxb-...)',
143
+ validate: v => v.startsWith('xoxb-') ? true : 'Must start with xoxb-',
144
+ },
145
+ {
146
+ type: 'text',
147
+ name: 'slackAppToken',
148
+ message: 'Slack App Token (xapp-...)',
149
+ validate: v => v.startsWith('xapp-') ? true : 'Must start with xapp-',
150
+ },
151
+ ], { onCancel: () => process.exit(0) });
152
+
153
+ console.log();
154
+ log(kleur.gray(' LiteLLM gateway (routes Claude API calls through your proxy)'));
155
+
156
+ const llm = await prompts([
157
+ {
158
+ type: 'text',
159
+ name: 'litellmUrl',
160
+ message: 'LiteLLM gateway URL',
161
+ initial: 'https://your-litellm-gateway.example.com',
162
+ },
163
+ {
164
+ type: 'text',
165
+ name: 'litellmToken',
166
+ message: 'LiteLLM auth token (sk-...)',
167
+ },
168
+ {
169
+ type: 'text',
170
+ name: 'anthropicModel',
171
+ message: 'Model name (must match LiteLLM deployment)',
172
+ initial: 'anthropic-claude-opus-4-6',
173
+ },
174
+ ], { onCancel: () => process.exit(0) });
175
+
176
+ // Forge — the built-in guide agent
177
+ console.log();
178
+ log(kleur.gray(' Forge is your built-in guide — it knows how IronClaws works and can'));
179
+ log(kleur.gray(' help you build new agents directly from Slack (@Forge add a new agent).'));
180
+ console.log();
181
+
182
+ const agent = await prompts([
183
+ {
184
+ type: 'text',
185
+ name: 'agentName',
186
+ message: 'Forge display name (shown in Slack)',
187
+ initial: 'Forge',
188
+ },
189
+ {
190
+ type: 'text',
191
+ name: 'agentFolder',
192
+ message: 'Forge folder name',
193
+ initial: 'forge',
194
+ validate: v => /^[a-z0-9-]+$/.test(v) ? true : 'Lowercase letters, numbers and hyphens only',
195
+ },
196
+ {
197
+ type: 'text',
198
+ name: 'channelId',
199
+ message: 'Slack channel ID for Forge (right-click channel → View channel details)',
200
+ validate: v => v.startsWith('C') || v.startsWith('D') ? true : 'Should start with C (channel) or D (DM)',
201
+ },
202
+ ], { onCancel: () => process.exit(0) });
203
+
204
+ // Optional
205
+ console.log();
206
+ const optional = await prompts([
207
+ {
208
+ type: 'confirm',
209
+ name: 'startInfra',
210
+ message: 'Start OneCLI infrastructure now? (docker compose up -d)',
211
+ initial: true,
212
+ },
213
+ {
214
+ type: 'confirm',
215
+ name: 'buildImage',
216
+ message: 'Build the agent container image? (takes ~2 min)',
217
+ initial: true,
218
+ },
219
+ {
220
+ type: 'confirm',
221
+ name: 'startNano',
222
+ message: 'Start NanoClaw now?',
223
+ initial: true,
224
+ },
225
+ ], { onCancel: () => process.exit(0) });
226
+
227
+ // ── Generate project ─────────────────────────────────────────────────────────
228
+
229
+ console.log();
230
+ log(kleur.bold(' Creating your project...'));
231
+ console.log();
232
+
233
+ // Copy template
234
+ copyDir(TEMPLATE_DIR, projectDir);
235
+ success('Template copied');
236
+
237
+ // Rename forge folder to user's chosen name
238
+ const exampleDir = path.join(projectDir, 'groups', 'forge');
239
+ const agentDir = path.join(projectDir, 'groups', agent.agentFolder);
240
+ if (fs.existsSync(exampleDir)) {
241
+ fs.renameSync(exampleDir, agentDir);
242
+ }
243
+
244
+ // Generate .env
245
+ const envContent = [
246
+ `# IronClaws — generated by create-ironclaws`,
247
+ ``,
248
+ `# ── NanoClaw core ─────────────────────────────────────────────────────────────`,
249
+ `ASSISTANT_NAME=${core.assistantName}`,
250
+ `CONTAINER_IMAGE=argus-claude:latest`,
251
+ ``,
252
+ `# ── Slack ─────────────────────────────────────────────────────────────────────`,
253
+ `SLACK_BOT_TOKEN=${core.slackBotToken}`,
254
+ `SLACK_APP_TOKEN=${core.slackAppToken}`,
255
+ ``,
256
+ `# ── LiteLLM / Anthropic ───────────────────────────────────────────────────────`,
257
+ `ANTHROPIC_BASE_URL=${llm.litellmUrl}`,
258
+ `ANTHROPIC_BEDROCK_BASE_URL=${llm.litellmUrl}/bedrock`,
259
+ `ANTHROPIC_AUTH_TOKEN=${llm.litellmToken}`,
260
+ ``,
261
+ `CLAUDE_CODE_USE_BEDROCK=1`,
262
+ `CLAUDE_CODE_SKIP_BEDROCK_AUTH=1`,
263
+ `CLAUDE_CODE_API_KEY_HELPER_TTL_MS=3600000`,
264
+ ``,
265
+ `ANTHROPIC_MODEL=${llm.anthropicModel}`,
266
+ `ANTHROPIC_SMALL_FAST_MODEL=${llm.anthropicModel}`,
267
+ `ANTHROPIC_DEFAULT_OPUS_MODEL=${llm.anthropicModel}`,
268
+ `ANTHROPIC_DEFAULT_SONNET_MODEL=${llm.anthropicModel}`,
269
+ `ANTHROPIC_DEFAULT_HAIKU_MODEL=${llm.anthropicModel}`,
270
+ ``,
271
+ `# ── Agent channel IDs ─────────────────────────────────────────────────────────`,
272
+ `${agent.agentFolder.toUpperCase().replace(/-/g, '_')}_CHANNEL_ID=${agent.channelId}`,
273
+ ].join('\n');
274
+
275
+ fs.writeFileSync(path.join(projectDir, '.env'), envContent);
276
+ success('.env generated');
277
+
278
+ // Generate agents.yaml
279
+ const agentEnvKey = `${agent.agentFolder.toUpperCase().replace(/-/g, '_')}_CHANNEL_ID`;
280
+ const agentsYaml = [
281
+ `# agents.yaml — Your agent fleet`,
282
+ `# Add more agents here. Set their channel ID in .env.`,
283
+ `# NanoClaw auto-registers agents on startup.`,
284
+ ``,
285
+ `agents:`,
286
+ ` - folder: ${agent.agentFolder}`,
287
+ ` name: "${agent.agentName}"`,
288
+ ` trigger: "@${core.assistantName}"`,
289
+ ` channel_env: ${agentEnvKey}`,
290
+ ` requires_trigger: false`,
291
+ ` onecli_secrets: [litellm]`,
292
+ ` onecli_id: ${generateUUID()}`,
293
+ ].join('\n');
294
+
295
+ fs.writeFileSync(path.join(projectDir, 'agents.yaml'), agentsYaml);
296
+
297
+ // Update agent-credentials.yaml with actual agent name
298
+ const credPath = path.join(projectDir, 'agent-credentials.yaml');
299
+ if (fs.existsSync(credPath)) {
300
+ const cred = fs.readFileSync(credPath, 'utf-8')
301
+ .replace('forge:', `${agent.agentFolder}:`);
302
+ fs.writeFileSync(credPath, cred);
303
+ }
304
+
305
+ success('Configuration generated');
306
+
307
+ // Start infrastructure
308
+ if (optional.startInfra) {
309
+ console.log();
310
+ info('Starting OneCLI infrastructure...');
311
+ const result = spawnSync('docker', ['compose', 'up', '-d'], {
312
+ cwd: projectDir,
313
+ stdio: 'inherit',
314
+ });
315
+ if (result.status !== 0) {
316
+ warn('docker compose up failed — start it manually: docker compose up -d');
317
+ } else {
318
+ success('OneCLI infrastructure started');
319
+
320
+ // Register LiteLLM secret
321
+ if (llm.litellmToken && llm.litellmUrl) {
322
+ info('Registering LiteLLM credential with OneCLI...');
323
+ const ready = waitForOneCLI();
324
+ if (ready) {
325
+ try {
326
+ const host = llm.litellmUrl.replace(/^https?:\/\//, '').split('/')[0];
327
+ registerOneCLISecret('litellm', host, 'Authorization', `Bearer ${llm.litellmToken}`, '');
328
+ success(`LiteLLM secret registered for ${host}`);
329
+ } catch (e) {
330
+ warn(`Could not register secret automatically. Run manually: bash ${path.join(projectDir, 'scripts/setup-onecli-secrets.sh')}`);
331
+ }
332
+ } else {
333
+ warn('OneCLI not ready in time — run setup-onecli-secrets.sh manually after it starts');
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Install dependencies
340
+ console.log();
341
+ info('Installing dependencies...');
342
+ spawnSync('npm', ['ci'], { cwd: projectDir, stdio: 'inherit' });
343
+ success('Dependencies installed');
344
+
345
+ // Build container image
346
+ if (optional.buildImage) {
347
+ console.log();
348
+ info('Building container image (this takes ~2 minutes)...');
349
+ const b1 = spawnSync('bash', ['./container/build.sh'], { cwd: projectDir, stdio: 'inherit' });
350
+ const b2 = spawnSync('bash', ['./container/build-argus.sh'], { cwd: projectDir, stdio: 'inherit' });
351
+ if (b1.status === 0 && b2.status === 0) {
352
+ success('Container image built: argus-claude:latest');
353
+ } else {
354
+ warn('Image build failed — run: cd container && ./build.sh && ./build-argus.sh');
355
+ }
356
+ }
357
+
358
+ // Done!
359
+ console.log();
360
+ console.log(kleur.bold().green(' ✦ Setup complete!'));
361
+ console.log();
362
+
363
+ if (optional.startNano) {
364
+ info(`Starting NanoClaw in ${projectDir}...`);
365
+ console.log();
366
+ spawnSync('npm', ['run', 'dev'], { cwd: projectDir, stdio: 'inherit' });
367
+ } else {
368
+ log(` To start NanoClaw:`);
369
+ log(kleur.cyan(` cd ${path.relative(process.cwd(), projectDir)} && npm run dev`));
370
+ console.log();
371
+ log(` Then send this in Slack:`);
372
+ log(kleur.cyan(` @${core.assistantName} hello`));
373
+ console.log();
374
+ if (!optional.startInfra) {
375
+ log(` Don't forget to start OneCLI first:`);
376
+ log(kleur.cyan(` cd ${path.relative(process.cwd(), projectDir)} && docker compose up -d`));
377
+ console.log();
378
+ }
379
+ log(kleur.gray(` ⚠ Network enforcement (iptables) is only active on Linux.`));
380
+ log(kleur.gray(` On macOS/Windows, agents have unrestricted network access.`));
381
+ log(kleur.gray(` Use a Linux VM for production deployments.`));
382
+ }
383
+
384
+ console.log();
385
+ }
386
+
387
+ function generateUUID() {
388
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
389
+ const r = Math.random() * 16 | 0;
390
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
391
+ });
392
+ }
393
+
394
+ main().catch(e => { console.error(e); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "create-ironclaws",
3
+ "version": "1.0.0",
4
+ "description": "Create an IronClaws AI agent platform in seconds",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-ironclaws": "bin/create.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "scripts": {
14
+ "test": "node bin/create.js --dry-run"
15
+ },
16
+ "dependencies": {
17
+ "kleur": "^4.1.5",
18
+ "prompts": "^2.4.2"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "agents",
26
+ "slack",
27
+ "it-ops",
28
+ "claude",
29
+ "nanoclaw",
30
+ "ironclaws"
31
+ ],
32
+ "license": "MIT"
33
+ }
@@ -0,0 +1,38 @@
1
+ # IronClaws configuration
2
+ # Generated by: npx create-ironclaws
3
+ # Fill in all values, then run: docker compose up -d && npm run dev
4
+
5
+ # ── NanoClaw core ─────────────────────────────────────────────────────────────
6
+ ASSISTANT_NAME=Argus
7
+ CONTAINER_IMAGE=argus-claude:latest
8
+
9
+ # ── Slack ─────────────────────────────────────────────────────────────────────
10
+ SLACK_BOT_TOKEN=xoxb-...
11
+ SLACK_APP_TOKEN=xapp-...
12
+
13
+ # ── LiteLLM / Anthropic ───────────────────────────────────────────────────────
14
+ ANTHROPIC_BASE_URL=https://your-litellm-gateway.example.com
15
+ ANTHROPIC_BEDROCK_BASE_URL=https://your-litellm-gateway.example.com/bedrock
16
+ ANTHROPIC_AUTH_TOKEN=sk-...
17
+
18
+ CLAUDE_CODE_USE_BEDROCK=1
19
+ CLAUDE_CODE_SKIP_BEDROCK_AUTH=1
20
+ CLAUDE_CODE_API_KEY_HELPER_TTL_MS=3600000
21
+
22
+ ANTHROPIC_MODEL=anthropic-claude-opus-4-6
23
+ ANTHROPIC_SMALL_FAST_MODEL=anthropic-claude-opus-4-6
24
+ ANTHROPIC_DEFAULT_OPUS_MODEL=anthropic-claude-opus-4-6
25
+ ANTHROPIC_DEFAULT_SONNET_MODEL=anthropic-claude-sonnet-4-6
26
+ ANTHROPIC_DEFAULT_HAIKU_MODEL=anthropic.claude-haiku-4-5
27
+
28
+ # ── GitHub (optional — needed for agents that create PRs) ─────────────────────
29
+ # GITHUB_APP_ID=
30
+ # GITHUB_APP_INSTALLATION_ID=
31
+ # GITHUB_REPO=owner/repo
32
+
33
+ # ── OneCLI API key (if your OneCLI instance has auth enabled) ─────────────────
34
+ # ONECLI_API_KEY=oc_...
35
+
36
+ # ── Agent channel IDs ─────────────────────────────────────────────────────────
37
+ # Right-click a channel in Slack → View channel details → scroll to bottom
38
+ FORGE_CHANNEL_ID=C... # Forge — your IronClaws guide agent
@@ -0,0 +1,104 @@
1
+ # NanoClaw — Ardoq IT Ops Agent Platform
2
+
3
+ Internal deployment of NanoClaw. Hosts the Ardoq IT agent fleet over Slack.
4
+
5
+ ## Architecture
6
+
7
+ Single Node.js host process. Each registered agent gets its own Docker container (isolated filesystem + memory) spawned on demand. Agents communicate back via IPC files. All agents share one Docker image: `argus-claude:latest`.
8
+
9
+ ## Key Files
10
+
11
+ | File | Purpose |
12
+ |------|---------|
13
+ | `src/index.ts` | Main loop: message routing, agent invocation, state |
14
+ | `src/channels/slack.ts` | Slack channel integration (Socket Mode) |
15
+ | `src/container-runner.ts` | Spawns containers with correct mounts and env vars |
16
+ | `src/ipc.ts` | IPC file watcher — agents write here to send messages / schedule tasks |
17
+ | `src/task-scheduler.ts` | Cron-based task scheduling |
18
+ | `src/db.ts` | SQLite: messages, groups, sessions, tasks |
19
+ | `src/config.ts` | Trigger patterns, paths, env var loading |
20
+ | `src/sender-allowlist.ts` | Per-channel sender filtering |
21
+ | `groups/{name}/CLAUDE.md` | Per-agent identity and instructions |
22
+ | `container/Dockerfile.argus` | Agent container image definition |
23
+ | `container/tools/` | Python tools available inside containers |
24
+ | `container/skills/` | Shared skills available to all agents |
25
+
26
+ ## Agent Fleet
27
+
28
+ | Agent | Folder | Channel | Purpose |
29
+ |-------|--------|---------|---------|
30
+ | Argus Alert | `argus-alert` | Alert channel | Monitors alerts, investigates incidents, raises fix PRs |
31
+ | Argus Deps | `argus-deps` | Deps channel | Manages Dependabot alerts, bumps packages |
32
+ | Byte | `byte` | IT standup | Team standup agent, team memory |
33
+ | Byte IT Internal | `byte-it-internal` | IT internal | Silent reader, writes to Byte's MEMORY.md |
34
+ | Ticket Creator | `ticket-creator` | Ticket channel | Creates Jira tickets from Slack conversation |
35
+ | Jira Worker | `jira-worker` | Worker channel | Picks up IAI tickets, implements, raises PRs |
36
+ | Global Claw | `global-claw` | DM (Sewnet only) | Meta-agent: manages all other agents |
37
+
38
+ ## Container Image
39
+
40
+ All agents use `argus-claude:latest`. Built with:
41
+ ```bash
42
+ cd container && ./build-argus.sh
43
+ ```
44
+
45
+ Set `CONTAINER_IMAGE=argus-claude:latest` in `.env`.
46
+
47
+ ## Tools Available Inside Containers
48
+
49
+ Located in `container/tools/` — baked into the image:
50
+ - `elastic_query.py` — Kibana/Elastic log querying
51
+ - `jira_tool.py` — Jira CRUD (get, search, create, update, comment, assign)
52
+ - `gdrive_tool.py` — Google Drive transcript reading
53
+ - `slack_history_tool.py` — Slack channel history fetching
54
+
55
+ ## Skills Available Inside Containers
56
+
57
+ Located in `container/skills/` — loaded at runtime:
58
+ - `agent-browser` — web browsing
59
+ - `capabilities` — list available skills
60
+ - `slack-formatting` — Slack mrkdwn formatting reference
61
+ - `status` — system health check
62
+ - `agent-status` — dashboard of all agents
63
+ - `edit-agent` — collaborative CLAUDE.md editing
64
+
65
+ ## Runtime Config
66
+
67
+ Key env vars (set in `.env`):
68
+ ```
69
+ SLACK_BOT_TOKEN Slack bot token (xoxb-...)
70
+ SLACK_APP_TOKEN Slack app-level token (xapp-...)
71
+ GITHUB_TOKEN GitHub PAT for raising PRs
72
+ GITHUB_APP_ID GitHub App ID (for token generation)
73
+ ELASTIC_BASE_URL Kibana base URL
74
+ ELASTIC_API_KEY Kibana API key
75
+ JIRA_BASE_URL https://ardoqcom.atlassian.net
76
+ JIRA_EMAIL Jira account email
77
+ JIRA_API_TOKEN Jira API token
78
+ JIRA_PROJECT_KEY IAI
79
+ CONTAINER_IMAGE argus-claude:latest
80
+ ```
81
+
82
+ ## Security
83
+
84
+ - Mount allowlist: `~/.config/nanoclaw/mount-allowlist.json` — controls what host paths containers can access
85
+ - Sender allowlist: `~/.config/nanoclaw/sender-allowlist.json` — per-channel sender filtering, outside project root (tamper-proof)
86
+ - Global Claw DM is restricted to Sewnet only (U04S2VA1CU8) at allowlist level
87
+ - `github-app.pem` is gitignored — never commit
88
+
89
+ ## Running
90
+
91
+ ```bash
92
+ npm run dev # Development (hot reload)
93
+ npm run build # Compile TypeScript
94
+ npm start # Production
95
+ ```
96
+
97
+ ## First-Time Setup
98
+
99
+ 1. Copy `.env.example` to `.env` and fill in all values
100
+ 2. Run `./setup-agents.sh` to register all agents in the SQLite database
101
+ 3. Update `~/.config/nanoclaw/sender-allowlist.json` with channel IDs
102
+ 4. Update `~/.config/nanoclaw/mount-allowlist.json` for Global Claw repo access
103
+ 5. Build the container: `cd container && ./build-argus.sh`
104
+ 6. Start: `npm run dev`