u-foo 1.0.6 → 1.1.9

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 (149) hide show
  1. package/README.md +44 -4
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +11 -2
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +154 -0
  64. package/src/chat/index.js +935 -2909
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +132 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1580 -0
  98. package/src/config.js +47 -1
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +661 -488
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. package/src/bus/API_DESIGN.md +0 -204
package/src/cli.js CHANGED
@@ -3,6 +3,9 @@ const { spawnSync } = require("child_process");
3
3
  const net = require("net");
4
4
  const fs = require("fs");
5
5
  const { socketPath, isRunning } = require("./daemon");
6
+ const { runBusCoreCommand } = require("./cli/busCoreCommands");
7
+ const { runCtxCommand } = require("./cli/ctxCoreCommands");
8
+ const { runOnlineCommand } = require("./cli/onlineCoreCommands");
6
9
 
7
10
  function getPackageRoot() {
8
11
  return path.resolve(__dirname, "..");
@@ -58,6 +61,7 @@ async function ensureDaemonRunning(projectRoot) {
58
61
  // eslint-disable-next-line no-await-in-loop
59
62
  await new Promise((r) => setTimeout(r, 200));
60
63
  }
64
+ throw new Error("Failed to start ufoo daemon");
61
65
  }
62
66
 
63
67
  async function sendDaemonRequest(projectRoot, payload) {
@@ -125,6 +129,70 @@ function requireOptional(name) {
125
129
  }
126
130
  }
127
131
 
132
+ function collectOption(value, previous) {
133
+ const next = Array.isArray(previous) ? previous.slice() : [];
134
+ const parts = String(value || "")
135
+ .split(",")
136
+ .map((part) => part.trim())
137
+ .filter(Boolean);
138
+ return next.concat(parts);
139
+ }
140
+
141
+ function collectOptionValues(argv, name) {
142
+ const values = [];
143
+ for (let i = 0; i < argv.length; i += 1) {
144
+ if (argv[i] !== name) continue;
145
+ const value = argv[i + 1];
146
+ if (!value || value.startsWith("--")) continue;
147
+ values.push(value);
148
+ i += 1;
149
+ }
150
+ return values;
151
+ }
152
+
153
+ function parseJsonObject(text, fallback = {}) {
154
+ const raw = String(text || "").trim();
155
+ if (!raw) return fallback;
156
+ const parsed = JSON.parse(raw);
157
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
158
+ throw new Error("Expected JSON object");
159
+ }
160
+ return parsed;
161
+ }
162
+
163
+ function normalizeReportPhase(action = "") {
164
+ const value = String(action || "").trim().toLowerCase();
165
+ if (value === "start") return "start";
166
+ if (value === "progress") return "progress";
167
+ if (value === "error" || value === "fail" || value === "failed") return "error";
168
+ if (value === "done" || value === "finish" || value === "finished") return "done";
169
+ return "";
170
+ }
171
+
172
+ function resolveOnlineAuthToken(opts) {
173
+ if (!opts) return "";
174
+ if (opts.authToken) return opts.authToken;
175
+ let tokens = null;
176
+ try {
177
+ // eslint-disable-next-line global-require
178
+ tokens = require("./online/tokens");
179
+ } catch {
180
+ return "";
181
+ }
182
+ const filePath = opts.tokenFile || tokens.defaultTokensPath();
183
+ let entry = null;
184
+ if (opts.subscriber) entry = tokens.getToken(filePath, opts.subscriber);
185
+ if (!entry && opts.nickname) entry = tokens.getTokenByNickname(filePath, opts.nickname);
186
+ if (!entry) return "";
187
+ return entry.token_hash || entry.token || "";
188
+ }
189
+
190
+ function onlineAuthHeaders(opts) {
191
+ const token = resolveOnlineAuthToken(opts);
192
+ if (!token) return {};
193
+ return { Authorization: `Bearer ${token}` };
194
+ }
195
+
128
196
  async function runCli(argv) {
129
197
  const pkg = require(path.resolve(getPackageRoot(), "package.json"));
130
198
 
@@ -204,6 +272,208 @@ async function runCli(argv) {
204
272
  process.exitCode = 1;
205
273
  }
206
274
  });
275
+ program
276
+ .command("recover")
277
+ .description("List recoverable agents or recover a specific one")
278
+ .argument("[action]", "list|run", "list")
279
+ .argument("[target]", "Nickname or subscriber ID")
280
+ .option("--json", "Output recoverable list as JSON")
281
+ .action(async (action, target, opts) => {
282
+ try {
283
+ const projectRoot = process.cwd();
284
+ await ensureDaemonRunning(projectRoot);
285
+ const normalizedAction = (action || "list").toLowerCase();
286
+
287
+ if (normalizedAction === "list") {
288
+ const resp = await sendDaemonRequest(projectRoot, {
289
+ type: "list_recoverable_agents",
290
+ target: target || "",
291
+ });
292
+ const result = resp?.data?.recoverable || { recoverable: [], skipped: [] };
293
+ if (opts.json) {
294
+ console.log(JSON.stringify(result, null, 2));
295
+ return;
296
+ }
297
+
298
+ const recoverable = result.recoverable || [];
299
+ console.log(resp?.data?.reply || `Found ${recoverable.length} recoverable agent(s)`);
300
+ recoverable.forEach((item) => {
301
+ const nickname = item.nickname ? ` (${item.nickname})` : "";
302
+ const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
303
+ console.log(` - ${item.id}${nickname}${meta}`);
304
+ });
305
+ return;
306
+ }
307
+
308
+ if (normalizedAction === "run") {
309
+ if (!target) {
310
+ console.error("recover run requires <target>");
311
+ process.exitCode = 1;
312
+ return;
313
+ }
314
+ const resp = await sendDaemonRequest(projectRoot, {
315
+ type: "resume_agents",
316
+ target,
317
+ });
318
+ const reply = resp?.data?.reply || "Recover requested";
319
+ console.log(reply);
320
+ if (resp?.data?.resume?.resumed?.length) {
321
+ resp.data.resume.resumed.forEach((item) => {
322
+ const label = item.nickname ? ` (${item.nickname})` : "";
323
+ console.log(` - ${item.id}${label}`);
324
+ });
325
+ }
326
+ return;
327
+ }
328
+
329
+ console.error("recover action must be list|run");
330
+ process.exitCode = 1;
331
+ } catch (err) {
332
+ console.error(err.message || String(err));
333
+ process.exitCode = 1;
334
+ }
335
+ });
336
+
337
+ program
338
+ .command("report")
339
+ .description("Report agent task status to daemon/ufoo-agent")
340
+ .argument("<action>", "start|progress|done|error|list")
341
+ .argument("[message...]", "Task message/summary")
342
+ .option("--task <id>", "Task ID (default: task-<timestamp>)")
343
+ .option("--agent <id>", "Agent ID (default: UFOO_SUBSCRIBER_ID)")
344
+ .option("--scope <scope>", "Report visibility: public|private", "public")
345
+ .option("--controller <id>", "Controller ID for private channel", "ufoo-agent")
346
+ .option("--summary <text>", "Summary text for done")
347
+ .option("--error <text>", "Error text for error")
348
+ .option("--meta <json>", "JSON metadata object")
349
+ .option("-n, --num <n>", "List count", "20")
350
+ .option("--json", "Output as JSON")
351
+ .action(async (action, messageParts, opts) => {
352
+ const normalized = normalizeReportPhase(action);
353
+ const text = Array.isArray(messageParts) ? messageParts.join(" ").trim() : String(messageParts || "").trim();
354
+ const projectRoot = process.cwd();
355
+ const { listReports } = require("./report/store");
356
+
357
+ if ((action || "").toLowerCase() === "list") {
358
+ try {
359
+ const rows = listReports(projectRoot, {
360
+ num: parseInt(opts.num, 10),
361
+ agent: opts.agent || "",
362
+ });
363
+ if (opts.json) {
364
+ console.log(JSON.stringify(rows, null, 2));
365
+ return;
366
+ }
367
+ console.log(`=== Reports (${rows.length} shown) ===`);
368
+ rows.forEach((row) => {
369
+ const detail = row.phase === "error"
370
+ ? (row.error || row.summary || row.message || row.task_id)
371
+ : (row.summary || row.message || row.task_id);
372
+ console.log(`${row.ts || "-"} [${row.phase}] ${row.agent_id || "unknown-agent"} ${row.task_id || ""} ${detail}`);
373
+ });
374
+ if (rows.length === 0) console.log("No reports found.");
375
+ } catch (err) {
376
+ console.error(err.message || String(err));
377
+ process.exitCode = 1;
378
+ }
379
+ return;
380
+ }
381
+
382
+ if (!normalized) {
383
+ console.error("report action must be start|progress|done|error|list");
384
+ process.exitCode = 1;
385
+ return;
386
+ }
387
+
388
+ let meta = {};
389
+ try {
390
+ meta = parseJsonObject(opts.meta, {});
391
+ } catch (err) {
392
+ console.error(`Invalid --meta: ${err.message}`);
393
+ process.exitCode = 1;
394
+ return;
395
+ }
396
+
397
+ const agentId = String(opts.agent || process.env.UFOO_SUBSCRIBER_ID || "unknown-agent").trim() || "unknown-agent";
398
+ const taskId = String(opts.task || `task-${Date.now()}`).trim();
399
+ const summary = String(opts.summary || (normalized === "done" ? text : "")).trim();
400
+ const error = String(opts.error || (normalized === "error" ? text : "")).trim();
401
+ const report = {
402
+ phase: normalized,
403
+ task_id: taskId,
404
+ agent_id: agentId,
405
+ message: text,
406
+ summary,
407
+ error,
408
+ ok: normalized !== "error",
409
+ source: "cli",
410
+ scope: opts.scope || "public",
411
+ controller_id: opts.controller || "ufoo-agent",
412
+ meta,
413
+ };
414
+
415
+ try {
416
+ await ensureDaemonRunning(projectRoot);
417
+ const resp = await sendDaemonRequest(projectRoot, {
418
+ type: "agent_report",
419
+ report,
420
+ });
421
+ const out = resp?.data?.report || report;
422
+ if (opts.json) {
423
+ console.log(JSON.stringify(out, null, 2));
424
+ return;
425
+ }
426
+ const detail = out.phase === "error"
427
+ ? (out.error || out.summary || out.message || out.task_id)
428
+ : (out.summary || out.message || out.task_id);
429
+ console.log(`[report] ${out.phase} ${out.agent_id} ${out.task_id} ${detail}`);
430
+ } catch (err) {
431
+ console.error(err.message || String(err));
432
+ process.exitCode = 1;
433
+ }
434
+ });
435
+
436
+ program
437
+ .command("ucode")
438
+ .description("ucode core preparation helpers")
439
+ .argument("[action]", "doctor|prepare|build", "doctor")
440
+ .option("--skip-install", "Skip npm install even if node_modules is missing")
441
+ .action((action, opts) => {
442
+ const { inspectUcodeSetup, formatUcodeDoctor, prepareAndInspectUcode } = require("./agent/ucodeDoctor");
443
+ const { buildUcodeCore } = require("./agent/ucodeBuild");
444
+ const normalized = String(action || "doctor").trim().toLowerCase();
445
+ if (normalized !== "doctor" && normalized !== "prepare" && normalized !== "build") {
446
+ console.error("ucode action must be doctor|prepare|build");
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ if (normalized === "build") {
451
+ try {
452
+ const built = buildUcodeCore({
453
+ projectRoot: process.cwd(),
454
+ installIfMissing: !opts.skipInstall,
455
+ stdio: "inherit",
456
+ });
457
+ console.log("=== ucode build ===");
458
+ console.log(`workspace: ${built.workspaceRoot}`);
459
+ console.log(`core: ${built.coreRoot}`);
460
+ console.log(`dist: ${built.distCliPath}`);
461
+ console.log(`steps: ${built.steps.join(", ")}`);
462
+ return;
463
+ } catch (err) {
464
+ console.error(err.message || String(err));
465
+ process.exitCode = 1;
466
+ return;
467
+ }
468
+ }
469
+ const result = normalized === "prepare"
470
+ ? prepareAndInspectUcode({ projectRoot: process.cwd() })
471
+ : inspectUcodeSetup({ projectRoot: process.cwd() });
472
+ console.log(formatUcodeDoctor(result));
473
+ if (normalized === "prepare" && result.bootstrapPrepared && result.bootstrapPrepared.file) {
474
+ console.log(`prepared bootstrap: ${result.bootstrapPrepared.file}`);
475
+ }
476
+ });
207
477
 
208
478
  program
209
479
  .command("init")
@@ -252,6 +522,168 @@ async function runCli(argv) {
252
522
  }
253
523
  });
254
524
 
525
+ const online = program.command("online").description("ufoo online helpers");
526
+ online
527
+ .command("server")
528
+ .description("Start ufoo-online relay server")
529
+ .option("--port <port>", "Listen port", "8787")
530
+ .option("--host <host>", "Listen host", "127.0.0.1")
531
+ .option("--token-file <path>", "Token file for auth validation")
532
+ .option("--idle-timeout <ms>", "Idle timeout in ms", "30000")
533
+ .option("--insecure", "Allow any token (dev only)")
534
+ .option("--tls-cert <path>", "TLS certificate file")
535
+ .option("--tls-key <path>", "TLS private key file")
536
+ .action(async (opts) => {
537
+ try {
538
+ await runOnlineCommand("server", { opts }, {
539
+ mode: "commander",
540
+ onlineAuthHeaders,
541
+ projectRoot: process.cwd(),
542
+ });
543
+ } catch (err) {
544
+ console.error(err.message || String(err));
545
+ process.exitCode = 1;
546
+ }
547
+ });
548
+ online
549
+ .command("token")
550
+ .description("Generate and store a ufoo-online token")
551
+ .argument("<subscriber>", "Subscriber ID (e.g., claude-code:abc123)")
552
+ .option("--nickname <name>", "Nickname for this agent")
553
+ .option("--server <url>", "Online server URL")
554
+ .option("--file <path>", "Tokens file path")
555
+ .action(async (subscriber, opts) => {
556
+ try {
557
+ await runOnlineCommand("token", { subscriber, opts }, {
558
+ mode: "commander",
559
+ onlineAuthHeaders,
560
+ projectRoot: process.cwd(),
561
+ });
562
+ } catch (err) {
563
+ console.error(err.message || String(err));
564
+ process.exitCode = 1;
565
+ }
566
+ });
567
+
568
+ online
569
+ .command("room")
570
+ .description("Manage online rooms (HTTP)")
571
+ .argument("<action>", "create|list")
572
+ .option("--server <url>", "Online server base URL (http://host:port)")
573
+ .option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
574
+ .option("--token-file <path>", "Token file path for auth lookup")
575
+ .option("--subscriber <id>", "Subscriber ID to resolve token")
576
+ .option("--nickname <name>", "Nickname to resolve token")
577
+ .option("--name <room>", "Room name (optional)")
578
+ .option("--type <type>", "Room type (public|private)")
579
+ .option("--password <pwd>", "Room password (private only)")
580
+ .action(async (action, opts) => {
581
+ try {
582
+ await runOnlineCommand("room", { action, opts }, {
583
+ mode: "commander",
584
+ onlineAuthHeaders,
585
+ projectRoot: process.cwd(),
586
+ });
587
+ } catch (err) {
588
+ console.error(err.message || String(err));
589
+ process.exitCode = 1;
590
+ }
591
+ });
592
+
593
+ online
594
+ .command("channel")
595
+ .description("Manage online channels (HTTP)")
596
+ .argument("<action>", "create|list")
597
+ .option("--server <url>", "Online server base URL (http://host:port)")
598
+ .option("--auth-token <token>", "Bearer token for HTTP auth (token or token_hash)")
599
+ .option("--token-file <path>", "Token file path for auth lookup")
600
+ .option("--subscriber <id>", "Subscriber ID to resolve token")
601
+ .option("--nickname <name>", "Nickname to resolve token")
602
+ .option("--name <name>", "Channel name (unique)")
603
+ .option("--type <type>", "Channel type (world|public)")
604
+ .action(async (action, opts) => {
605
+ try {
606
+ await runOnlineCommand("channel", { action, opts }, {
607
+ mode: "commander",
608
+ onlineAuthHeaders,
609
+ projectRoot: process.cwd(),
610
+ });
611
+ } catch (err) {
612
+ console.error(err.message || String(err));
613
+ process.exitCode = 1;
614
+ }
615
+ });
616
+
617
+ online
618
+ .command("connect")
619
+ .description("Connect to ufoo-online relay (long-running)")
620
+ .requiredOption("--nickname <name>", "Agent nickname")
621
+ .option("--url <url>", "WebSocket URL", "ws://127.0.0.1:8787/ufoo/online")
622
+ .option("--subscriber <id>", "Subscriber ID (auto-generated if omitted)")
623
+ .option("--token <tok>", "Auth token")
624
+ .option("--token-hash <hash>", "Auth token hash")
625
+ .option("--token-file <path>", "Token file path")
626
+ .option("--world <name>", "World name", "default")
627
+ .option("--ping-ms <ms>", "Keepalive ping interval (ms)")
628
+ .option("--join <channel>", "Join channel after connect")
629
+ .option("--room <room>", "Join private room (enables bus/decisions/wake sync)")
630
+ .option("--room-password <pwd>", "Room password")
631
+ .option("--interval <ms>", "Bus sync poll interval in ms", "1500")
632
+ .option("--allow-insecure-ws", "Allow ws:// to non-localhost (insecure)")
633
+ .option("--trust-remote", "Trust all private-room members for bus/decisions/wake sync")
634
+ .option("--allow-from <subscriberId>", "Allow bus/decisions/wake from subscriber ID (repeatable)", collectOption)
635
+ .action(async (opts) => {
636
+ try {
637
+ await runOnlineCommand("connect", { opts }, {
638
+ mode: "commander",
639
+ onlineAuthHeaders,
640
+ projectRoot: process.cwd(),
641
+ });
642
+ } catch (err) {
643
+ console.error(err.message || String(err));
644
+ process.exitCode = 1;
645
+ }
646
+ });
647
+
648
+ online
649
+ .command("send")
650
+ .description("Send a message to a channel or room via outbox")
651
+ .requiredOption("--nickname <name>", "Agent nickname (must match a running connect)")
652
+ .requiredOption("--text <message>", "Message text")
653
+ .option("--channel <name>", "Target channel")
654
+ .option("--room <id>", "Target room")
655
+ .action(async (opts) => {
656
+ try {
657
+ await runOnlineCommand("send", { opts }, {
658
+ mode: "commander",
659
+ onlineAuthHeaders,
660
+ projectRoot: process.cwd(),
661
+ });
662
+ } catch (err) {
663
+ console.error(err.message || String(err));
664
+ process.exitCode = 1;
665
+ }
666
+ });
667
+
668
+ online
669
+ .command("inbox")
670
+ .description("View ufoo-online inbox for a nickname")
671
+ .argument("<nickname>", "Agent nickname")
672
+ .option("--clear", "Clear the inbox")
673
+ .option("--unread", "Show unread messages only")
674
+ .action(async (nickname, opts) => {
675
+ try {
676
+ await runOnlineCommand("inbox", { nickname, opts }, {
677
+ mode: "commander",
678
+ onlineAuthHeaders,
679
+ projectRoot: process.cwd(),
680
+ });
681
+ } catch (err) {
682
+ console.error(err.message || String(err));
683
+ process.exitCode = 1;
684
+ }
685
+ });
686
+
255
687
  const bus = program.command("bus").description("Project bus commands");
256
688
  bus
257
689
  .command("alert")
@@ -330,7 +762,7 @@ async function runCli(argv) {
330
762
  });
331
763
  bus
332
764
  .command("inject")
333
- .description("Inject /ubus command into agent's terminal (via PTY socket or tmux)")
765
+ .description("Inject /bus into a Terminal.app tab by subscriber ID")
334
766
  .argument("<subscriber>", "Subscriber ID to inject into")
335
767
  .action((subscriber) => {
336
768
  const EventBus = require("./bus");
@@ -344,6 +776,24 @@ async function runCli(argv) {
344
776
  }
345
777
  })();
346
778
  });
779
+ bus
780
+ .command("wake")
781
+ .description("Wake an agent (inject /ubus into its terminal)")
782
+ .argument("<target>", "Subscriber ID or nickname")
783
+ .option("--reason <reason>", "Wake reason")
784
+ .option("--no-shake", "Disable window shake")
785
+ .action((target, opts) => {
786
+ const EventBus = require("./bus");
787
+ const eventBus = new EventBus(process.cwd());
788
+ (async () => {
789
+ try {
790
+ await eventBus.wake(target, { reason: opts.reason || "remote", shake: opts.shake !== false });
791
+ } catch (err) {
792
+ console.error(err.message || String(err));
793
+ process.exitCode = 1;
794
+ }
795
+ })();
796
+ });
347
797
  bus
348
798
  .command("activate")
349
799
  .description("Activate (focus) the terminal/tmux window of an agent")
@@ -372,58 +822,8 @@ async function runCli(argv) {
372
822
  const cmdArgs = args.slice(1);
373
823
 
374
824
  try {
375
- switch (cmd) {
376
- case "init":
377
- await eventBus.init();
378
- break;
379
- case "join":
380
- {
381
- const subscriber = await eventBus.join(cmdArgs[0], cmdArgs[1], cmdArgs[2]);
382
- if (subscriber) console.log(subscriber);
383
- }
384
- break;
385
- case "leave":
386
- await eventBus.leave(cmdArgs[0]);
387
- break;
388
- case "send":
389
- {
390
- // 自动 join(如果还没有 join)并获取 subscriber ID
391
- const publisher = await eventBus.ensureJoined();
392
- await eventBus.send(cmdArgs[0], cmdArgs[1], publisher);
393
- }
394
- break;
395
- case "broadcast":
396
- {
397
- // 自动 join(如果还没有 join)并获取 subscriber ID
398
- const publisher = await eventBus.ensureJoined();
399
- await eventBus.broadcast(cmdArgs[0], publisher);
400
- }
401
- break;
402
- case "check":
403
- await eventBus.check(cmdArgs[0]);
404
- break;
405
- case "ack":
406
- await eventBus.ack(cmdArgs[0]);
407
- break;
408
- case "consume":
409
- await eventBus.consume(cmdArgs[0], cmdArgs.includes("--from-beginning"));
410
- break;
411
- case "status":
412
- await eventBus.status();
413
- break;
414
- case "resolve":
415
- await eventBus.resolve(cmdArgs[0], cmdArgs[1]);
416
- break;
417
- case "rename":
418
- await eventBus.rename(cmdArgs[0], cmdArgs[1]);
419
- break;
420
- case "whoami":
421
- await eventBus.whoami();
422
- break;
423
- default:
424
- console.error(`Unknown bus subcommand: ${sub}`);
425
- process.exitCode = 1;
426
- }
825
+ const result = await runBusCoreCommand(eventBus, cmd, cmdArgs);
826
+ if (result && result.subscriber) console.log(result.subscriber);
427
827
  } catch (err) {
428
828
  console.error(err.message);
429
829
  process.exitCode = 1;
@@ -432,98 +832,25 @@ async function runCli(argv) {
432
832
 
433
833
  program
434
834
  .command("ctx")
435
- .description("Project context commands (doctor|lint|decisions)")
436
- .argument("[subcmd]", "Subcommand (doctor|lint|decisions)", "doctor")
835
+ .description("Project context commands (doctor|lint|decisions|sync)")
836
+ .argument("[subcmd]", "Subcommand (doctor|lint|decisions|sync)", "doctor")
437
837
  .allowUnknownOption(true)
438
838
  .argument("[subargs...]", "Subcommand args")
439
839
  .action(async (subcmd, subargs = []) => {
440
- const DecisionsManager = require("./context/decisions");
441
- const ContextDoctor = require("./context/doctor");
442
840
  const cwd = process.cwd();
443
841
 
444
842
  try {
445
- switch (subcmd) {
446
- case "doctor": {
447
- const doctor = new ContextDoctor(cwd);
448
- const mode = subargs.includes("--project") ? "project" : "protocol";
449
- const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
450
- await doctor.run({ mode, projectPath });
451
- break;
452
- }
453
- case "lint": {
454
- const doctor = new ContextDoctor(cwd);
455
- const mode = subargs.includes("--project") ? "project" : "protocol";
456
- const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
457
- if (mode === "project") {
458
- doctor.lintProject(projectPath);
459
- } else {
460
- doctor.lintProtocol();
461
- }
462
- break;
463
- }
464
- case "decisions": {
465
- const manager = new DecisionsManager(cwd);
466
- const opts = {};
467
-
468
- if (subargs[0] === "index" || subargs.includes("--index")) {
469
- manager.writeIndex();
470
- break;
471
- }
472
- if (subargs[0] === "new") {
473
- const create = { title: "", author: "", status: "" };
474
- for (let i = 1; i < subargs.length; i++) {
475
- const arg = subargs[i];
476
- if (arg === "--author") {
477
- create.author = subargs[++i] || "";
478
- continue;
479
- }
480
- if (arg === "--status") {
481
- create.status = subargs[++i] || "";
482
- continue;
483
- }
484
- if (!arg.startsWith("-")) {
485
- create.title = create.title ? `${create.title} ${arg}` : arg;
486
- continue;
487
- }
488
- }
489
- manager.createDecision(create);
490
- break;
491
- }
492
-
493
- // Parse options
494
- for (let i = 0; i < subargs.length; i++) {
495
- if (subargs[i] === "-n") opts.num = parseInt(subargs[++i]);
496
- if (subargs[i] === "-s") opts.status = subargs[++i];
497
- if (subargs[i] === "-l") opts.listOnly = true;
498
- if (subargs[i] === "-a") opts.all = true;
499
- if (subargs[i] === "-d") {
500
- manager.decisionsDir = subargs[++i];
501
- manager.contextDir = path.dirname(manager.decisionsDir);
502
- manager.indexFile = path.join(manager.contextDir, "decisions.jsonl");
503
- }
504
- }
505
-
506
- if (opts.listOnly) {
507
- manager.list({ status: opts.status || "open" });
508
- } else {
509
- manager.show({
510
- status: opts.status || "open",
511
- num: opts.num || 1,
512
- all: opts.all || false,
513
- });
514
- }
515
- break;
516
- }
517
- default:
518
- console.error(
519
- chalk.red(
520
- `Unknown ctx subcommand: ${subcmd}. Supported: doctor, lint, decisions`
521
- )
522
- );
523
- process.exitCode = 1;
524
- }
843
+ await runCtxCommand(subcmd, subargs, {
844
+ cwd,
845
+ allowIndexNew: true,
846
+ updateDecisionIndexPaths: true,
847
+ });
525
848
  } catch (err) {
526
- console.error(chalk.red(`Error: ${err.message}`));
849
+ if (err && err.code === "UFOO_CTX_UNKNOWN") {
850
+ console.error(chalk.red(err.message));
851
+ } else {
852
+ console.error(chalk.red(`Error: ${err.message}`));
853
+ }
527
854
  process.exitCode = 1;
528
855
  }
529
856
  });
@@ -562,11 +889,24 @@ async function runCli(argv) {
562
889
  console.log(" ufoo daemon --start|--stop|--status");
563
890
  console.log(" ufoo chat");
564
891
  console.log(" ufoo resume [nickname]");
892
+ console.log(" ufoo recover [list [target] | run <target>] [--json]");
893
+ console.log(" ufoo report <start|progress|done|error|list> [message] [--task <id>] [--agent <id>]");
894
+ console.log(" ufoo ucode [doctor|prepare|build] [--skip-install]");
565
895
  console.log(" ufoo init [--modules <list>] [--project <dir>]");
566
896
  console.log(" ufoo skills list");
567
897
  console.log(" ufoo skills install <name|all> [--target <dir> | --codex | --agents]");
898
+ console.log(" ufoo online server [--port 8787] [--host 127.0.0.1] [--token-file <path>]");
899
+ console.log(" ufoo online token <subscriber> [--nickname <name>] [--server <url>] [--file <path>]");
900
+ console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--server <url>]");
901
+ console.log(" ufoo online room list [--server <url>]");
902
+ console.log(" ufoo online channel create --name <name> [--type world|public] [--server <url>]");
903
+ console.log(" ufoo online channel list [--server <url>]");
904
+ console.log(" ufoo online connect --nickname <name> [--join <ch>] [--room <id> --room-password <pwd>] [...]");
905
+ console.log(" ufoo online send --nickname <name> --text <msg> [--channel <ch>] [--room <id>]");
906
+ console.log(" ufoo online inbox <nickname> [--clear] [--unread]");
907
+ console.log(" ufoo bus wake <target> [--reason <reason>] [--no-shake]");
568
908
  console.log(" ufoo bus <args...> (JS bus implementation)");
569
- console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions)");
909
+ console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions|sync)");
570
910
  console.log("");
571
911
  console.log("Notes:");
572
912
  console.log(" - For Codex notifications, use ufoo bus alert / ufoo bus listen");
@@ -626,6 +966,214 @@ async function runCli(argv) {
626
966
  })();
627
967
  return;
628
968
  }
969
+ if (cmd === "recover") {
970
+ const first = rest[0] || "";
971
+ const action = first && !first.startsWith("--") ? first.toLowerCase() : "list";
972
+ const targetIdx = first && !first.startsWith("--") ? 1 : 0;
973
+ const target = rest[targetIdx] && !rest[targetIdx].startsWith("--") ? rest[targetIdx] : "";
974
+ const outputJson = rest.includes("--json");
975
+ (async () => {
976
+ try {
977
+ const projectRoot = process.cwd();
978
+ await ensureDaemonRunning(projectRoot);
979
+
980
+ if (action === "list") {
981
+ const resp = await sendDaemonRequest(projectRoot, {
982
+ type: "list_recoverable_agents",
983
+ target,
984
+ });
985
+ const result = resp?.data?.recoverable || { recoverable: [], skipped: [] };
986
+ if (outputJson) {
987
+ console.log(JSON.stringify(result, null, 2));
988
+ return;
989
+ }
990
+ const recoverable = result.recoverable || [];
991
+ console.log(resp?.data?.reply || `Found ${recoverable.length} recoverable agent(s)`);
992
+ recoverable.forEach((item) => {
993
+ const nickname = item.nickname ? ` (${item.nickname})` : "";
994
+ const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
995
+ console.log(` - ${item.id}${nickname}${meta}`);
996
+ });
997
+ return;
998
+ }
999
+
1000
+ if (action === "run") {
1001
+ if (!target) {
1002
+ console.error("recover run requires <target>");
1003
+ process.exitCode = 1;
1004
+ return;
1005
+ }
1006
+ const resp = await sendDaemonRequest(projectRoot, {
1007
+ type: "resume_agents",
1008
+ target,
1009
+ });
1010
+ const reply = resp?.data?.reply || "Recover requested";
1011
+ console.log(reply);
1012
+ return;
1013
+ }
1014
+
1015
+ console.error("recover action must be list|run");
1016
+ process.exitCode = 1;
1017
+ } catch (err) {
1018
+ console.error(err.message || String(err));
1019
+ process.exitCode = 1;
1020
+ }
1021
+ })();
1022
+ return;
1023
+ }
1024
+ if (cmd === "report") {
1025
+ const action = String(rest[0] || "").toLowerCase();
1026
+ const normalized = normalizeReportPhase(action);
1027
+ const { listReports } = require("./report/store");
1028
+
1029
+ if (action === "list") {
1030
+ const agentIdx = rest.indexOf("--agent");
1031
+ const numIdx = rest.indexOf("--num");
1032
+ const nIdx = rest.indexOf("-n");
1033
+ const json = rest.includes("--json");
1034
+ const agent = agentIdx !== -1 ? (rest[agentIdx + 1] || "") : "";
1035
+ const numRaw = numIdx !== -1
1036
+ ? rest[numIdx + 1]
1037
+ : (nIdx !== -1 ? rest[nIdx + 1] : "20");
1038
+ try {
1039
+ const rows = listReports(process.cwd(), { agent, num: parseInt(numRaw, 10) });
1040
+ if (json) {
1041
+ console.log(JSON.stringify(rows, null, 2));
1042
+ return;
1043
+ }
1044
+ console.log(`=== Reports (${rows.length} shown) ===`);
1045
+ rows.forEach((row) => {
1046
+ const detail = row.phase === "error"
1047
+ ? (row.error || row.summary || row.message || row.task_id)
1048
+ : (row.summary || row.message || row.task_id);
1049
+ console.log(`${row.ts || "-"} [${row.phase}] ${row.agent_id || "unknown-agent"} ${row.task_id || ""} ${detail}`);
1050
+ });
1051
+ if (rows.length === 0) console.log("No reports found.");
1052
+ } catch (err) {
1053
+ console.error(err.message || String(err));
1054
+ process.exitCode = 1;
1055
+ }
1056
+ return;
1057
+ }
1058
+
1059
+ if (!normalized) {
1060
+ console.error("report action must be start|progress|done|error|list");
1061
+ process.exitCode = 1;
1062
+ return;
1063
+ }
1064
+
1065
+ const getOpt = (name, fallback = "") => {
1066
+ const idx = rest.indexOf(name);
1067
+ if (idx === -1 || idx + 1 >= rest.length) return fallback;
1068
+ const value = rest[idx + 1];
1069
+ if (!value || value.startsWith("--")) return fallback;
1070
+ return value;
1071
+ };
1072
+
1073
+ const isValueToken = (token) =>
1074
+ token && !token.startsWith("--") && token !== action;
1075
+ const message = rest.slice(1).filter((token, idx, arr) => {
1076
+ if (!isValueToken(token)) return false;
1077
+ const prev = arr[idx - 1] || "";
1078
+ if (
1079
+ prev === "--task"
1080
+ || prev === "--agent"
1081
+ || prev === "--scope"
1082
+ || prev === "--controller"
1083
+ || prev === "--summary"
1084
+ || prev === "--error"
1085
+ || prev === "--meta"
1086
+ || prev === "--num"
1087
+ || prev === "-n"
1088
+ ) {
1089
+ return false;
1090
+ }
1091
+ return true;
1092
+ }).join(" ").trim();
1093
+
1094
+ let meta = {};
1095
+ try {
1096
+ meta = parseJsonObject(getOpt("--meta", ""), {});
1097
+ } catch (err) {
1098
+ console.error(`Invalid --meta: ${err.message}`);
1099
+ process.exitCode = 1;
1100
+ return;
1101
+ }
1102
+
1103
+ const report = {
1104
+ phase: normalized,
1105
+ task_id: getOpt("--task", `task-${Date.now()}`),
1106
+ agent_id: getOpt("--agent", process.env.UFOO_SUBSCRIBER_ID || "unknown-agent"),
1107
+ message,
1108
+ summary: getOpt("--summary", normalized === "done" ? message : ""),
1109
+ error: getOpt("--error", normalized === "error" ? message : ""),
1110
+ ok: normalized !== "error",
1111
+ source: "cli",
1112
+ scope: getOpt("--scope", "public"),
1113
+ controller_id: getOpt("--controller", "ufoo-agent"),
1114
+ meta,
1115
+ };
1116
+
1117
+ (async () => {
1118
+ try {
1119
+ await ensureDaemonRunning(process.cwd());
1120
+ const resp = await sendDaemonRequest(process.cwd(), {
1121
+ type: "agent_report",
1122
+ report,
1123
+ });
1124
+ const out = resp?.data?.report || report;
1125
+ if (rest.includes("--json")) {
1126
+ console.log(JSON.stringify(out, null, 2));
1127
+ return;
1128
+ }
1129
+ const detail = out.phase === "error"
1130
+ ? (out.error || out.summary || out.message || out.task_id)
1131
+ : (out.summary || out.message || out.task_id);
1132
+ console.log(`[report] ${out.phase} ${out.agent_id} ${out.task_id} ${detail}`);
1133
+ } catch (err) {
1134
+ console.error(err.message || String(err));
1135
+ process.exitCode = 1;
1136
+ }
1137
+ })();
1138
+ return;
1139
+ }
1140
+ if (cmd === "ucode") {
1141
+ const action = String(rest[0] || "doctor").trim().toLowerCase();
1142
+ const { inspectUcodeSetup, formatUcodeDoctor, prepareAndInspectUcode } = require("./agent/ucodeDoctor");
1143
+ const { buildUcodeCore } = require("./agent/ucodeBuild");
1144
+ const skipInstall = rest.includes("--skip-install");
1145
+ if (action !== "doctor" && action !== "prepare" && action !== "build") {
1146
+ console.error("ucode action must be doctor|prepare|build");
1147
+ process.exitCode = 1;
1148
+ return;
1149
+ }
1150
+ if (action === "build") {
1151
+ try {
1152
+ const built = buildUcodeCore({
1153
+ projectRoot: process.cwd(),
1154
+ installIfMissing: !skipInstall,
1155
+ stdio: "inherit",
1156
+ });
1157
+ console.log("=== ucode build ===");
1158
+ console.log(`workspace: ${built.workspaceRoot}`);
1159
+ console.log(`core: ${built.coreRoot}`);
1160
+ console.log(`dist: ${built.distCliPath}`);
1161
+ console.log(`steps: ${built.steps.join(", ")}`);
1162
+ } catch (err) {
1163
+ console.error(err.message || String(err));
1164
+ process.exitCode = 1;
1165
+ }
1166
+ return;
1167
+ }
1168
+ const result = action === "prepare"
1169
+ ? prepareAndInspectUcode({ projectRoot: process.cwd() })
1170
+ : inspectUcodeSetup({ projectRoot: process.cwd() });
1171
+ console.log(formatUcodeDoctor(result));
1172
+ if (action === "prepare" && result.bootstrapPrepared && result.bootstrapPrepared.file) {
1173
+ console.log(`prepared bootstrap: ${result.bootstrapPrepared.file}`);
1174
+ }
1175
+ return;
1176
+ }
629
1177
  if (cmd === "init") {
630
1178
  const UfooInit = require("./init");
631
1179
  const init = new UfooInit(repoRoot);
@@ -684,6 +1232,35 @@ async function runCli(argv) {
684
1232
  process.exitCode = 1;
685
1233
  return;
686
1234
  }
1235
+ if (cmd === "online") {
1236
+ const sub = rest[0] || "";
1237
+ if (!sub) {
1238
+ help();
1239
+ process.exitCode = 1;
1240
+ return;
1241
+ }
1242
+
1243
+ (async () => {
1244
+ try {
1245
+ await runOnlineCommand(sub, { argv: rest }, {
1246
+ mode: "fallback",
1247
+ onlineAuthHeaders,
1248
+ projectRoot: process.cwd(),
1249
+ collectOptionValues,
1250
+ collectOption,
1251
+ defaultChannelType: "public",
1252
+ });
1253
+ } catch (err) {
1254
+ if (err && err.code === "UFOO_ONLINE_UNKNOWN") {
1255
+ help();
1256
+ } else {
1257
+ console.error(err.message || String(err));
1258
+ }
1259
+ process.exitCode = 1;
1260
+ }
1261
+ })();
1262
+ return;
1263
+ }
687
1264
  if (cmd === "bus") {
688
1265
  const sub = rest[0] || "";
689
1266
  if (sub === "alert") {
@@ -776,6 +1353,24 @@ async function runCli(argv) {
776
1353
  })();
777
1354
  return;
778
1355
  }
1356
+ if (sub === "wake") {
1357
+ const EventBus = require("./bus");
1358
+ const eventBus = new EventBus(process.cwd());
1359
+ (async () => {
1360
+ try {
1361
+ const target = rest[1];
1362
+ if (!target) throw new Error("wake requires <subscriber-id|nickname>");
1363
+ const reasonIdx = rest.indexOf("--reason");
1364
+ const reason = reasonIdx !== -1 ? rest[reasonIdx + 1] : "remote";
1365
+ const shake = !rest.includes("--no-shake");
1366
+ await eventBus.wake(target, { reason, shake });
1367
+ } catch (err) {
1368
+ console.error(err.message || String(err));
1369
+ process.exitCode = 1;
1370
+ }
1371
+ })();
1372
+ return;
1373
+ }
779
1374
 
780
1375
  // Use JavaScript EventBus module for core commands
781
1376
  const EventBus = require("./bus");
@@ -784,58 +1379,8 @@ async function runCli(argv) {
784
1379
  (async () => {
785
1380
  try {
786
1381
  const cmdArgs = rest.slice(1);
787
- switch (sub) {
788
- case "init":
789
- await eventBus.init();
790
- break;
791
- case "join":
792
- {
793
- const subscriber = await eventBus.join(cmdArgs[0], cmdArgs[1], cmdArgs[2]);
794
- if (subscriber) console.log(subscriber);
795
- }
796
- break;
797
- case "leave":
798
- await eventBus.leave(cmdArgs[0]);
799
- break;
800
- case "send":
801
- {
802
- // 自动 join(如果还没有 join)并获取 subscriber ID
803
- const publisher = await eventBus.ensureJoined();
804
- await eventBus.send(cmdArgs[0], cmdArgs[1], publisher);
805
- }
806
- break;
807
- case "broadcast":
808
- {
809
- // 自动 join(如果还没有 join)并获取 subscriber ID
810
- const publisher = await eventBus.ensureJoined();
811
- await eventBus.broadcast(cmdArgs[0], publisher);
812
- }
813
- break;
814
- case "check":
815
- await eventBus.check(cmdArgs[0]);
816
- break;
817
- case "ack":
818
- await eventBus.ack(cmdArgs[0]);
819
- break;
820
- case "consume":
821
- await eventBus.consume(cmdArgs[0], cmdArgs.includes("--from-beginning"));
822
- break;
823
- case "status":
824
- await eventBus.status();
825
- break;
826
- case "resolve":
827
- await eventBus.resolve(cmdArgs[0], cmdArgs[1]);
828
- break;
829
- case "rename":
830
- await eventBus.rename(cmdArgs[0], cmdArgs[1]);
831
- break;
832
- case "whoami":
833
- await eventBus.whoami();
834
- break;
835
- default:
836
- console.error(`Unknown bus subcommand: ${sub}`);
837
- process.exitCode = 1;
838
- }
1382
+ const result = await runBusCoreCommand(eventBus, sub, cmdArgs);
1383
+ if (result && result.subscriber) console.log(result.subscriber);
839
1384
  } catch (err) {
840
1385
  console.error(err.message);
841
1386
  process.exitCode = 1;
@@ -846,57 +1391,15 @@ async function runCli(argv) {
846
1391
  if (cmd === "ctx") {
847
1392
  const sub = rest[0] || "doctor";
848
1393
  const subargs = rest.slice(1);
849
- const DecisionsManager = require("./context/decisions");
850
- const ContextDoctor = require("./context/doctor");
851
1394
  const cwd = process.cwd();
852
1395
 
853
1396
  (async () => {
854
1397
  try {
855
- switch (sub) {
856
- case "doctor": {
857
- const doctor = new ContextDoctor(cwd);
858
- const mode = subargs.includes("--project") ? "project" : "protocol";
859
- const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
860
- await doctor.run({ mode, projectPath });
861
- break;
862
- }
863
- case "lint": {
864
- const doctor = new ContextDoctor(cwd);
865
- const mode = subargs.includes("--project") ? "project" : "protocol";
866
- const projectPath = mode === "project" ? subargs[subargs.indexOf("--project") + 1] : null;
867
- if (mode === "project") {
868
- doctor.lintProject(projectPath);
869
- } else {
870
- doctor.lintProtocol();
871
- }
872
- break;
873
- }
874
- case "decisions": {
875
- const manager = new DecisionsManager(cwd);
876
- const opts = {};
877
-
878
- for (let i = 0; i < subargs.length; i++) {
879
- if (subargs[i] === "-n") opts.num = parseInt(subargs[++i]);
880
- if (subargs[i] === "-s") opts.status = subargs[++i];
881
- if (subargs[i] === "-l") opts.listOnly = true;
882
- if (subargs[i] === "-a") opts.all = true;
883
- if (subargs[i] === "-d") manager.decisionsDir = subargs[++i];
884
- }
885
-
886
- if (opts.listOnly) {
887
- manager.list({ status: opts.status || "open" });
888
- } else {
889
- manager.show({
890
- status: opts.status || "open",
891
- num: opts.num || 1,
892
- all: opts.all || false,
893
- });
894
- }
895
- break;
896
- }
897
- default:
898
- throw new Error(`Unknown ctx subcommand: ${sub}`);
899
- }
1398
+ await runCtxCommand(sub, subargs, {
1399
+ cwd,
1400
+ allowIndexNew: false,
1401
+ updateDecisionIndexPaths: false,
1402
+ });
900
1403
  } catch (err) {
901
1404
  console.error(`Error: ${err.message}`);
902
1405
  process.exit(1);