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.
- package/README.md +110 -11
- package/README.zh-CN.md +9 -7
- package/SKILLS/ufoo/SKILL.md +132 -0
- package/SKILLS/uinit/SKILL.md +78 -0
- package/SKILLS/ustatus/SKILL.md +36 -0
- package/bin/uclaude.js +13 -0
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ucodex.js +13 -0
- package/bin/ufoo +9 -31
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +17 -0
- package/modules/AGENTS.template.md +29 -11
- package/modules/bus/README.md +33 -25
- package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
- package/modules/context/README.md +18 -40
- package/modules/context/SKILLS/uctx/SKILL.md +63 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +25 -4
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +30 -0
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +554 -33
- package/src/agent/internalRunner.js +150 -56
- package/src/agent/launcher.js +754 -0
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +340 -0
- package/src/agent/ptyRunner.js +847 -0
- package/src/agent/ptyWrapper.js +379 -0
- package/src/agent/readyDetector.js +175 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +46 -42
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +172 -0
- package/src/bus/daemon.js +436 -0
- package/src/bus/index.js +842 -0
- package/src/bus/inject.js +315 -0
- package/src/bus/message.js +430 -0
- package/src/bus/nickname.js +88 -0
- package/src/bus/queue.js +136 -0
- package/src/bus/shake.js +26 -0
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +312 -0
- package/src/bus/utils.js +363 -0
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +1011 -1392
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +1162 -96
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +56 -3
- package/src/context/decisions.js +324 -0
- package/src/context/doctor.js +183 -0
- package/src/context/index.js +55 -0
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +998 -170
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +630 -48
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +306 -0
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +31 -1
- package/src/daemon/status.js +48 -8
- package/src/doctor/index.js +50 -0
- package/src/init/index.js +318 -0
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/skills/index.js +159 -0
- package/src/status/index.js +285 -0
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/terminal/detect.js +64 -0
- package/src/terminal/index.js +8 -0
- package/src/terminal/iterm2.js +126 -0
- package/src/ufoo/agentsStore.js +107 -0
- package/src/ufoo/paths.js +46 -0
- package/src/utils/banner.js +76 -0
- package/bin/uclaude +0 -65
- package/bin/ucodex +0 -65
- package/modules/bus/scripts/bus-alert.sh +0 -185
- package/modules/bus/scripts/bus-listen.sh +0 -117
- package/modules/context/ASSUMPTIONS.md +0 -7
- package/modules/context/CONSTRAINTS.md +0 -7
- package/modules/context/CONTEXT-STRUCTURE.md +0 -49
- package/modules/context/DECISION-PROTOCOL.md +0 -62
- package/modules/context/HANDOFF.md +0 -33
- package/modules/context/RULES.md +0 -15
- package/modules/context/SKILLS/README.md +0 -14
- package/modules/context/SYSTEM.md +0 -18
- package/modules/context/TEMPLATES/assumptions.md +0 -4
- package/modules/context/TEMPLATES/constraints.md +0 -4
- package/modules/context/TEMPLATES/decision.md +0 -16
- package/modules/context/TEMPLATES/project-context-readme.md +0 -6
- package/modules/context/TEMPLATES/system.md +0 -3
- package/modules/context/TEMPLATES/terminology.md +0 -4
- package/modules/context/TERMINOLOGY.md +0 -10
- package/scripts/banner.sh +0 -89
- package/scripts/bus-alert.sh +0 -6
- package/scripts/bus-autotrigger.sh +0 -6
- package/scripts/bus-daemon.sh +0 -231
- package/scripts/bus-inject.sh +0 -144
- package/scripts/bus-listen.sh +0 -6
- package/scripts/bus.sh +0 -984
- package/scripts/context-decisions.sh +0 -167
- package/scripts/context-doctor.sh +0 -72
- package/scripts/context-lint.sh +0 -110
- package/scripts/doctor.sh +0 -22
- package/scripts/init.sh +0 -247
- package/scripts/skills.sh +0 -113
- 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
|
-
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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("
|
|
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
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
184
|
-
|
|
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
|
|
815
|
+
.description("Run bus commands (join, check, send, status, etc.)")
|
|
189
816
|
.allowUnknownOption(true)
|
|
190
|
-
.argument("<args...>", "Arguments passed to
|
|
191
|
-
.action((args) => {
|
|
192
|
-
const
|
|
193
|
-
|
|
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
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
"
|
|
228
|
-
)} / ${chalk.cyan("
|
|
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
|
|
252
|
-
console.log(" ufoo
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
"--modules",
|
|
289
|
-
getOpt("--
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|