u-foo 1.0.3 → 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 (179) hide show
  1. package/README.md +110 -11
  2. package/README.zh-CN.md +9 -7
  3. package/SKILLS/ufoo/SKILL.md +132 -0
  4. package/SKILLS/uinit/SKILL.md +78 -0
  5. package/SKILLS/ustatus/SKILL.md +36 -0
  6. package/bin/uclaude.js +13 -0
  7. package/bin/ucode-core.js +15 -0
  8. package/bin/ucode.js +125 -0
  9. package/bin/ucodex.js +13 -0
  10. package/bin/ufoo +9 -31
  11. package/bin/ufoo-assistant-agent.js +5 -0
  12. package/bin/ufoo-engine.js +25 -0
  13. package/bin/ufoo.js +17 -0
  14. package/modules/AGENTS.template.md +29 -11
  15. package/modules/bus/README.md +33 -25
  16. package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
  17. package/modules/context/README.md +18 -40
  18. package/modules/context/SKILLS/uctx/SKILL.md +63 -1
  19. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  20. package/package.json +25 -4
  21. package/scripts/import-pi-mono.js +124 -0
  22. package/scripts/postinstall.js +30 -0
  23. package/scripts/sync-claude-skills.sh +21 -0
  24. package/src/agent/cliRunner.js +554 -33
  25. package/src/agent/internalRunner.js +150 -56
  26. package/src/agent/launcher.js +754 -0
  27. package/src/agent/normalizeOutput.js +1 -1
  28. package/src/agent/notifier.js +340 -0
  29. package/src/agent/ptyRunner.js +847 -0
  30. package/src/agent/ptyWrapper.js +379 -0
  31. package/src/agent/readyDetector.js +175 -0
  32. package/src/agent/ucode.js +443 -0
  33. package/src/agent/ucodeBootstrap.js +113 -0
  34. package/src/agent/ucodeBuild.js +67 -0
  35. package/src/agent/ucodeDoctor.js +184 -0
  36. package/src/agent/ucodeRuntimeConfig.js +129 -0
  37. package/src/agent/ufooAgent.js +46 -42
  38. package/src/assistant/agent.js +260 -0
  39. package/src/assistant/bridge.js +172 -0
  40. package/src/assistant/engine.js +252 -0
  41. package/src/assistant/stdio.js +58 -0
  42. package/src/assistant/ufooEngineCli.js +306 -0
  43. package/src/bus/activate.js +172 -0
  44. package/src/bus/daemon.js +436 -0
  45. package/src/bus/index.js +842 -0
  46. package/src/bus/inject.js +315 -0
  47. package/src/bus/message.js +430 -0
  48. package/src/bus/nickname.js +88 -0
  49. package/src/bus/queue.js +136 -0
  50. package/src/bus/shake.js +26 -0
  51. package/src/bus/store.js +189 -0
  52. package/src/bus/subscriber.js +312 -0
  53. package/src/bus/utils.js +363 -0
  54. package/src/chat/agentBar.js +117 -0
  55. package/src/chat/agentDirectory.js +88 -0
  56. package/src/chat/agentSockets.js +225 -0
  57. package/src/chat/agentViewController.js +298 -0
  58. package/src/chat/chatLogController.js +115 -0
  59. package/src/chat/commandExecutor.js +700 -0
  60. package/src/chat/commands.js +132 -0
  61. package/src/chat/completionController.js +414 -0
  62. package/src/chat/cronScheduler.js +160 -0
  63. package/src/chat/daemonConnection.js +166 -0
  64. package/src/chat/daemonCoordinator.js +64 -0
  65. package/src/chat/daemonMessageRouter.js +257 -0
  66. package/src/chat/daemonReconnect.js +41 -0
  67. package/src/chat/daemonTransport.js +36 -0
  68. package/src/chat/daemonTransportDefaults.js +10 -0
  69. package/src/chat/dashboardKeyController.js +480 -0
  70. package/src/chat/dashboardView.js +154 -0
  71. package/src/chat/index.js +1011 -1392
  72. package/src/chat/inputHistoryController.js +105 -0
  73. package/src/chat/inputListenerController.js +304 -0
  74. package/src/chat/inputMath.js +104 -0
  75. package/src/chat/inputSubmitHandler.js +171 -0
  76. package/src/chat/layout.js +165 -0
  77. package/src/chat/pasteController.js +81 -0
  78. package/src/chat/rawKeyMap.js +42 -0
  79. package/src/chat/settingsController.js +132 -0
  80. package/src/chat/statusLineController.js +177 -0
  81. package/src/chat/streamTracker.js +138 -0
  82. package/src/chat/text.js +70 -0
  83. package/src/chat/transport.js +61 -0
  84. package/src/cli/busCoreCommands.js +59 -0
  85. package/src/cli/ctxCoreCommands.js +199 -0
  86. package/src/cli/onlineCoreCommands.js +379 -0
  87. package/src/cli.js +1162 -96
  88. package/src/code/README.md +29 -0
  89. package/src/code/UCODE_PROMPT.md +32 -0
  90. package/src/code/agent.js +1651 -0
  91. package/src/code/cli.js +158 -0
  92. package/src/code/config +0 -0
  93. package/src/code/dispatch.js +42 -0
  94. package/src/code/index.js +70 -0
  95. package/src/code/nativeRunner.js +1213 -0
  96. package/src/code/runtime.js +154 -0
  97. package/src/code/sessionStore.js +162 -0
  98. package/src/code/taskDecomposer.js +269 -0
  99. package/src/code/tools/bash.js +53 -0
  100. package/src/code/tools/common.js +42 -0
  101. package/src/code/tools/edit.js +70 -0
  102. package/src/code/tools/read.js +44 -0
  103. package/src/code/tools/write.js +35 -0
  104. package/src/code/tui.js +1580 -0
  105. package/src/config.js +56 -3
  106. package/src/context/decisions.js +324 -0
  107. package/src/context/doctor.js +183 -0
  108. package/src/context/index.js +55 -0
  109. package/src/context/sync.js +127 -0
  110. package/src/daemon/agentProcessManager.js +74 -0
  111. package/src/daemon/cronOps.js +241 -0
  112. package/src/daemon/index.js +998 -170
  113. package/src/daemon/ipcServer.js +99 -0
  114. package/src/daemon/ops.js +630 -48
  115. package/src/daemon/promptLoop.js +319 -0
  116. package/src/daemon/promptRequest.js +101 -0
  117. package/src/daemon/providerSessions.js +306 -0
  118. package/src/daemon/reporting.js +90 -0
  119. package/src/daemon/run.js +31 -1
  120. package/src/daemon/status.js +48 -8
  121. package/src/doctor/index.js +50 -0
  122. package/src/init/index.js +318 -0
  123. package/src/online/bridge.js +663 -0
  124. package/src/online/client.js +245 -0
  125. package/src/online/runner.js +253 -0
  126. package/src/online/server.js +992 -0
  127. package/src/online/tokens.js +103 -0
  128. package/src/report/store.js +331 -0
  129. package/src/shared/eventContract.js +35 -0
  130. package/src/shared/ptySocketContract.js +21 -0
  131. package/src/skills/index.js +159 -0
  132. package/src/status/index.js +285 -0
  133. package/src/terminal/adapterContract.js +87 -0
  134. package/src/terminal/adapterRouter.js +84 -0
  135. package/src/terminal/adapters/externalAdapter.js +14 -0
  136. package/src/terminal/adapters/internalAdapter.js +13 -0
  137. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  138. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  139. package/src/terminal/adapters/terminalAdapter.js +31 -0
  140. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  141. package/src/terminal/detect.js +64 -0
  142. package/src/terminal/index.js +8 -0
  143. package/src/terminal/iterm2.js +126 -0
  144. package/src/ufoo/agentsStore.js +107 -0
  145. package/src/ufoo/paths.js +46 -0
  146. package/src/utils/banner.js +76 -0
  147. package/bin/uclaude +0 -65
  148. package/bin/ucodex +0 -65
  149. package/modules/bus/scripts/bus-alert.sh +0 -185
  150. package/modules/bus/scripts/bus-listen.sh +0 -117
  151. package/modules/context/ASSUMPTIONS.md +0 -7
  152. package/modules/context/CONSTRAINTS.md +0 -7
  153. package/modules/context/CONTEXT-STRUCTURE.md +0 -49
  154. package/modules/context/DECISION-PROTOCOL.md +0 -62
  155. package/modules/context/HANDOFF.md +0 -33
  156. package/modules/context/RULES.md +0 -15
  157. package/modules/context/SKILLS/README.md +0 -14
  158. package/modules/context/SYSTEM.md +0 -18
  159. package/modules/context/TEMPLATES/assumptions.md +0 -4
  160. package/modules/context/TEMPLATES/constraints.md +0 -4
  161. package/modules/context/TEMPLATES/decision.md +0 -16
  162. package/modules/context/TEMPLATES/project-context-readme.md +0 -6
  163. package/modules/context/TEMPLATES/system.md +0 -3
  164. package/modules/context/TEMPLATES/terminology.md +0 -4
  165. package/modules/context/TERMINOLOGY.md +0 -10
  166. package/scripts/banner.sh +0 -89
  167. package/scripts/bus-alert.sh +0 -6
  168. package/scripts/bus-autotrigger.sh +0 -6
  169. package/scripts/bus-daemon.sh +0 -231
  170. package/scripts/bus-inject.sh +0 -144
  171. package/scripts/bus-listen.sh +0 -6
  172. package/scripts/bus.sh +0 -984
  173. package/scripts/context-decisions.sh +0 -167
  174. package/scripts/context-doctor.sh +0 -72
  175. package/scripts/context-lint.sh +0 -110
  176. package/scripts/doctor.sh +0 -22
  177. package/scripts/init.sh +0 -247
  178. package/scripts/skills.sh +0 -113
  179. package/scripts/status.sh +0 -125
package/src/cli.js CHANGED
@@ -1,5 +1,11 @@
1
1
  const path = require("path");
2
2
  const { spawnSync } = require("child_process");
3
+ const net = require("net");
4
+ const fs = require("fs");
5
+ const { socketPath, isRunning } = require("./daemon");
6
+ const { runBusCoreCommand } = require("./cli/busCoreCommands");
7
+ const { runCtxCommand } = require("./cli/ctxCoreCommands");
8
+ const { runOnlineCommand } = require("./cli/onlineCoreCommands");
3
9
 
4
10
  function getPackageRoot() {
5
11
  return path.resolve(__dirname, "..");
@@ -22,6 +28,98 @@ function getPackageScript(rel) {
22
28
  return path.join(getPackageRoot(), rel);
23
29
  }
24
30
 
31
+ function connectSocket(sockPath) {
32
+ return new Promise((resolve, reject) => {
33
+ const client = net.createConnection(sockPath, () => resolve(client));
34
+ client.on("error", reject);
35
+ });
36
+ }
37
+
38
+ async function connectWithRetry(sockPath, retries, delayMs) {
39
+ for (let i = 0; i < retries; i += 1) {
40
+ try {
41
+ // eslint-disable-next-line no-await-in-loop
42
+ const client = await connectSocket(sockPath);
43
+ return client;
44
+ } catch {
45
+ // eslint-disable-next-line no-await-in-loop
46
+ await new Promise((r) => setTimeout(r, delayMs));
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ async function ensureDaemonRunning(projectRoot) {
53
+ if (isRunning(projectRoot)) return;
54
+ const repoRoot = getPackageRoot();
55
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "daemon", "start"]);
56
+ const sock = socketPath(projectRoot);
57
+ for (let i = 0; i < 30; i += 1) {
58
+ if (fs.existsSync(sock)) {
59
+ return;
60
+ }
61
+ // eslint-disable-next-line no-await-in-loop
62
+ await new Promise((r) => setTimeout(r, 200));
63
+ }
64
+ throw new Error("Failed to start ufoo daemon");
65
+ }
66
+
67
+ async function sendDaemonRequest(projectRoot, payload) {
68
+ const sock = socketPath(projectRoot);
69
+ const client = await connectWithRetry(sock, 25, 200);
70
+ if (!client) {
71
+ throw new Error("Failed to connect to ufoo daemon");
72
+ }
73
+ return new Promise((resolve, reject) => {
74
+ let buffer = "";
75
+ const timeout = setTimeout(() => {
76
+ try {
77
+ client.destroy();
78
+ } catch {
79
+ // ignore
80
+ }
81
+ reject(new Error("Daemon request timeout"));
82
+ }, 8000);
83
+ const cleanup = () => {
84
+ clearTimeout(timeout);
85
+ client.removeAllListeners();
86
+ try {
87
+ client.end();
88
+ } catch {
89
+ // ignore
90
+ }
91
+ };
92
+ client.on("data", (data) => {
93
+ buffer += data.toString("utf8");
94
+ const lines = buffer.split(/\r?\n/);
95
+ buffer = lines.pop() || "";
96
+ for (const line of lines) {
97
+ if (!line.trim()) continue;
98
+ let msg;
99
+ try {
100
+ msg = JSON.parse(line);
101
+ } catch {
102
+ continue;
103
+ }
104
+ if (msg.type === "response" || msg.type === "error") {
105
+ cleanup();
106
+ if (msg.type === "error") {
107
+ reject(new Error(msg.error || "Daemon error"));
108
+ } else {
109
+ resolve(msg);
110
+ }
111
+ return;
112
+ }
113
+ }
114
+ });
115
+ client.on("error", (err) => {
116
+ cleanup();
117
+ reject(err);
118
+ });
119
+ client.write(`${JSON.stringify(payload)}\n`);
120
+ });
121
+ }
122
+
25
123
  function requireOptional(name) {
26
124
  try {
27
125
  // eslint-disable-next-line global-require, import/no-dynamic-require
@@ -31,6 +129,70 @@ function requireOptional(name) {
31
129
  }
32
130
  }
33
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
+
34
196
  async function runCli(argv) {
35
197
  const pkg = require(path.resolve(getPackageRoot(), "package.json"));
36
198
 
@@ -44,21 +206,25 @@ async function runCli(argv) {
44
206
  program
45
207
  .name("ufoo")
46
208
  .description("ufoo CLI (wrapper-first; prefers project-local scripts).")
47
- .version(pkg.version);
209
+ .version(pkg.version, "-v, --version", "Display version with banner");
48
210
 
49
211
  program
50
212
  .command("doctor")
51
213
  .description("Run repo doctor checks")
52
214
  .action(() => {
53
215
  const repoRoot = getPackageRoot();
54
- run("bash", [path.join(repoRoot, "scripts/doctor.sh")]);
216
+ const RepoDoctor = require("./doctor");
217
+ const doctor = new RepoDoctor(repoRoot);
218
+ const ok = doctor.run();
219
+ if (!ok) process.exitCode = 1;
55
220
  });
56
221
  program
57
222
  .command("status")
58
223
  .description("Show project status (banner, unread bus, open decisions)")
59
- .action(() => {
60
- const repoRoot = getPackageRoot();
61
- run("bash", [path.join(repoRoot, "scripts/status.sh")]);
224
+ .action(async () => {
225
+ const StatusDisplay = require("./status");
226
+ const status = new StatusDisplay(process.cwd());
227
+ await status.show();
62
228
  });
63
229
  program
64
230
  .command("daemon")
@@ -81,21 +247,249 @@ async function runCli(argv) {
81
247
  const repoRoot = getPackageRoot();
82
248
  run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "chat"]);
83
249
  });
250
+ program
251
+ .command("resume")
252
+ .description("Resume agent sessions (optional nickname)")
253
+ .argument("[nickname]", "Nickname or subscriber ID to resume")
254
+ .action(async (nickname) => {
255
+ try {
256
+ const projectRoot = process.cwd();
257
+ await ensureDaemonRunning(projectRoot);
258
+ const resp = await sendDaemonRequest(projectRoot, {
259
+ type: "resume_agents",
260
+ target: nickname || "",
261
+ });
262
+ const reply = resp?.data?.reply || "Resume requested";
263
+ console.log(reply);
264
+ if (resp?.data?.resume?.resumed?.length) {
265
+ resp.data.resume.resumed.forEach((item) => {
266
+ const label = item.nickname ? ` (${item.nickname})` : "";
267
+ console.log(` - ${item.agent}${label}`);
268
+ });
269
+ }
270
+ } catch (err) {
271
+ console.error(err.message || String(err));
272
+ process.exitCode = 1;
273
+ }
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
+ });
84
477
 
85
478
  program
86
479
  .command("init")
87
480
  .description("Initialize modules in a project")
88
481
  .option("--modules <list>", "Comma-separated modules (context,bus,resources)", "context")
89
482
  .option("--project <dir>", "Target project directory", process.cwd())
90
- .action((opts) => {
483
+ .action(async (opts) => {
484
+ const UfooInit = require("./init");
91
485
  const repoRoot = getPackageRoot();
92
- run("bash", [
93
- path.join(repoRoot, "scripts/init.sh"),
94
- "--modules",
95
- opts.modules,
96
- "--project",
97
- opts.project,
98
- ]);
486
+ const init = new UfooInit(repoRoot);
487
+ try {
488
+ await init.init(opts);
489
+ } catch (err) {
490
+ console.error(err.message);
491
+ process.exitCode = 1;
492
+ }
99
493
  });
100
494
 
101
495
  const skills = program.command("skills").description("Manage skills templates");
@@ -103,8 +497,11 @@ async function runCli(argv) {
103
497
  .command("list")
104
498
  .description("List available skills")
105
499
  .action(() => {
500
+ const SkillsManager = require("./skills");
106
501
  const repoRoot = getPackageRoot();
107
- run("bash", [path.join(repoRoot, "scripts/skills.sh"), "list"]);
502
+ const manager = new SkillsManager(repoRoot);
503
+ const skillsList = manager.list();
504
+ skillsList.forEach((skill) => console.log(skill));
108
505
  });
109
506
  skills
110
507
  .command("install")
@@ -113,13 +510,178 @@ async function runCli(argv) {
113
510
  .option("--target <dir>", "Install target directory")
114
511
  .option("--codex", "Install into ~/.codex/skills")
115
512
  .option("--agents", "Install into ~/.agents/skills")
116
- .action((name, opts) => {
513
+ .action(async (name, opts) => {
514
+ const SkillsManager = require("./skills");
117
515
  const repoRoot = getPackageRoot();
118
- const args = [path.join(repoRoot, "scripts/skills.sh"), "install", name];
119
- if (opts.target) args.push("--target", opts.target);
120
- if (opts.codex) args.push("--codex");
121
- if (opts.agents) args.push("--agents");
122
- run("bash", args);
516
+ const manager = new SkillsManager(repoRoot);
517
+ try {
518
+ await manager.install(name, opts);
519
+ } catch (err) {
520
+ console.error(err.message);
521
+ process.exitCode = 1;
522
+ }
523
+ });
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
+ }
123
685
  });
124
686
 
125
687
  const bus = program.command("bus").description("Project bus commands");
@@ -135,29 +697,42 @@ async function runCli(argv) {
135
697
  .option("--no-bell", "Disable terminal bell")
136
698
  .allowUnknownOption(true)
137
699
  .action((subscriber, interval, opts) => {
138
- const script = getPackageScript("scripts/bus-alert.sh");
139
- const args = [script, subscriber, interval];
140
- if (opts.notify) args.push("--notify");
141
- if (opts.daemon) args.push("--daemon");
142
- if (opts.stop) args.push("--stop");
143
- if (opts.title === false) args.push("--no-title");
144
- if (opts.bell === false) args.push("--no-bell");
145
- run("bash", args);
700
+ const EventBus = require("./bus");
701
+ const eventBus = new EventBus(process.cwd());
702
+ const parsedInterval = parseInt(interval, 10);
703
+ eventBus
704
+ .alert(subscriber, Number.isFinite(parsedInterval) ? parsedInterval : 2, {
705
+ notify: opts.notify,
706
+ daemon: opts.daemon,
707
+ stop: opts.stop,
708
+ title: opts.title !== false,
709
+ bell: opts.bell !== false,
710
+ })
711
+ .catch((err) => {
712
+ console.error(err.message);
713
+ process.exitCode = 1;
714
+ });
146
715
  });
147
716
  bus
148
717
  .command("listen")
149
718
  .description("Foreground listener for incoming messages")
150
- .argument("<subscriber>", "Subscriber ID")
719
+ .argument("[subscriber]", "Subscriber ID")
151
720
  .option("--from-beginning", "Print existing queued messages first")
152
721
  .option("--reset", "Truncate pending queue before listening")
153
722
  .option("--auto-join", "Auto-join bus to get subscriber ID")
154
723
  .action((subscriber, opts) => {
155
- const script = getPackageScript("scripts/bus-listen.sh");
156
- const args = [script, subscriber];
157
- if (opts.fromBeginning) args.push("--from-beginning");
158
- if (opts.reset) args.push("--reset");
159
- if (opts.autoJoin) args.push("--auto-join");
160
- run("bash", args);
724
+ const EventBus = require("./bus");
725
+ const eventBus = new EventBus(process.cwd());
726
+ eventBus
727
+ .listen(subscriber, {
728
+ fromBeginning: opts.fromBeginning,
729
+ reset: opts.reset,
730
+ autoJoin: opts.autoJoin,
731
+ })
732
+ .catch((err) => {
733
+ console.error(err.message);
734
+ process.exitCode = 1;
735
+ });
161
736
  });
162
737
  bus
163
738
  .command("daemon")
@@ -167,56 +742,117 @@ async function runCli(argv) {
167
742
  .option("--stop", "Stop running daemon")
168
743
  .option("--status", "Check daemon status")
169
744
  .action((opts) => {
170
- const script = getPackageScript("scripts/bus-daemon.sh");
171
- const args = [script];
172
- if (opts.interval) args.push("--interval", opts.interval);
173
- if (opts.daemon) args.push("--daemon");
174
- if (opts.stop) args.push("--stop");
175
- if (opts.status) args.push("--status");
176
- run("bash", args);
745
+ const EventBus = require("./bus");
746
+ const eventBus = new EventBus(process.cwd());
747
+ (async () => {
748
+ try {
749
+ const interval = parseInt(opts.interval, 10) * 1000 || 2000;
750
+ if (opts.stop) {
751
+ await eventBus.daemon("stop");
752
+ } else if (opts.status) {
753
+ await eventBus.daemon("status");
754
+ } else {
755
+ await eventBus.daemon("start", { background: opts.daemon, interval });
756
+ }
757
+ } catch (err) {
758
+ console.error(err.message);
759
+ process.exitCode = 1;
760
+ }
761
+ })();
177
762
  });
178
763
  bus
179
764
  .command("inject")
180
765
  .description("Inject /bus into a Terminal.app tab by subscriber ID")
181
766
  .argument("<subscriber>", "Subscriber ID to inject into")
182
767
  .action((subscriber) => {
183
- const script = getPackageScript("scripts/bus-inject.sh");
184
- run("bash", [script, subscriber]);
768
+ const EventBus = require("./bus");
769
+ const eventBus = new EventBus(process.cwd());
770
+ (async () => {
771
+ try {
772
+ await eventBus.inject(subscriber);
773
+ } catch (err) {
774
+ console.error(err.message);
775
+ process.exitCode = 1;
776
+ }
777
+ })();
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
+ });
797
+ bus
798
+ .command("activate")
799
+ .description("Activate (focus) the terminal/tmux window of an agent")
800
+ .argument("<agent-id>", "Agent ID or nickname to activate")
801
+ .action((agentId) => {
802
+ const AgentActivator = require("./bus/activate");
803
+ const activator = new AgentActivator(process.cwd());
804
+ (async () => {
805
+ try {
806
+ await activator.activate(agentId);
807
+ } catch (err) {
808
+ console.error(err.message);
809
+ process.exitCode = 1;
810
+ }
811
+ })();
185
812
  });
186
813
  bus
187
814
  .command("run", { isDefault: true })
188
- .description("Run bus.sh commands (join, check, send, status, etc.)")
815
+ .description("Run bus commands (join, check, send, status, etc.)")
189
816
  .allowUnknownOption(true)
190
- .argument("<args...>", "Arguments passed to scripts/bus.sh")
191
- .action((args) => {
192
- const script = getPackageScript("scripts/bus.sh");
193
- run("bash", [script, ...args]);
817
+ .argument("<args...>", "Arguments passed to bus module")
818
+ .action(async (args) => {
819
+ const EventBus = require("./bus");
820
+ const eventBus = new EventBus(process.cwd());
821
+ const cmd = args[0];
822
+ const cmdArgs = args.slice(1);
823
+
824
+ try {
825
+ const result = await runBusCoreCommand(eventBus, cmd, cmdArgs);
826
+ if (result && result.subscriber) console.log(result.subscriber);
827
+ } catch (err) {
828
+ console.error(err.message);
829
+ process.exitCode = 1;
830
+ }
194
831
  });
195
832
 
196
833
  program
197
834
  .command("ctx")
198
- .description("Project ctx commands (delegates to ./scripts/context-*.sh)")
199
- .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")
200
837
  .allowUnknownOption(true)
201
838
  .argument("[subargs...]", "Subcommand args")
202
- .action((subcmd, subargs = []) => {
203
- const map = {
204
- doctor: "scripts/context-doctor.sh",
205
- lint: "scripts/context-lint.sh",
206
- decisions: "scripts/context-decisions.sh",
207
- };
208
- const rel = map[subcmd];
209
- if (!rel) {
210
- console.error(
211
- chalk.red(
212
- `Unknown ctx subcommand: ${subcmd}. Supported: ${Object.keys(map).join(", ")}`
213
- )
214
- );
839
+ .action(async (subcmd, subargs = []) => {
840
+ const cwd = process.cwd();
841
+
842
+ try {
843
+ await runCtxCommand(subcmd, subargs, {
844
+ cwd,
845
+ allowIndexNew: true,
846
+ updateDecisionIndexPaths: true,
847
+ });
848
+ } catch (err) {
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
+ }
215
854
  process.exitCode = 1;
216
- return;
217
855
  }
218
- const script = getPackageScript(rel);
219
- run("bash", [script, ...subargs]);
220
856
  });
221
857
 
222
858
  program.addHelpText(
@@ -224,10 +860,17 @@ async function runCli(argv) {
224
860
  `\nNotes:\n - If 'ufoo' isn't in PATH, run it via ${chalk.cyan(
225
861
  "./bin/ufoo"
226
862
  )} (repo) or install globally via npm.\n - For bus notifications inside Codex, prefer ${chalk.cyan(
227
- "scripts/bus-alert.sh"
228
- )} / ${chalk.cyan("scripts/bus-listen.sh")} (no IME issues).\n`
863
+ "ufoo bus alert"
864
+ )} / ${chalk.cyan("ufoo bus listen")} (no IME issues).\n`
229
865
  );
230
866
 
867
+ // 检查是否是 --version 或 -V 参数
868
+ if (argv.includes("--version") || argv.includes("-V")) {
869
+ const { showUfooBanner } = require("./utils/banner");
870
+ showUfooBanner({ version: pkg.version });
871
+ return;
872
+ }
873
+
231
874
  await program.parseAsync(argv);
232
875
  return;
233
876
  }
@@ -245,14 +888,28 @@ async function runCli(argv) {
245
888
  console.log(" ufoo status");
246
889
  console.log(" ufoo daemon --start|--stop|--status");
247
890
  console.log(" ufoo chat");
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]");
248
895
  console.log(" ufoo init [--modules <list>] [--project <dir>]");
249
896
  console.log(" ufoo skills list");
250
897
  console.log(" ufoo skills install <name|all> [--target <dir> | --codex | --agents]");
251
- console.log(" ufoo bus <args...> (delegates to ./scripts/bus.sh)");
252
- console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions)");
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]");
908
+ console.log(" ufoo bus <args...> (JS bus implementation)");
909
+ console.log(" ufoo ctx <subcmd> ... (doctor|lint|decisions|sync)");
253
910
  console.log("");
254
911
  console.log("Notes:");
255
- console.log(" - For Codex notifications, use scripts/bus-alert.sh / scripts/bus-listen.sh");
912
+ console.log(" - For Codex notifications, use ufoo bus alert / ufoo bus listen");
256
913
  };
257
914
 
258
915
  if (cmd === "" || cmd === "--help" || cmd === "-h") {
@@ -260,12 +917,26 @@ async function runCli(argv) {
260
917
  return;
261
918
  }
262
919
 
920
+ if (cmd === "--version" || cmd === "-V") {
921
+ const { showUfooBanner } = require("./utils/banner");
922
+ showUfooBanner({ version: pkg.version });
923
+ return;
924
+ }
925
+
263
926
  if (cmd === "doctor") {
264
- run("bash", [path.join(repoRoot, "scripts/doctor.sh")]);
927
+ const RepoDoctor = require("./doctor");
928
+ const doctor = new RepoDoctor(repoRoot);
929
+ const ok = doctor.run();
930
+ if (!ok) process.exitCode = 1;
265
931
  return;
266
932
  }
267
933
  if (cmd === "status") {
268
- run("bash", [path.join(repoRoot, "scripts/status.sh")]);
934
+ const StatusDisplay = require("./status");
935
+ const status = new StatusDisplay(process.cwd());
936
+ status.show().catch((err) => {
937
+ console.error(err.message);
938
+ process.exitCode = 1;
939
+ });
269
940
  return;
270
941
  }
271
942
  if (cmd === "daemon") {
@@ -276,69 +947,464 @@ async function runCli(argv) {
276
947
  run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "chat"]);
277
948
  return;
278
949
  }
950
+ if (cmd === "resume") {
951
+ const nickname = rest[0] || "";
952
+ (async () => {
953
+ try {
954
+ const projectRoot = process.cwd();
955
+ await ensureDaemonRunning(projectRoot);
956
+ const resp = await sendDaemonRequest(projectRoot, {
957
+ type: "resume_agents",
958
+ target: nickname,
959
+ });
960
+ const reply = resp?.data?.reply || "Resume requested";
961
+ console.log(reply);
962
+ } catch (err) {
963
+ console.error(err.message || String(err));
964
+ process.exitCode = 1;
965
+ }
966
+ })();
967
+ return;
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
+ }
279
1177
  if (cmd === "init") {
1178
+ const UfooInit = require("./init");
1179
+ const init = new UfooInit(repoRoot);
1180
+
280
1181
  const getOpt = (name, def) => {
281
1182
  const i = rest.indexOf(name);
282
1183
  if (i === -1) return def;
283
1184
  if (i + 1 >= rest.length) throw new Error(`Missing value for ${name}`);
284
1185
  return rest[i + 1];
285
1186
  };
286
- run("bash", [
287
- path.join(repoRoot, "scripts/init.sh"),
288
- "--modules",
289
- getOpt("--modules", "context"),
290
- "--project",
291
- getOpt("--project", process.cwd()),
292
- ]);
1187
+
1188
+ const opts = {
1189
+ modules: getOpt("--modules", "context"),
1190
+ project: getOpt("--project", process.cwd()),
1191
+ };
1192
+
1193
+ init.init(opts).catch((err) => {
1194
+ console.error(err.message);
1195
+ process.exitCode = 1;
1196
+ });
293
1197
  return;
294
1198
  }
295
1199
  if (cmd === "skills") {
1200
+ const SkillsManager = require("./skills");
1201
+ const manager = new SkillsManager(repoRoot);
296
1202
  const sub = rest[0] || "";
1203
+
297
1204
  if (sub === "list") {
298
- run("bash", [path.join(repoRoot, "scripts/skills.sh"), "list"]);
1205
+ const skillsList = manager.list();
1206
+ skillsList.forEach((skill) => console.log(skill));
299
1207
  return;
300
1208
  }
301
1209
  if (sub === "install") {
302
1210
  const name = rest[1];
303
1211
  if (!name) throw new Error("skills install requires <name|all>");
304
- run("bash", [path.join(repoRoot, "scripts/skills.sh"), "install", ...rest.slice(1)]);
1212
+
1213
+ const options = {};
1214
+ for (let i = 2; i < rest.length; i++) {
1215
+ if (rest[i] === "--target" && i + 1 < rest.length) {
1216
+ options.target = rest[i + 1];
1217
+ i++;
1218
+ } else if (rest[i] === "--codex") {
1219
+ options.codex = true;
1220
+ } else if (rest[i] === "--agents") {
1221
+ options.agents = true;
1222
+ }
1223
+ }
1224
+
1225
+ manager.install(name, options).catch((err) => {
1226
+ console.error(err.message);
1227
+ process.exitCode = 1;
1228
+ });
305
1229
  return;
306
1230
  }
307
1231
  help();
308
1232
  process.exitCode = 1;
309
1233
  return;
310
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
+ }
311
1264
  if (cmd === "bus") {
312
1265
  const sub = rest[0] || "";
313
1266
  if (sub === "alert") {
314
- run("bash", [getPackageScript("scripts/bus-alert.sh"), ...rest.slice(1)]);
1267
+ const EventBus = require("./bus");
1268
+ const eventBus = new EventBus(process.cwd());
1269
+ const args = rest.slice(1);
1270
+ const subscriber = args[0];
1271
+ let interval = 2;
1272
+ let idx = 1;
1273
+ if (args[1] && /^[0-9]+$/.test(args[1])) {
1274
+ interval = parseInt(args[1], 10);
1275
+ idx = 2;
1276
+ }
1277
+ const options = {
1278
+ notify: args.includes("--notify"),
1279
+ daemon: args.includes("--daemon"),
1280
+ stop: args.includes("--stop"),
1281
+ title: !args.includes("--no-title"),
1282
+ bell: !args.includes("--no-bell"),
1283
+ };
1284
+ eventBus
1285
+ .alert(subscriber, interval, options)
1286
+ .catch((err) => {
1287
+ console.error(err.message);
1288
+ process.exitCode = 1;
1289
+ });
315
1290
  return;
316
1291
  }
317
1292
  if (sub === "listen") {
318
- run("bash", [getPackageScript("scripts/bus-listen.sh"), ...rest.slice(1)]);
1293
+ const EventBus = require("./bus");
1294
+ const eventBus = new EventBus(process.cwd());
1295
+ const args = rest.slice(1);
1296
+ const subscriber = args.find((arg) => !arg.startsWith("--"));
1297
+ const options = {
1298
+ fromBeginning: args.includes("--from-beginning"),
1299
+ reset: args.includes("--reset"),
1300
+ autoJoin: args.includes("--auto-join"),
1301
+ };
1302
+ eventBus
1303
+ .listen(subscriber, options)
1304
+ .catch((err) => {
1305
+ console.error(err.message);
1306
+ process.exitCode = 1;
1307
+ });
319
1308
  return;
320
1309
  }
321
1310
  if (sub === "daemon") {
322
- run("bash", [getPackageScript("scripts/bus-daemon.sh"), ...rest.slice(1)]);
1311
+ // 使用 JavaScript daemon
1312
+ const EventBus = require("./bus");
1313
+ const eventBus = new EventBus(process.cwd());
1314
+
1315
+ (async () => {
1316
+ try {
1317
+ const hasStop = rest.includes("--stop");
1318
+ const hasStatus = rest.includes("--status");
1319
+ const hasDaemon = rest.includes("--daemon");
1320
+ const intervalIdx = rest.indexOf("--interval");
1321
+ const interval = intervalIdx !== -1 ? parseInt(rest[intervalIdx + 1], 10) * 1000 : 2000;
1322
+
1323
+ if (hasStop) {
1324
+ await eventBus.daemon("stop");
1325
+ } else if (hasStatus) {
1326
+ await eventBus.daemon("status");
1327
+ } else {
1328
+ await eventBus.daemon("start", { background: hasDaemon, interval });
1329
+ }
1330
+ } catch (err) {
1331
+ console.error(err.message);
1332
+ process.exitCode = 1;
1333
+ }
1334
+ })();
323
1335
  return;
324
1336
  }
325
1337
  if (sub === "inject") {
326
- run("bash", [getPackageScript("scripts/bus-inject.sh"), ...rest.slice(1)]);
1338
+ // 使用 JavaScript inject
1339
+ const EventBus = require("./bus");
1340
+ const eventBus = new EventBus(process.cwd());
1341
+
1342
+ (async () => {
1343
+ try {
1344
+ const subscriber = rest[1];
1345
+ if (!subscriber) {
1346
+ throw new Error("inject requires <subscriber-id>");
1347
+ }
1348
+ await eventBus.inject(subscriber);
1349
+ } catch (err) {
1350
+ console.error(err.message);
1351
+ process.exitCode = 1;
1352
+ }
1353
+ })();
1354
+ return;
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
+ })();
327
1372
  return;
328
1373
  }
329
- run("bash", [getPackageScript("scripts/bus.sh"), ...rest]);
1374
+
1375
+ // Use JavaScript EventBus module for core commands
1376
+ const EventBus = require("./bus");
1377
+ const eventBus = new EventBus(process.cwd());
1378
+
1379
+ (async () => {
1380
+ try {
1381
+ const cmdArgs = rest.slice(1);
1382
+ const result = await runBusCoreCommand(eventBus, sub, cmdArgs);
1383
+ if (result && result.subscriber) console.log(result.subscriber);
1384
+ } catch (err) {
1385
+ console.error(err.message);
1386
+ process.exitCode = 1;
1387
+ }
1388
+ })();
330
1389
  return;
331
1390
  }
332
1391
  if (cmd === "ctx") {
333
1392
  const sub = rest[0] || "doctor";
334
- const map = {
335
- doctor: "scripts/context-doctor.sh",
336
- lint: "scripts/context-lint.sh",
337
- decisions: "scripts/context-decisions.sh",
338
- };
339
- const rel = map[sub];
340
- if (!rel) throw new Error(`Unknown ctx subcommand: ${sub}`);
341
- run("bash", [getPackageScript(rel), ...rest.slice(1)]);
1393
+ const subargs = rest.slice(1);
1394
+ const cwd = process.cwd();
1395
+
1396
+ (async () => {
1397
+ try {
1398
+ await runCtxCommand(sub, subargs, {
1399
+ cwd,
1400
+ allowIndexNew: false,
1401
+ updateDecisionIndexPaths: false,
1402
+ });
1403
+ } catch (err) {
1404
+ console.error(`Error: ${err.message}`);
1405
+ process.exit(1);
1406
+ }
1407
+ })();
342
1408
  return;
343
1409
  }
344
1410