let-them-talk 5.3.0 → 5.4.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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7216
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
package/cli.js CHANGED
@@ -4,30 +4,37 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
  const { execSync } = require('child_process');
7
-
8
- const command = process.argv[2];
7
+ const { resolveDataDir: resolveSharedDataDir } = require('./data-dir');
8
+ const { createCanonicalState } = require('./state/canonical');
9
9
 
10
10
  function printUsage() {
11
11
  console.log(`
12
- Let Them Talk — Agent Bridge v5.3.0
12
+ Let Them Talk — Agent Bridge v5.4.0
13
13
  MCP message broker for inter-agent communication
14
14
  Supports: Claude Code, Gemini CLI, Codex CLI, Ollama
15
15
 
16
- Usage:
16
+ Setup (one-time):
17
17
  npx let-them-talk init Auto-detect CLI and configure MCP
18
18
  npx let-them-talk init --claude Configure for Claude Code
19
19
  npx let-them-talk init --gemini Configure for Gemini CLI
20
20
  npx let-them-talk init --codex Configure for Codex CLI
21
21
  npx let-them-talk init --all Configure for all supported CLIs
22
- npx let-them-talk init --ollama Setup Ollama agent bridge (local LLM)
23
- npx let-them-talk init --template T Initialize with a team template (pair, team, review, debate, ollama)
22
+ npx let-them-talk init --ollama Setup Ollama agent bridge (local LLM)
23
+ npx let-them-talk init --template <name> Initialize and print an agent template
24
+
25
+ After init, use the local launcher (no re-download):
26
+ node .agent-bridge/launch.js Dashboard (http://localhost:3000)
27
+ node .agent-bridge/launch.js --lan Dashboard on LAN (phone/tablet)
28
+ node .agent-bridge/launch.js status Show active agents and message count
29
+ node .agent-bridge/launch.js msg <agent> <text> Send a message to an agent
30
+ node .agent-bridge/launch.js reset Clear all conversation data
31
+ node .agent-bridge/launch.js migrate Backfill canonical event stream from legacy projections
32
+ node .agent-bridge/launch.js migrate --dry-run Preview what migrate would do
33
+
34
+ Or via npx (re-downloads each time):
35
+ npx let-them-talk dashboard
36
+ npx let-them-talk status
24
37
  npx let-them-talk templates List available agent templates
25
- npx let-them-talk dashboard Launch the web dashboard (http://localhost:3000)
26
- npx let-them-talk dashboard --lan Launch dashboard accessible on LAN (phone/tablet)
27
- npx let-them-talk reset Clear all conversation data
28
- npx let-them-talk msg <agent> <text> Send a message to an agent
29
- npx let-them-talk run "prompt" [--agents N] [--timeout M] Autonomous execution with N agents, auto-stop after M minutes
30
- npx let-them-talk status Show active agents and message count
31
38
  npx let-them-talk uninstall Remove agent-bridge from all CLI configs
32
39
  npx let-them-talk help Show this help message
33
40
 
@@ -77,11 +84,11 @@ function detectOllama() {
77
84
 
78
85
  // The data directory where all agents read/write — must be the same for server + dashboard
79
86
  function dataDir(cwd) {
80
- return path.join(cwd, '.agent-bridge');
87
+ return resolveSharedDataDir({ cwd });
81
88
  }
82
89
 
83
90
  // Configure for Claude Code (.mcp.json in project root)
84
- function setupClaude(serverPath, cwd) {
91
+ function setupClaude(serverPath, cwd, log = console.log) {
85
92
  const mcpConfigPath = path.join(cwd, '.mcp.json');
86
93
  let mcpConfig = { mcpServers: {} };
87
94
  if (fs.existsSync(mcpConfigPath)) {
@@ -92,7 +99,7 @@ function setupClaude(serverPath, cwd) {
92
99
  // Backup corrupted file before overwriting
93
100
  const backup = mcpConfigPath + '.backup';
94
101
  fs.copyFileSync(mcpConfigPath, backup);
95
- console.log(' [warn] Existing .mcp.json was invalid — backed up to .mcp.json.backup');
102
+ log(' [warn] Existing .mcp.json was invalid — backed up to .mcp.json.backup');
96
103
  }
97
104
  }
98
105
 
@@ -103,11 +110,11 @@ function setupClaude(serverPath, cwd) {
103
110
  };
104
111
 
105
112
  fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
106
- console.log(' [ok] Claude Code: .mcp.json updated');
113
+ log(' [ok] Claude Code: .mcp.json updated');
107
114
  }
108
115
 
109
116
  // Configure for Gemini CLI (.gemini/settings.json or GEMINI.md with MCP config)
110
- function setupGemini(serverPath, cwd) {
117
+ function setupGemini(serverPath, cwd, log = console.log) {
111
118
  // Gemini CLI uses .gemini/settings.json for MCP configuration
112
119
  const geminiDir = path.join(cwd, '.gemini');
113
120
  const settingsPath = path.join(geminiDir, 'settings.json');
@@ -124,7 +131,7 @@ function setupGemini(serverPath, cwd) {
124
131
  } catch {
125
132
  const backup = settingsPath + '.backup';
126
133
  fs.copyFileSync(settingsPath, backup);
127
- console.log(' [warn] Existing settings.json was invalid — backed up to settings.json.backup');
134
+ log(' [warn] Existing settings.json was invalid — backed up to settings.json.backup');
128
135
  }
129
136
  }
130
137
 
@@ -136,11 +143,11 @@ function setupGemini(serverPath, cwd) {
136
143
  };
137
144
 
138
145
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
139
- console.log(' [ok] Gemini CLI: .gemini/settings.json updated');
146
+ log(' [ok] Gemini CLI: .gemini/settings.json updated');
140
147
  }
141
148
 
142
149
  // Configure for Codex CLI (uses .codex/config.toml)
143
- function setupCodex(serverPath, cwd) {
150
+ function setupCodex(serverPath, cwd, log = console.log) {
144
151
  const codexDir = path.join(cwd, '.codex');
145
152
  const configPath = path.join(codexDir, 'config.toml');
146
153
 
@@ -171,16 +178,16 @@ timeout = 300
171
178
  fs.writeFileSync(configPath, config);
172
179
  }
173
180
 
174
- console.log(' [ok] Codex CLI: .codex/config.toml updated');
181
+ log(' [ok] Codex CLI: .codex/config.toml updated');
175
182
  }
176
183
 
177
184
  // Setup Ollama agent bridge script
178
- function setupOllama(serverPath, cwd) {
185
+ function setupOllama(serverPath, cwd, log = console.log) {
179
186
  const dir = dataDir(cwd);
180
- const scriptPath = path.join(cwd, '.agent-bridge', 'ollama-agent.js');
187
+ const scriptPath = path.join(dir, 'ollama-agent.js');
181
188
 
182
- if (!fs.existsSync(path.join(cwd, '.agent-bridge'))) {
183
- fs.mkdirSync(path.join(cwd, '.agent-bridge'), { recursive: true });
189
+ if (!fs.existsSync(dir)) {
190
+ fs.mkdirSync(dir, { recursive: true });
184
191
  }
185
192
 
186
193
  const script = `#!/usr/bin/env node
@@ -291,27 +298,112 @@ process.on('SIGINT', function() { console.log('\\n[' + name + '] Shutting down.'
291
298
  const tmpPath = scriptPath + '.tmp.' + process.pid;
292
299
  fs.writeFileSync(tmpPath, script);
293
300
  fs.renameSync(tmpPath, scriptPath);
294
- console.log(' [ok] Ollama agent script created: .agent-bridge/ollama-agent.js');
295
- console.log('');
296
- console.log(' Launch an Ollama agent with:');
297
- console.log(' node .agent-bridge/ollama-agent.js <name> <model>');
298
- console.log('');
299
- console.log(' Examples:');
300
- console.log(' node .agent-bridge/ollama-agent.js Ollama llama3');
301
- console.log(' node .agent-bridge/ollama-agent.js Coder codellama');
302
- console.log(' node .agent-bridge/ollama-agent.js Writer mistral');
301
+ log(' [ok] Ollama agent script created: .agent-bridge/ollama-agent.js');
302
+ log('');
303
+ log(' Launch an Ollama agent with:');
304
+ log(' node .agent-bridge/ollama-agent.js <name> <model>');
305
+ log('');
306
+ log(' Examples:');
307
+ log(' node .agent-bridge/ollama-agent.js Ollama llama3');
308
+ log(' node .agent-bridge/ollama-agent.js Coder codellama');
309
+ log(' node .agent-bridge/ollama-agent.js Writer mistral');
303
310
  }
304
311
 
305
- function init() {
306
- const cwd = process.cwd();
312
+ // Merge-safe AGENTS.md / CLAUDE.md updater. Writes a marker block with the
313
+ // background-worker rules so any agent cd'd into this folder gets them every
314
+ // session. Re-running replaces only the marked block, leaving user content alone.
315
+ function setupAgentsMarkdown(cwd, log = console.log) {
316
+ const BEGIN = '<!-- BEGIN let-them-talk (auto-managed — do not edit between markers) -->';
317
+ const END = '<!-- END let-them-talk -->';
318
+ const block = [
319
+ BEGIN,
320
+ '',
321
+ '## Let Them Talk — Background-Worker Mode',
322
+ '',
323
+ 'This project uses the `agent-bridge` MCP server for multi-agent coordination.',
324
+ 'When you run in this folder, you are a **background worker on a team**, not an',
325
+ 'interactive chat assistant. Follow these rules strictly:',
326
+ '',
327
+ '1. **Your CLI terminal output is invisible** to the owner and to every other',
328
+ ' agent. If you want anyone to see something, it MUST go through',
329
+ ' `send_message(to="...", content="...")` or `broadcast(content="...")`.',
330
+ '',
331
+ '2. **No narration in terminal.** Do not "reply" to messages in your terminal',
332
+ ' window. Do not summarize your progress in terminal. Do not print status',
333
+ ' updates in terminal. Those are invisible. Talk like a human on a team chat',
334
+ ' — announce starts, finishes, blockers, and questions via `send_message`.',
335
+ '',
336
+ '3. **Stay in the listen loop.** After every action, call `listen_group()` (or',
337
+ ' `listen()` in direct mode). When it returns an empty batch, that is NORMAL',
338
+ ' — call it again immediately. If it returns a tool error like',
339
+ ' `"timed out awaiting tools/call"`, that is a Codex-level transport hiccup',
340
+ ' — immediately call it again. Never stop looping, never treat an empty',
341
+ ' return or tool error as "done".',
342
+ '',
343
+ '4. **Reply to Dashboard/Owner via `send_message(to="Dashboard")`.** The owner',
344
+ ' reads replies in the dashboard Messages tab, not your terminal.',
345
+ '',
346
+ '5. **Do not answer on another agent\'s behalf.** If a message targets a',
347
+ ' specific agent (`msg.to`), only that agent should reply.',
348
+ '',
349
+ '6. The loop only ends when the owner sends a message telling you to stop.',
350
+ '',
351
+ END,
352
+ ].join('\n');
353
+
354
+ const targets = [
355
+ { file: 'AGENTS.md', label: 'Codex / oh-my-codex' },
356
+ { file: 'CLAUDE.md', label: 'Claude Code' },
357
+ ];
358
+
359
+ for (const { file, label } of targets) {
360
+ const fp = path.join(cwd, file);
361
+ let existing = '';
362
+ let existed = false;
363
+ if (fs.existsSync(fp)) {
364
+ existing = fs.readFileSync(fp, 'utf8');
365
+ existed = true;
366
+ }
367
+
368
+ const markerRegex = new RegExp(
369
+ BEGIN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
370
+ '[\\s\\S]*?' +
371
+ END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
372
+ 'g'
373
+ );
374
+
375
+ let next;
376
+ if (markerRegex.test(existing)) {
377
+ // Replace only the managed block
378
+ next = existing.replace(markerRegex, block);
379
+ log(' [ok] ' + file + ': refreshed Let Them Talk block (' + label + ')');
380
+ } else if (existed) {
381
+ // Append below user content
382
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
383
+ next = existing + separator + block + '\n';
384
+ log(' [ok] ' + file + ': appended Let Them Talk block (' + label + ')');
385
+ } else {
386
+ // New file — minimal content so only our block is present
387
+ next = '# ' + path.basename(cwd) + ' — Agent Instructions\n\n' + block + '\n';
388
+ log(' [ok] ' + file + ': created with Let Them Talk block (' + label + ')');
389
+ }
390
+ fs.writeFileSync(fp, next);
391
+ }
392
+ }
393
+
394
+ function init(options) {
395
+ const opts = options || {};
396
+ const cwd = opts.cwd || process.cwd();
307
397
  const serverPath = path.join(__dirname, 'server.js').replace(/\\/g, '/');
308
398
  const gitignorePath = path.join(cwd, '.gitignore');
309
- const flag = process.argv[3];
399
+ const argv = Array.isArray(opts.argv) ? opts.argv : process.argv;
400
+ const flag = opts.flag !== undefined ? opts.flag : argv[3];
401
+ const log = typeof opts.log === 'function' ? opts.log : console.log;
310
402
 
311
- console.log('');
312
- console.log(' Let Them Talk — Initializing Agent Bridge');
313
- console.log(' ==========================================');
314
- console.log('');
403
+ log('');
404
+ log(' Let Them Talk — Initializing Agent Bridge');
405
+ log(' ==========================================');
406
+ log('');
315
407
 
316
408
  let targets = [];
317
409
 
@@ -326,12 +418,12 @@ function init() {
326
418
  } else if (flag === '--ollama') {
327
419
  const ollama = detectOllama();
328
420
  if (!ollama.installed) {
329
- console.log(' Ollama not found. Install it from: https://ollama.com/download');
330
- console.log(' After installing, run: ollama pull llama3');
331
- console.log('');
421
+ log(' Ollama not found. Install it from: https://ollama.com/download');
422
+ log(' After installing, run: ollama pull llama3');
423
+ log('');
332
424
  } else {
333
- console.log(' Ollama detected: ' + ollama.version);
334
- setupOllama(serverPath, cwd);
425
+ log(' Ollama detected: ' + ollama.version);
426
+ setupOllama(serverPath, cwd, log);
335
427
  }
336
428
  targets = detectCLIs();
337
429
  if (targets.length === 0) targets = ['claude'];
@@ -341,22 +433,28 @@ function init() {
341
433
  if (targets.length === 0) {
342
434
  // Default to claude if nothing detected
343
435
  targets = ['claude'];
344
- console.log(' No CLI detected, defaulting to Claude Code config.');
436
+ log(' No CLI detected, defaulting to Claude Code config.');
345
437
  } else {
346
- console.log(` Detected CLI(s): ${targets.join(', ')}`);
438
+ log(` Detected CLI(s): ${targets.join(', ')}`);
347
439
  }
348
440
  }
349
441
 
350
- console.log('');
442
+ log('');
351
443
 
352
444
  for (const target of targets) {
353
445
  switch (target) {
354
- case 'claude': setupClaude(serverPath, cwd); break;
355
- case 'gemini': setupGemini(serverPath, cwd); break;
356
- case 'codex': setupCodex(serverPath, cwd); break;
446
+ case 'claude': setupClaude(serverPath, cwd, log); break;
447
+ case 'gemini': setupGemini(serverPath, cwd, log); break;
448
+ case 'codex': setupCodex(serverPath, cwd, log); break;
357
449
  }
358
450
  }
359
451
 
452
+ // Persistent system-level directive for any agent that starts in this folder.
453
+ // Codex (via oh-my-codex's developer_instructions) and Claude Code both read
454
+ // AGENTS.md / CLAUDE.md automatically on startup. A marker block lets us merge
455
+ // in/out cleanly without clobbering whatever else the user has written.
456
+ setupAgentsMarkdown(cwd, log);
457
+
360
458
  // Add .agent-bridge/ and MCP config files to .gitignore
361
459
  const gitignoreEntries = ['.agent-bridge/', '.mcp.json', '.codex/', '.gemini/'];
362
460
  if (fs.existsSync(gitignorePath)) {
@@ -365,24 +463,62 @@ function init() {
365
463
  if (missing.length) {
366
464
  content += '\n# Agent Bridge (auto-added by let-them-talk init)\n' + missing.join('\n') + '\n';
367
465
  fs.writeFileSync(gitignorePath, content);
368
- console.log(' [ok] Added to .gitignore: ' + missing.join(', '));
466
+ log(' [ok] Added to .gitignore: ' + missing.join(', '));
369
467
  } else {
370
- console.log(' [ok] .gitignore already configured');
468
+ log(' [ok] .gitignore already configured');
371
469
  }
372
470
  } else {
373
471
  fs.writeFileSync(gitignorePath, '# Agent Bridge (auto-added by let-them-talk init)\n' + gitignoreEntries.join('\n') + '\n');
374
- console.log(' [ok] .gitignore created');
472
+ log(' [ok] .gitignore created');
375
473
  }
376
474
 
377
- console.log('');
378
- console.log(' Agent Bridge is ready! Restart your CLI to pick up the MCP tools.');
379
- console.log('');
475
+ // Save local launcher scripts so users never need to re-download
476
+ const bridgeDir = dataDir(cwd);
477
+ if (!fs.existsSync(bridgeDir)) {
478
+ fs.mkdirSync(bridgeDir, { recursive: true });
479
+ }
480
+
481
+ const cliPath = path.join(__dirname, 'cli.js').replace(/\\/g, '/');
482
+
483
+ // Dashboard launcher - run with: node .agent-bridge/launch.js
484
+ const launcherScript = `#!/usr/bin/env node
485
+ // Auto-generated by let-them-talk init - launch dashboard without re-downloading
486
+ // Usage: node .agent-bridge/launch.js [--lan|dashboard|status|reset|msg]
487
+
488
+ const firstArg = process.argv[2] || 'dashboard';
489
+ const cliPath = ${JSON.stringify(cliPath)};
490
+
491
+ try {
492
+ require('fs').accessSync(cliPath);
493
+ } catch {
494
+ console.error(' Let Them Talk CLI not found at: ' + cliPath);
495
+ console.error(' The npx cache may have been cleaned. Fix with either:');
496
+ console.error(' npx let-them-talk init (re-creates launcher)');
497
+ console.error(' npm i -g let-them-talk (permanent global install)');
498
+ process.exit(1);
499
+ }
500
+
501
+ // Forward to cli.js with the command
502
+ const forwardedArgs = firstArg === '--lan'
503
+ ? ['dashboard', '--lan', ...process.argv.slice(3)]
504
+ : [firstArg, ...process.argv.slice(3)];
505
+ process.argv = [process.argv[0], cliPath, ...forwardedArgs];
506
+ require(cliPath);
507
+ `;
508
+
509
+ fs.writeFileSync(path.join(bridgeDir, 'launch.js'), launcherScript);
510
+ const launcherPath = path.join(bridgeDir, 'launch.js');
511
+ log(' [ok] Local launcher saved to .agent-bridge/launch.js');
512
+
513
+ log('');
514
+ log(' Agent Bridge is ready! Restart your CLI to pick up the MCP tools.');
515
+ log('');
380
516
 
381
517
  // Show template if --template was provided
382
518
  var templateFlag = null;
383
- for (var i = 3; i < process.argv.length; i++) {
384
- if (process.argv[i] === '--template' && process.argv[i + 1]) {
385
- templateFlag = process.argv[i + 1];
519
+ for (var i = 3; i < argv.length; i++) {
520
+ if (argv[i] === '--template' && argv[i + 1]) {
521
+ templateFlag = argv[i + 1];
386
522
  break;
387
523
  }
388
524
  }
@@ -390,22 +526,30 @@ function init() {
390
526
  if (templateFlag) {
391
527
  showTemplate(templateFlag);
392
528
  } else {
393
- console.log(' Open two terminals and start a conversation between agents.');
394
- console.log(' Tip: Use "npx let-them-talk init --template pair" for ready-made prompts.');
395
- console.log('');
396
- console.log(' \x1b[1m Try autonomous mode:\x1b[0m');
397
- console.log(' npx let-them-talk run "build a REST API" --agents 3');
398
- console.log('');
399
- console.log(' \x1b[1m Monitor:\x1b[0m');
400
- console.log(' npx let-them-talk dashboard');
401
- console.log(' npx let-them-talk status');
402
- console.log(' npx let-them-talk doctor');
403
- console.log('');
529
+ log(' Open two terminals and start a conversation between agents.');
530
+ log(' Tip: Use "npx let-them-talk init --template pair" for ready-made prompts.');
531
+ log('');
532
+ log(' \x1b[1m Monitor:\x1b[0m');
533
+ log(' node .agent-bridge/launch.js (dashboard)');
534
+ log(' node .agent-bridge/launch.js status (agent status)');
535
+ log(' node .agent-bridge/launch.js reset (clear data)');
536
+ log('');
537
+ log(' Or use npx (re-downloads each time):');
538
+ log(' npx let-them-talk dashboard');
539
+ log('');
404
540
  }
541
+
542
+ return {
543
+ cwd,
544
+ flag: flag || null,
545
+ targets,
546
+ bridgeDir,
547
+ launcherPath,
548
+ };
405
549
  }
406
550
 
407
551
  function reset() {
408
- const targetDir = process.env.AGENT_BRIDGE_DATA_DIR || path.join(process.cwd(), '.agent-bridge');
552
+ const targetDir = resolveDataDirCli();
409
553
 
410
554
  if (!fs.existsSync(targetDir)) {
411
555
  console.log(' No .agent-bridge/ directory found. Nothing to reset.');
@@ -436,38 +580,79 @@ function reset() {
436
580
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
437
581
  const archivePath = path.join(archiveDir, timestamp);
438
582
  try {
439
- fs.mkdirSync(archivePath, { recursive: true });
440
- const filesToArchive = ['history.jsonl', 'messages.jsonl', 'agents.json', 'decisions.json', 'tasks.json'];
441
- let archived = 0;
442
- for (const f of filesToArchive) {
443
- const src = path.join(targetDir, f);
444
- if (fs.existsSync(src)) {
445
- fs.copyFileSync(src, path.join(archivePath, f));
446
- archived++;
447
- }
448
- }
449
- if (archived > 0) {
450
- console.log(' [ok] Archived ' + archived + ' files to .agent-bridge-archive/' + timestamp + '/');
583
+ const archiveResult = getCanonicalStateCli().archiveFiles({
584
+ fileNames: ['history.jsonl', 'messages.jsonl', 'agents.json', 'decisions.json', 'tasks.json'],
585
+ destinationDir: archivePath,
586
+ });
587
+ if (archiveResult.archived > 0) {
588
+ console.log(' [ok] Archived ' + archiveResult.archived + ' files to .agent-bridge-archive/' + timestamp + '/');
451
589
  }
452
590
  } catch (e) {
453
591
  console.log(' [warn] Could not archive: ' + e.message + ' — proceeding with reset anyway.');
454
592
  }
455
593
 
456
- fs.rmSync(targetDir, { recursive: true, force: true });
457
- fs.mkdirSync(targetDir, { recursive: true });
594
+ getCanonicalStateCli().resetRuntime({
595
+ fixedFileNames: [
596
+ 'messages.jsonl',
597
+ 'history.jsonl',
598
+ 'agents.json',
599
+ 'acks.json',
600
+ 'tasks.json',
601
+ 'profiles.json',
602
+ 'workflows.json',
603
+ 'branches.json',
604
+ 'read_receipts.json',
605
+ 'permissions.json',
606
+ 'config.json',
607
+ 'decisions.json',
608
+ ],
609
+ });
458
610
  console.log(' Cleared all data from ' + targetDir);
459
611
  }
460
612
 
461
613
  function getTemplates() {
462
- const templatesDir = path.join(__dirname, 'templates');
463
- if (!fs.existsSync(templatesDir)) return [];
464
- return fs.readdirSync(templatesDir)
465
- .filter(f => f.endsWith('.json'))
466
- .map(f => {
467
- try { return JSON.parse(fs.readFileSync(path.join(templatesDir, f), 'utf8')); }
468
- catch { return null; }
469
- })
470
- .filter(Boolean);
614
+ var all = [];
615
+
616
+ // 1. Built-in templates (shipped with the package)
617
+ var builtinDir = path.join(__dirname, 'templates');
618
+ if (fs.existsSync(builtinDir)) {
619
+ fs.readdirSync(builtinDir).filter(f => f.endsWith('.json')).forEach(f => {
620
+ try { var t = JSON.parse(fs.readFileSync(path.join(builtinDir, f), 'utf8')); t._source = 'built-in'; all.push(t); }
621
+ catch {}
622
+ });
623
+ }
624
+
625
+ // 2. Project-local templates: .agent-bridge/templates/ in current working directory
626
+ var localDir = path.join(resolveDataDirCli(), 'templates');
627
+ if (fs.existsSync(localDir)) {
628
+ fs.readdirSync(localDir).filter(f => f.endsWith('.json')).forEach(f => {
629
+ try {
630
+ var t = JSON.parse(fs.readFileSync(path.join(localDir, f), 'utf8'));
631
+ t._source = 'local';
632
+ // Don't add duplicates (local overrides built-in with same name)
633
+ var existing = all.findIndex(e => e.name === t.name);
634
+ if (existing >= 0) all[existing] = t;
635
+ else all.push(t);
636
+ } catch {}
637
+ });
638
+ }
639
+
640
+ // 3. User-global templates: ~/.let-them-talk/templates/
641
+ var homeDir = process.env.HOME || process.env.USERPROFILE || '';
642
+ var globalDir = path.join(homeDir, '.let-them-talk', 'templates');
643
+ if (fs.existsSync(globalDir)) {
644
+ fs.readdirSync(globalDir).filter(f => f.endsWith('.json')).forEach(f => {
645
+ try {
646
+ var t = JSON.parse(fs.readFileSync(path.join(globalDir, f), 'utf8'));
647
+ t._source = 'global';
648
+ var existing = all.findIndex(e => e.name === t.name);
649
+ if (existing >= 0) all[existing] = t;
650
+ else all.push(t);
651
+ } catch {}
652
+ });
653
+ }
654
+
655
+ return all;
471
656
  }
472
657
 
473
658
  function listTemplates() {
@@ -478,11 +663,17 @@ function listTemplates() {
478
663
  console.log('');
479
664
  for (const t of templates) {
480
665
  const agentNames = t.agents.map(a => a.name).join(', ');
481
- console.log(' ' + t.name.padEnd(12) + ' ' + t.description);
666
+ const sourceTag = t._source === 'local' ? ' [local]' : t._source === 'global' ? ' [global]' : '';
667
+ console.log(' ' + t.name.padEnd(12) + ' ' + t.description + sourceTag);
482
668
  console.log(' ' + ''.padEnd(12) + ' Agents: ' + agentNames);
483
669
  console.log('');
484
670
  }
485
671
  console.log(' Usage: npx let-them-talk init --template <name>');
672
+ console.log(' Note: this command lists agent templates only. Conversation workflow templates ship separately in agent-bridge/conversation-templates/*.json.');
673
+ console.log('');
674
+ console.log(' Custom templates:');
675
+ console.log(' Project-local: .agent-bridge/templates/*.json');
676
+ console.log(' User-global: ~/.let-them-talk/templates/*.json');
486
677
  console.log('');
487
678
  }
488
679
 
@@ -499,8 +690,9 @@ function showTemplate(templateName) {
499
690
  console.log(' Template: ' + template.name);
500
691
  console.log(' ' + template.description);
501
692
  console.log('');
502
- console.log(' Copy these prompts into each terminal:');
693
+ console.log(' Copy these agent prompts into each terminal:');
503
694
  console.log(' ======================================');
695
+ console.log(' These prompts assume current onboarding: register, get_briefing(), then get_guide() when you need the current rules.');
504
696
 
505
697
  for (var i = 0; i < template.agents.length; i++) {
506
698
  var a = template.agents[i];
@@ -520,7 +712,11 @@ function dashboard() {
520
712
  }
521
713
 
522
714
  function resolveDataDirCli() {
523
- return process.env.AGENT_BRIDGE_DATA_DIR || path.join(process.cwd(), '.agent-bridge');
715
+ return resolveSharedDataDir();
716
+ }
717
+
718
+ function getCanonicalStateCli() {
719
+ return createCanonicalState({ dataDir: resolveDataDirCli(), processPid: process.pid });
524
720
  }
525
721
 
526
722
  function readJsonl(filePath) {
@@ -569,10 +765,7 @@ function cliMsg() {
569
765
  timestamp: new Date().toISOString(),
570
766
  };
571
767
 
572
- const messagesFile = path.join(dir, 'messages.jsonl');
573
- const historyFile = path.join(dir, 'history.jsonl');
574
- fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
575
- fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
768
+ getCanonicalStateCli().appendMessage(msg);
576
769
 
577
770
  console.log(' Message sent to ' + recipient + ': ' + text);
578
771
  }
@@ -658,155 +851,13 @@ function cliStatus() {
658
851
  console.log('');
659
852
  }
660
853
 
661
- // v5.0: One-command autonomous execution
662
- function cliRun() {
663
- const prompt = process.argv[3];
664
- if (!prompt) {
665
- console.error(' Usage: npx let-them-talk run "build a login system" [--agents N] [--timeout M]');
666
- console.error(' Spawns N agent processes, auto-assigns roles, creates autonomous workflow, and starts execution.');
667
- process.exit(1);
668
- }
669
-
670
- // Parse --agents flag (default: 3)
671
- let agentCount = 3;
672
- const agentsIdx = process.argv.indexOf('--agents');
673
- if (agentsIdx !== -1 && process.argv[agentsIdx + 1]) {
674
- agentCount = Math.max(2, Math.min(10, parseInt(process.argv[agentsIdx + 1]) || 3));
675
- }
676
-
677
- // Parse --timeout flag (default: no timeout, in minutes)
678
- let timeoutMin = 0;
679
- const timeoutIdx = process.argv.indexOf('--timeout');
680
- if (timeoutIdx !== -1 && process.argv[timeoutIdx + 1]) {
681
- timeoutMin = Math.max(1, parseInt(process.argv[timeoutIdx + 1]) || 0);
682
- }
683
-
684
- const cwd = process.cwd();
685
- const dir = path.join(cwd, '.agent-bridge');
686
- const serverPath = path.join(__dirname, 'server.js');
687
-
688
- // Ensure data directory exists
689
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
690
-
691
- // Set group conversation mode
692
- const configPath = path.join(dir, 'config.json');
693
- const config = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {};
694
- config.conversation_mode = 'group';
695
- fs.writeFileSync(configPath, JSON.stringify(config));
696
-
697
- // Agent names based on count
698
- const AGENT_NAMES = ['Lead', 'Builder', 'Reviewer', 'Architect', 'Frontend', 'Backend', 'Tester', 'Designer', 'DevOps', 'Security'];
699
- const names = AGENT_NAMES.slice(0, agentCount);
700
-
701
- console.log('');
702
- console.log(' Let Them Talk — Autonomous Run');
703
- console.log(' ===============================');
704
- console.log(' Prompt: ' + prompt);
705
- console.log(' Agents: ' + agentCount + ' (' + names.join(', ') + ')');
706
- console.log(' Mode: Autonomous (proactive work loop)');
707
- console.log('');
708
-
709
- const { spawn } = require('child_process');
710
- const children = [];
711
-
712
- // Spawn agent processes
713
- for (let i = 0; i < agentCount; i++) {
714
- const agentName = names[i];
715
- console.log(' Spawning agent: ' + agentName + '...');
716
-
717
- const child = spawn('node', [serverPath], {
718
- env: {
719
- ...process.env,
720
- AGENT_BRIDGE_DATA_DIR: dir,
721
- AGENT_BRIDGE_AUTO_REGISTER: agentName,
722
- AGENT_BRIDGE_AUTO_PROMPT: i === 0 ? prompt : '', // only first agent gets the prompt
723
- },
724
- stdio: 'pipe',
725
- cwd: cwd,
726
- });
727
-
728
- child.on('error', (err) => {
729
- console.error(' [' + agentName + '] Error: ' + err.message);
730
- });
731
-
732
- child.on('exit', (code) => {
733
- if (code !== 0 && code !== null) {
734
- console.log(' \x1b[33m[' + agentName + '] Crashed (code ' + code + '). Auto-restarting...\x1b[0m');
735
- const restart = spawn('node', [serverPath], {
736
- env: { ...process.env, AGENT_BRIDGE_DATA_DIR: dir, AGENT_BRIDGE_AUTO_REGISTER: agentName },
737
- stdio: 'pipe', cwd: cwd,
738
- });
739
- const entry = children.find(c => c.name === agentName);
740
- if (entry) entry.process = restart;
741
- } else {
742
- console.log(' [' + agentName + '] Exited.');
743
- }
744
- });
745
-
746
- children.push({ name: agentName, process: child });
747
- }
748
-
749
- console.log('');
750
- console.log(' All ' + agentCount + ' agents spawned. They will auto-register and start working.');
751
- console.log(' Open the dashboard to monitor: npx let-them-talk dashboard');
752
- console.log('');
753
- console.log(' Press Ctrl+C to stop all agents.');
754
- console.log('');
755
-
756
- // Inject the prompt as a dashboard message after agents register
757
- setTimeout(() => {
758
- try {
759
- const messagesFile = path.join(dir, 'messages.jsonl');
760
- const msg = {
761
- id: 'run_' + Date.now().toString(36),
762
- from: 'Dashboard',
763
- to: '__group__',
764
- content: prompt,
765
- timestamp: new Date().toISOString(),
766
- broadcast: true,
767
- };
768
- fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
769
- const historyFile = path.join(dir, 'history.jsonl');
770
- fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
771
- console.log(' Prompt injected to team. Agents will pick it up via get_work().');
772
- } catch (e) {
773
- console.error(' Failed to inject prompt: ' + e.message);
774
- }
775
- }, 3000); // 3s delay for agents to register
776
-
777
- // Clean shutdown on Ctrl+C
778
- process.on('SIGINT', () => {
779
- console.log('\n Stopping all agents...');
780
- for (const c of children) {
781
- try { c.process.kill(); } catch {}
782
- }
783
- process.exit(0);
784
- });
785
-
786
- // Auto-stop after --timeout minutes
787
- if (timeoutMin > 0) {
788
- console.log(' Auto-stop in ' + timeoutMin + ' minute(s).');
789
- setTimeout(() => {
790
- console.log('\n Timeout reached (' + timeoutMin + 'min). Stopping all agents...');
791
- for (const c of children) { try { c.process.kill(); } catch {} }
792
- process.exit(0);
793
- }, timeoutMin * 60000);
794
- }
795
-
796
- // Periodic progress updates every 30s
797
- setInterval(() => {
798
- try {
799
- const agentsData = fs.existsSync(path.join(dir, 'agents.json')) ? JSON.parse(fs.readFileSync(path.join(dir, 'agents.json'), 'utf8')) : {};
800
- const online = Object.values(agentsData).filter(a => {
801
- try { process.kill(a.pid, 0); return true; } catch { return false; }
802
- }).length;
803
- const history = fs.existsSync(path.join(dir, 'history.jsonl')) ? fs.readFileSync(path.join(dir, 'history.jsonl'), 'utf8').trim().split(/\r?\n/).filter(l => l.trim()).length : 0;
804
- const tasksData = fs.existsSync(path.join(dir, 'tasks.json')) ? JSON.parse(fs.readFileSync(path.join(dir, 'tasks.json'), 'utf8')) : [];
805
- const done = Array.isArray(tasksData) ? tasksData.filter(t => t.status === 'done').length : 0;
806
- const active = Array.isArray(tasksData) ? tasksData.filter(t => t.status === 'in_progress').length : 0;
807
- console.log(` \x1b[90m[${new Date().toLocaleTimeString()}]\x1b[0m ${online} agents | ${history} msgs | ${done} done, ${active} active`);
808
- } catch {}
809
- }, 30000);
854
+ function cliMigrate() {
855
+ const args = process.argv.slice(3);
856
+ const dryRun = args.includes('--dry-run') || args.includes('-n');
857
+ const positional = args.filter((a) => !a.startsWith('-'));
858
+ const projectArg = positional[0] || process.cwd();
859
+ const { migrate } = require('./scripts/migrate-legacy-to-canonical');
860
+ migrate(projectArg, { dryRun });
810
861
  }
811
862
 
812
863
  // v5.0: Diagnostic health check
@@ -817,7 +868,7 @@ function cliDoctor() {
817
868
  let issues = 0;
818
869
 
819
870
  // Check data directory
820
- const dir = path.join(process.cwd(), '.agent-bridge');
871
+ const dir = resolveDataDirCli();
821
872
  if (fs.existsSync(dir)) {
822
873
  console.log(' \x1b[32m✓\x1b[0m .agent-bridge/ directory exists');
823
874
  try { fs.accessSync(dir, fs.constants.W_OK); console.log(' \x1b[32m✓\x1b[0m .agent-bridge/ is writable'); }
@@ -1041,49 +1092,154 @@ function uninstall() {
1041
1092
  console.log('');
1042
1093
  }
1043
1094
 
1044
- switch (command) {
1045
- case 'init':
1046
- init();
1047
- break;
1048
- case 'templates':
1049
- listTemplates();
1050
- break;
1051
- case 'dashboard':
1052
- dashboard();
1053
- break;
1054
- case 'reset':
1055
- reset();
1056
- break;
1057
- case 'doctor':
1058
- cliDoctor();
1059
- break;
1060
- case 'msg':
1061
- case 'message':
1062
- case 'send':
1063
- cliMsg();
1064
- break;
1065
- case 'status':
1066
- cliStatus();
1067
- break;
1068
- case 'run':
1069
- cliRun();
1070
- break;
1071
- case 'uninstall':
1072
- case 'remove':
1073
- uninstall();
1074
- break;
1075
- case 'plugin':
1076
- case 'plugins':
1077
- console.log(' Plugins have been removed in v3.4.3. CLI terminals have their own extension systems.');
1078
- break;
1079
- case 'help':
1080
- case '--help':
1081
- case '-h':
1082
- case undefined:
1083
- printUsage();
1084
- break;
1085
- default:
1086
- console.error(` Unknown command: ${command}`);
1087
- printUsage();
1088
- process.exit(1);
1095
+ // --- Assistant init ---
1096
+ function assistantInit() {
1097
+ const cwd = process.cwd();
1098
+ const dataDir = path.join(cwd, '.agent-bridge');
1099
+ const assistantDir = path.join(dataDir, 'assistant');
1100
+
1101
+ console.log('');
1102
+ console.log(' \x1b[1m\x1b[35mLet Them Talk — Assistant Setup\x1b[0m');
1103
+ console.log(' ════════════════════════════════════');
1104
+ console.log('');
1105
+
1106
+ // Check if Let Them Talk is already initialized
1107
+ if (!fs.existsSync(dataDir)) {
1108
+ console.log(' \x1b[31m✗ Let Them Talk not found.\x1b[0m');
1109
+ console.log(' Run \x1b[36mnpx let-them-talk init\x1b[0m first.');
1110
+ console.log('');
1111
+ return;
1112
+ }
1113
+
1114
+ // Create assistant directory with template files
1115
+ if (!fs.existsSync(assistantDir)) {
1116
+ fs.mkdirSync(assistantDir, { recursive: true });
1117
+ }
1118
+
1119
+ const templateDir = path.join(__dirname, 'assistant');
1120
+ const files = ['Soul.md', 'Identity.md', 'Memory.md', 'Skills.md', 'Tools.md', 'SafetyRules.md'];
1121
+ let created = 0;
1122
+ let skipped = 0;
1123
+
1124
+ for (const file of files) {
1125
+ const dest = path.join(assistantDir, file);
1126
+ if (fs.existsSync(dest)) {
1127
+ console.log(' \x1b[33m⊘\x1b[0m ' + file + ' (already exists, skipped)');
1128
+ skipped++;
1129
+ continue;
1130
+ }
1131
+ const src = path.join(templateDir, file);
1132
+ if (fs.existsSync(src)) {
1133
+ fs.copyFileSync(src, dest);
1134
+ } else {
1135
+ // Fallback: create minimal template
1136
+ const content = `# ${file.replace('.md', '')}\n\nEdit this file to customize your assistant.\n`;
1137
+ fs.writeFileSync(dest, content);
1138
+ }
1139
+ console.log(' \x1b[32m✓\x1b[0m ' + file);
1140
+ created++;
1141
+ }
1142
+
1143
+ console.log('');
1144
+ if (created > 0) {
1145
+ console.log(` Created ${created} file${created > 1 ? 's' : ''} in .agent-bridge/assistant/`);
1146
+ }
1147
+ if (skipped > 0) {
1148
+ console.log(` Skipped ${skipped} existing file${skipped > 1 ? 's' : ''}`);
1149
+ }
1150
+
1151
+ console.log('');
1152
+ console.log(' \x1b[1mNext steps:\x1b[0m');
1153
+ console.log('');
1154
+ console.log(' 1. Edit the personality files in \x1b[36m.agent-bridge/assistant/\x1b[0m');
1155
+ console.log(' - Soul.md → personality and tone');
1156
+ console.log(' - Identity.md → name and role');
1157
+ console.log(' - Skills.md → what it can do');
1158
+ console.log(' - Tools.md → allowed/blocked tools');
1159
+ console.log(' - SafetyRules.md → safety guardrails');
1160
+ console.log('');
1161
+ console.log(' 2. Start the dashboard:');
1162
+ console.log(' \x1b[36mnode .agent-bridge/launch.js\x1b[0m');
1163
+ console.log('');
1164
+ console.log(' 3. Open the Assistant tab in the dashboard:');
1165
+ console.log(' \x1b[36mhttp://<your-ip>:3000\x1b[0m → click \x1b[35mAssistant\x1b[0m tab');
1166
+ console.log('');
1167
+ console.log(' 4. Open a terminal with Let Them Talk and register as "Assistant"');
1168
+ console.log(' The agent should use the \x1b[36massistant()\x1b[0m tool to listen.');
1169
+ console.log('');
1170
+ }
1171
+
1172
+ function runCli() {
1173
+ const command = process.argv[2];
1174
+
1175
+ switch (command) {
1176
+ case 'init':
1177
+ init();
1178
+ break;
1179
+ case 'assistant':
1180
+ assistantInit();
1181
+ break;
1182
+ case 'templates':
1183
+ listTemplates();
1184
+ break;
1185
+ case 'dashboard':
1186
+ dashboard();
1187
+ break;
1188
+ case 'reset':
1189
+ reset();
1190
+ break;
1191
+ case 'doctor':
1192
+ cliDoctor();
1193
+ break;
1194
+ case 'migrate':
1195
+ case 'migrate-legacy':
1196
+ cliMigrate();
1197
+ break;
1198
+ case 'msg':
1199
+ case 'message':
1200
+ case 'send':
1201
+ cliMsg();
1202
+ break;
1203
+ case 'status':
1204
+ cliStatus();
1205
+ break;
1206
+ case 'uninstall':
1207
+ case 'remove':
1208
+ uninstall();
1209
+ break;
1210
+ case 'plugin':
1211
+ case 'plugins':
1212
+ console.log(' Plugins have been removed in v3.4.3. CLI terminals have their own extension systems.');
1213
+ break;
1214
+ case 'help':
1215
+ case '--help':
1216
+ case '-h':
1217
+ case undefined:
1218
+ printUsage();
1219
+ break;
1220
+ default:
1221
+ console.error(` Unknown command: ${command}`);
1222
+ printUsage();
1223
+ process.exit(1);
1224
+ }
1225
+ }
1226
+
1227
+ function shouldAutoRunCli() {
1228
+ if (require.main === module) return true;
1229
+ const argvPath = process.argv[1];
1230
+ if (!argvPath) return false;
1231
+ const normalizedArgvPath = path.resolve(argvPath).replace(/\\/g, '/');
1232
+ const normalizedFilePath = path.resolve(__filename).replace(/\\/g, '/');
1233
+ return process.platform === 'win32'
1234
+ ? normalizedArgvPath.toLowerCase() === normalizedFilePath.toLowerCase()
1235
+ : normalizedArgvPath === normalizedFilePath;
1236
+ }
1237
+
1238
+ module.exports = {
1239
+ init,
1240
+ runCli,
1241
+ };
1242
+
1243
+ if (shouldAutoRunCli()) {
1244
+ runCli();
1089
1245
  }