u-foo 2.4.5 → 2.4.7

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 (64) hide show
  1. package/README.md +6 -8
  2. package/README.zh-CN.md +5 -5
  3. package/SKILLS/ufoo/SKILL.md +2 -2
  4. package/SKILLS/uinit/SKILL.md +8 -11
  5. package/package.json +3 -8
  6. package/scripts/postinstall.js +1 -21
  7. package/src/agents/launch/launcher.js +1 -13
  8. package/src/app/chat/commandExecutor.js +14 -9
  9. package/src/app/chat/commands.js +2 -2
  10. package/src/app/chat/daemonCoordinator.js +4 -0
  11. package/src/app/chat/daemonReconnect.js +17 -7
  12. package/src/app/cli/features/doctor.js +11 -18
  13. package/src/app/cli/features/init.js +12 -191
  14. package/src/app/cli/features/skills.js +0 -15
  15. package/src/app/cli/run.js +12 -7
  16. package/src/code/README.md +4 -4
  17. package/src/code/cli.js +1 -1
  18. package/src/code/launcher/ucode.js +18 -45
  19. package/src/code/skills/loader.js +0 -13
  20. package/src/coordination/context/doctor.js +6 -26
  21. package/src/online/server.js +1 -1
  22. package/src/runtime/daemon/index.js +1 -1
  23. package/src/runtime/daemon/mcpServer.js +1 -1
  24. package/src/runtime/daemon/restart.js +293 -0
  25. package/src/runtime/daemon/run.js +31 -37
  26. package/src/runtime/terminal/index.js +1 -1
  27. package/src/ui/MIGRATION.md +8 -10
  28. package/src/ui/ink/ChatApp.js +12 -6
  29. package/bin/ucode-core.js +0 -15
  30. package/bin/ufoo +0 -71
  31. package/modules/AGENTS.template.md +0 -8
  32. package/modules/bus/README.md +0 -140
  33. package/modules/context/README.md +0 -60
  34. package/modules/online/README.md +0 -92
  35. package/modules/resources/ICONS/README.md +0 -12
  36. package/modules/resources/ICONS/libraries/README.md +0 -17
  37. package/modules/resources/ICONS/libraries/heroicons/LICENSE +0 -22
  38. package/modules/resources/ICONS/libraries/heroicons/README.md +0 -15
  39. package/modules/resources/ICONS/libraries/heroicons/arrow-right.svg +0 -4
  40. package/modules/resources/ICONS/libraries/heroicons/check.svg +0 -4
  41. package/modules/resources/ICONS/libraries/heroicons/chevron-down.svg +0 -4
  42. package/modules/resources/ICONS/libraries/heroicons/cog-6-tooth.svg +0 -5
  43. package/modules/resources/ICONS/libraries/heroicons/magnifying-glass.svg +0 -4
  44. package/modules/resources/ICONS/libraries/heroicons/x-mark.svg +0 -4
  45. package/modules/resources/ICONS/libraries/lucide/LICENSE +0 -40
  46. package/modules/resources/ICONS/libraries/lucide/README.md +0 -15
  47. package/modules/resources/ICONS/libraries/lucide/arrow-right.svg +0 -15
  48. package/modules/resources/ICONS/libraries/lucide/check.svg +0 -14
  49. package/modules/resources/ICONS/libraries/lucide/chevron-down.svg +0 -14
  50. package/modules/resources/ICONS/libraries/lucide/search.svg +0 -15
  51. package/modules/resources/ICONS/libraries/lucide/settings.svg +0 -15
  52. package/modules/resources/ICONS/libraries/lucide/x.svg +0 -15
  53. package/modules/resources/ICONS/rules.md +0 -7
  54. package/modules/resources/README.md +0 -9
  55. package/modules/resources/UI/ANTI-PATTERNS.md +0 -6
  56. package/modules/resources/UI/TONE.md +0 -6
  57. package/scripts/chat-app-smoke.js +0 -30
  58. package/scripts/global-chat-switch-benchmark.js +0 -406
  59. package/scripts/ink-demo.js +0 -23
  60. package/scripts/ink-smoke.js +0 -30
  61. package/scripts/ucode-app-smoke.js +0 -36
  62. /package/{modules/bus/SKILLS → SKILLS}/ubus/SKILL.md +0 -0
  63. /package/{modules/context/SKILLS → SKILLS}/uctx/SKILL.md +0 -0
  64. /package/{modules/online/SKILLS → SKILLS}/ufoo-online/SKILL.md +0 -0
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+
3
+ const DEFAULT_STOP_TIMEOUT_MS = 5000;
4
+ const DEFAULT_START_TIMEOUT_MS = 5000;
5
+ const DEFAULT_POLL_MS = 100;
6
+
7
+ function normalizePositiveInt(value, fallback) {
8
+ const n = Number(value);
9
+ if (!Number.isFinite(n) || n <= 0) return fallback;
10
+ return Math.trunc(n);
11
+ }
12
+
13
+ function waitAttempts(timeoutMs, pollMs) {
14
+ return Math.max(1, Math.ceil(timeoutMs / pollMs));
15
+ }
16
+
17
+ function defaultSleep(ms) {
18
+ return new Promise((resolve) => setTimeout(resolve, ms));
19
+ }
20
+
21
+ function daemonState(isRunning, projectRoot) {
22
+ if (typeof isRunning !== "function") return null;
23
+ return Boolean(isRunning(projectRoot));
24
+ }
25
+
26
+ async function waitForDaemonState({
27
+ projectRoot,
28
+ isRunning,
29
+ desired,
30
+ timeoutMs,
31
+ pollMs,
32
+ sleep = defaultSleep,
33
+ }) {
34
+ if (typeof isRunning !== "function") return true;
35
+ const attempts = waitAttempts(timeoutMs, pollMs);
36
+ for (let i = 0; i <= attempts; i += 1) {
37
+ if (daemonState(isRunning, projectRoot) === desired) return true;
38
+ if (i >= attempts) break;
39
+ // eslint-disable-next-line no-await-in-loop
40
+ await sleep(pollMs);
41
+ }
42
+ return daemonState(isRunning, projectRoot) === desired;
43
+ }
44
+
45
+ function waitForDaemonStateSync({
46
+ projectRoot,
47
+ isRunning,
48
+ desired,
49
+ timeoutMs,
50
+ pollMs,
51
+ sleepSync,
52
+ }) {
53
+ if (typeof isRunning !== "function") return true;
54
+ const attempts = waitAttempts(timeoutMs, pollMs);
55
+ for (let i = 0; i <= attempts; i += 1) {
56
+ if (daemonState(isRunning, projectRoot) === desired) return true;
57
+ if (i >= attempts) break;
58
+ if (typeof sleepSync === "function") sleepSync(pollMs);
59
+ }
60
+ return daemonState(isRunning, projectRoot) === desired;
61
+ }
62
+
63
+ function createResult(overrides = {}) {
64
+ return {
65
+ ok: false,
66
+ stopped: false,
67
+ started: false,
68
+ connected: false,
69
+ stopOk: false,
70
+ startOk: false,
71
+ error: null,
72
+ ...overrides,
73
+ };
74
+ }
75
+
76
+ async function restartDaemonLifecycle(options = {}) {
77
+ const {
78
+ projectRoot,
79
+ isRunning,
80
+ stopDaemon,
81
+ startDaemon,
82
+ stopOptions,
83
+ startOptions,
84
+ connect,
85
+ requestStatus,
86
+ stopTimeoutMs = DEFAULT_STOP_TIMEOUT_MS,
87
+ startTimeoutMs = DEFAULT_START_TIMEOUT_MS,
88
+ pollMs = DEFAULT_POLL_MS,
89
+ sleep = defaultSleep,
90
+ } = options;
91
+
92
+ if (!projectRoot) return createResult({ error: "missing_project_root" });
93
+ if (typeof startDaemon !== "function") return createResult({ error: "missing_start_daemon" });
94
+
95
+ let stopOk = true;
96
+ let stopError = null;
97
+ if (typeof stopDaemon === "function") {
98
+ try {
99
+ stopOk = Boolean(stopOptions === undefined
100
+ ? await stopDaemon(projectRoot)
101
+ : await stopDaemon(projectRoot, stopOptions));
102
+ } catch (err) {
103
+ stopOk = false;
104
+ stopError = err;
105
+ }
106
+ }
107
+
108
+ const stopped = await waitForDaemonState({
109
+ projectRoot,
110
+ isRunning,
111
+ desired: false,
112
+ timeoutMs: normalizePositiveInt(stopTimeoutMs, DEFAULT_STOP_TIMEOUT_MS),
113
+ pollMs: normalizePositiveInt(pollMs, DEFAULT_POLL_MS),
114
+ sleep,
115
+ });
116
+
117
+ if (!stopped) {
118
+ return createResult({
119
+ stopped: false,
120
+ stopOk,
121
+ error: "failed_to_stop",
122
+ stopError,
123
+ });
124
+ }
125
+
126
+ let startOk = true;
127
+ let startError = null;
128
+ try {
129
+ if (startOptions === undefined) {
130
+ await startDaemon(projectRoot);
131
+ } else {
132
+ await startDaemon(projectRoot, startOptions);
133
+ }
134
+ } catch (err) {
135
+ startOk = false;
136
+ startError = err;
137
+ }
138
+
139
+ if (!startOk) {
140
+ return createResult({
141
+ stopped: true,
142
+ stopOk,
143
+ startOk: false,
144
+ error: "failed_to_start",
145
+ stopError,
146
+ startError,
147
+ });
148
+ }
149
+
150
+ const started = await waitForDaemonState({
151
+ projectRoot,
152
+ isRunning,
153
+ desired: true,
154
+ timeoutMs: normalizePositiveInt(startTimeoutMs, DEFAULT_START_TIMEOUT_MS),
155
+ pollMs: normalizePositiveInt(pollMs, DEFAULT_POLL_MS),
156
+ sleep,
157
+ });
158
+
159
+ let connected = started;
160
+ let connectError = null;
161
+ if (typeof connect === "function") {
162
+ try {
163
+ connected = Boolean(await connect(projectRoot));
164
+ } catch (err) {
165
+ connected = false;
166
+ connectError = err;
167
+ }
168
+ }
169
+
170
+ if (connected && typeof requestStatus === "function") {
171
+ try {
172
+ requestStatus(projectRoot);
173
+ } catch {
174
+ // Status refresh is best-effort after the lifecycle itself succeeds.
175
+ }
176
+ }
177
+
178
+ const ok = typeof connect === "function" ? connected : started;
179
+ return createResult({
180
+ ok,
181
+ stopped: true,
182
+ started,
183
+ connected,
184
+ stopOk,
185
+ startOk: true,
186
+ error: ok ? null : (started ? "failed_to_connect" : "failed_to_start"),
187
+ stopError,
188
+ startError,
189
+ connectError,
190
+ });
191
+ }
192
+
193
+ function restartDaemonLifecycleSync(options = {}) {
194
+ const {
195
+ projectRoot,
196
+ isRunning,
197
+ stopDaemon,
198
+ startDaemon,
199
+ stopOptions,
200
+ startOptions,
201
+ stopTimeoutMs = DEFAULT_STOP_TIMEOUT_MS,
202
+ startTimeoutMs = DEFAULT_START_TIMEOUT_MS,
203
+ pollMs = DEFAULT_POLL_MS,
204
+ sleepSync,
205
+ } = options;
206
+
207
+ if (!projectRoot) return createResult({ error: "missing_project_root" });
208
+ if (typeof startDaemon !== "function") return createResult({ error: "missing_start_daemon" });
209
+
210
+ let stopOk = true;
211
+ let stopError = null;
212
+ if (typeof stopDaemon === "function") {
213
+ try {
214
+ stopOk = Boolean(stopOptions === undefined
215
+ ? stopDaemon(projectRoot)
216
+ : stopDaemon(projectRoot, stopOptions));
217
+ } catch (err) {
218
+ stopOk = false;
219
+ stopError = err;
220
+ }
221
+ }
222
+
223
+ const stopped = waitForDaemonStateSync({
224
+ projectRoot,
225
+ isRunning,
226
+ desired: false,
227
+ timeoutMs: normalizePositiveInt(stopTimeoutMs, DEFAULT_STOP_TIMEOUT_MS),
228
+ pollMs: normalizePositiveInt(pollMs, DEFAULT_POLL_MS),
229
+ sleepSync,
230
+ });
231
+
232
+ if (!stopped) {
233
+ return createResult({
234
+ stopped: false,
235
+ stopOk,
236
+ error: "failed_to_stop",
237
+ stopError,
238
+ });
239
+ }
240
+
241
+ let startOk = true;
242
+ let startError = null;
243
+ try {
244
+ if (startOptions === undefined) {
245
+ startDaemon(projectRoot);
246
+ } else {
247
+ startDaemon(projectRoot, startOptions);
248
+ }
249
+ } catch (err) {
250
+ startOk = false;
251
+ startError = err;
252
+ }
253
+
254
+ if (!startOk) {
255
+ return createResult({
256
+ stopped: true,
257
+ stopOk,
258
+ startOk: false,
259
+ error: "failed_to_start",
260
+ stopError,
261
+ startError,
262
+ });
263
+ }
264
+
265
+ const started = waitForDaemonStateSync({
266
+ projectRoot,
267
+ isRunning,
268
+ desired: true,
269
+ timeoutMs: normalizePositiveInt(startTimeoutMs, DEFAULT_START_TIMEOUT_MS),
270
+ pollMs: normalizePositiveInt(pollMs, DEFAULT_POLL_MS),
271
+ sleepSync,
272
+ });
273
+
274
+ return createResult({
275
+ ok: started,
276
+ stopped: true,
277
+ started,
278
+ connected: false,
279
+ stopOk,
280
+ startOk: true,
281
+ error: started ? null : "failed_to_start",
282
+ stopError,
283
+ startError,
284
+ });
285
+ }
286
+
287
+ module.exports = {
288
+ DEFAULT_STOP_TIMEOUT_MS,
289
+ DEFAULT_START_TIMEOUT_MS,
290
+ DEFAULT_POLL_MS,
291
+ restartDaemonLifecycle,
292
+ restartDaemonLifecycleSync,
293
+ };
@@ -1,8 +1,25 @@
1
1
  const path = require("path");
2
2
  const { startDaemon, stopDaemon, isRunning } = require("./index");
3
+ const { restartDaemonLifecycleSync } = require("./restart");
3
4
  const { loadConfig, defaultAgentModelForProvider } = require("../../config");
4
5
  const { resolveNodeExecutable } = require("../process/nodeExecutable");
5
6
 
7
+ function spawnDaemonStart(projectRoot) {
8
+ const { spawn } = require("child_process");
9
+ const child = spawn(resolveNodeExecutable(), [path.join(__dirname, "..", "..", "..", "bin", "ufoo.js"), "daemon", "start"], {
10
+ detached: true,
11
+ stdio: "ignore",
12
+ env: { ...process.env, UFOO_DAEMON_CHILD: "1" },
13
+ cwd: projectRoot,
14
+ });
15
+ child.unref();
16
+ return child;
17
+ }
18
+
19
+ function sleepSync(ms) {
20
+ require("child_process").spawnSync("sleep", [String(ms / 1000)]);
21
+ }
22
+
6
23
  function runDaemonCli(argv) {
7
24
  const cmd = argv[1] || "start";
8
25
  const projectRoot = process.cwd();
@@ -19,14 +36,7 @@ function runDaemonCli(argv) {
19
36
  if (cmd === "start" || cmd === "--start") {
20
37
  if (isRunning(projectRoot)) return;
21
38
  if (!process.env.UFOO_DAEMON_CHILD) {
22
- const { spawn } = require("child_process");
23
- const child = spawn(resolveNodeExecutable(), [path.join(__dirname, "..", "..", "..", "bin", "ufoo.js"), "daemon", "start"], {
24
- detached: true,
25
- stdio: "ignore",
26
- env: { ...process.env, UFOO_DAEMON_CHILD: "1" },
27
- cwd: projectRoot,
28
- });
29
- child.unref();
39
+ spawnDaemonStart(projectRoot);
30
40
  return;
31
41
  }
32
42
  startDaemon({ projectRoot, provider, model, resumeMode });
@@ -39,35 +49,19 @@ function runDaemonCli(argv) {
39
49
  return;
40
50
  }
41
51
  if (cmd === "restart" || cmd === "--restart") {
42
- // Stop if running
43
- if (isRunning(projectRoot)) {
44
- const stopped = stopDaemon(projectRoot, { source: process.env.UFOO_DAEMON_STOP_SOURCE || `daemon-cli:${cmd} pid=${process.pid}` });
45
- // Wait for clean shutdown
46
- let attempts = 0;
47
- while (isRunning(projectRoot) && attempts < 50) {
48
- attempts++;
49
- require("child_process").spawnSync("sleep", ["0.1"]);
50
- }
51
- if (!stopped && isRunning(projectRoot)) {
52
- process.exitCode = 1;
53
- return;
54
- }
55
- }
56
- // Start fresh daemon
57
- if (!process.env.UFOO_DAEMON_CHILD) {
58
- const { spawn } = require("child_process");
59
- const childEnv = { ...process.env, UFOO_DAEMON_CHILD: "1" };
60
- const child = spawn(resolveNodeExecutable(), [path.join(__dirname, "..", "..", "..", "bin", "ufoo.js"), "daemon", "start"], {
61
- detached: true,
62
- stdio: "ignore",
63
- env: childEnv,
64
- cwd: projectRoot,
65
- });
66
- child.unref();
67
- return;
68
- }
69
- // Manual restart does not auto-resume; crash-recovery is handled on next auto start with stale lock detection.
70
- startDaemon({ projectRoot, provider, model, resumeMode: "none" });
52
+ const result = restartDaemonLifecycleSync({
53
+ projectRoot,
54
+ isRunning,
55
+ stopDaemon,
56
+ startDaemon: () => {
57
+ if (!process.env.UFOO_DAEMON_CHILD) return spawnDaemonStart(projectRoot);
58
+ // Manual restart does not auto-resume; crash-recovery is handled on next auto start with stale lock detection.
59
+ return startDaemon({ projectRoot, provider, model, resumeMode: "none" });
60
+ },
61
+ stopOptions: { source: process.env.UFOO_DAEMON_STOP_SOURCE || `daemon-cli:${cmd} pid=${process.pid}` },
62
+ sleepSync,
63
+ });
64
+ if (!result.ok) process.exitCode = 1;
71
65
  return;
72
66
  }
73
67
  if (cmd === "status" || cmd === "--status") {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Terminal detection and feature modules.
2
+ * Terminal detection and feature helpers.
3
3
  */
4
4
 
5
5
  const detect = require("./detect");
@@ -105,9 +105,8 @@ layout, hooks, and proper isolation of pure logic from rendering.
105
105
 
106
106
  ### Smoke
107
107
 
108
- - [ ] `node scripts/ucode-app-smoke.js` exits 0.
109
- - [ ] `npx jest --silent` shows the pre-existing 5 OAuth failures only;
110
- every ink suite passes.
108
+ - [ ] `npx jest --runTestsByPath test/unit/ui/UcodeApp.test.js test/unit/code/ucodeTui.test.js --runInBand` passes.
109
+ - [ ] `npx jest --silent` passes; every ink suite stays green.
111
110
 
112
111
  ## Decision log
113
112
 
@@ -118,9 +117,9 @@ layout, hooks, and proper isolation of pure logic from rendering.
118
117
  avoids a build step. We can revisit if any single component grows
119
118
  past ~600 lines and readability suffers.
120
119
  - **Don't enable `--experimental-vm-modules` for jest.** The risk of
121
- surprise ESM behaviour across the existing 1800-test suite is too
122
- high. Render coverage stays in `scripts/*-smoke.js`; component logic
123
- is exercised by pure-function tests.
120
+ surprise ESM behaviour across the existing test suite is too high.
121
+ Component logic is exercised by pure-function tests; real TTY render
122
+ coverage remains a manual smoke step.
124
123
  - **Codex isn't a useful reference.** Its TUI is a Rust ratatui app
125
124
  (`codex-rs/tui`), not React-based. The architectural principle worth
126
125
  borrowing is its hard split between TUI and core protocol.
@@ -307,10 +306,9 @@ and `shouldEchoCommandInChat(text)` are pure.
307
306
  the same as ucode.
308
307
 
309
308
  ### Smoke
310
- - [ ] `node scripts/chat-app-smoke.js` exits 0.
311
- - [ ] `node scripts/ucode-app-smoke.js` exits 0.
312
- - [ ] `npx jest --silent` shows the pre-existing 5 OAuth failures
313
- only; every ink suite passes.
309
+ - [ ] `npx jest --runTestsByPath test/unit/ui/ChatApp.test.js test/unit/ui/chatReducer.test.js --runInBand` passes.
310
+ - [ ] `npx jest --runTestsByPath test/unit/ui/UcodeApp.test.js test/unit/code/ucodeTui.test.js --runInBand` passes.
311
+ - [ ] `npx jest --silent` passes; every ink suite stays green.
314
312
 
315
313
  ## P4 close-out
316
314
 
@@ -23,6 +23,7 @@ const fmt = require("../format");
23
23
  const { createMultilineInput } = require("./MultilineInput");
24
24
  const { createDashboardBar } = require("./DashboardBar");
25
25
  const { reducer, createInitialState } = require("./chatReducer");
26
+ const { restartDaemonLifecycle } = require("../../runtime/daemon/restart");
26
27
 
27
28
  function bootstrapEnvironment(projectRoot, options = {}) {
28
29
  // Ensure ufoo dirs exist and that we have a stable subscriber ID.
@@ -1813,11 +1814,15 @@ function createChatApp({ React, ink, props, interactive = true }) {
1813
1814
  return;
1814
1815
  }
1815
1816
  try { if (conn && typeof conn.close === "function") conn.close(); } catch { /* ignore */ }
1816
- transportStopDaemon(targetRoot, { source: "ink-command:/daemon restart" });
1817
- transportStartDaemon(targetRoot);
1818
- if (targetRoot === (currentProjectRootRef.current || props.projectRoot) && conn && typeof conn.connect === "function") {
1819
- await conn.connect();
1820
- }
1817
+ await restartDaemonLifecycle({
1818
+ projectRoot: targetRoot,
1819
+ isRunning: props.env && typeof props.env.isRunning === "function" ? props.env.isRunning : null,
1820
+ stopDaemon: (daemonRoot) => transportStopDaemon(daemonRoot, { source: "ink-command:/daemon restart" }),
1821
+ startDaemon: (daemonRoot) => transportStartDaemon(daemonRoot),
1822
+ connect: targetRoot === (currentProjectRootRef.current || props.projectRoot) && conn && typeof conn.connect === "function"
1823
+ ? () => conn.connect()
1824
+ : null,
1825
+ });
1821
1826
  },
1822
1827
  send: (req) => { try { if (conn && typeof conn.send === "function") conn.send(req); } catch { /* ignore */ } },
1823
1828
  requestStatus: requestDaemonStatus,
@@ -3544,7 +3549,7 @@ async function runChatInk(projectRoot, options = {}) {
3544
3549
  const repoRoot = path.join(__dirname, "..", "..", "..");
3545
3550
  const init = new env.UfooInit(repoRoot);
3546
3551
  await init.init({
3547
- modules: "context,bus",
3552
+ targets: "context,bus",
3548
3553
  project: projectRoot,
3549
3554
  controllerMode: env.globalMode,
3550
3555
  });
@@ -3591,6 +3596,7 @@ async function runChatInk(projectRoot, options = {}) {
3591
3596
  daemonConnection,
3592
3597
  stopDaemon,
3593
3598
  startDaemon,
3599
+ isDaemonRunning: env.isRunning,
3594
3600
  logMessage: () => {},
3595
3601
  queueStatusLine: () => {},
3596
3602
  resolveStatusLine: () => {},
package/bin/ucode-core.js DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { runUcodeCoreCli } = require("../src/code/cli");
4
-
5
- (async () => {
6
- const argv = process.argv.slice(2);
7
- const result = await runUcodeCoreCli({ argv, projectRoot: process.cwd() });
8
- if (result && typeof result.output === "string" && result.output) {
9
- process.stdout.write(result.output);
10
- }
11
- process.exit(typeof result.exitCode === "number" ? result.exitCode : 0);
12
- })().catch((err) => {
13
- process.stderr.write(`${err && err.message ? err.message : "ucode-core failed"}\n`);
14
- process.exit(1);
15
- });
package/bin/ufoo DELETED
@@ -1,71 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
-
6
- usage() {
7
- cat <<'USAGE'
8
- ufoo
9
-
10
- Monorepo layout:
11
- ~/.ufoo/ (clone of ufoo)
12
- modules/ai-context/ (protocol module)
13
- modules/ai-resources/ (optional resources)
14
-
15
- Usage:
16
- ufoo doctor
17
- ufoo status
18
- ufoo daemon --start|--stop|--status
19
- ufoo chat
20
- ufoo init [--modules context[,resources]] [--project <dir>]
21
- ufoo bus <...> # JS bus implementation (project-local data)
22
- ufoo ctx <...> # JS context commands (doctor|lint|decisions)
23
- ufoo skills list
24
- ufoo skills install <name|all> [--codex|--agents|--target <dir>]
25
-
26
- Notes:
27
- - `init` creates/updates <project>/.ai-context and injects CLAUDE.md/AGENTS.md blocks.
28
- - For Codex notifications, prefer `ufoo bus alert` / `ufoo bus listen` (no IME issues).
29
- USAGE
30
- }
31
-
32
- cmd="${1:-}"
33
- shift || true
34
-
35
- case "$cmd" in
36
- ""|--help|-h)
37
- exec node "$repo_root/bin/ufoo.js" chat
38
- ;;
39
- doctor)
40
- exec node "$repo_root/bin/ufoo.js" doctor "$@"
41
- ;;
42
- status)
43
- exec node "$repo_root/bin/ufoo.js" status "$@"
44
- ;;
45
- daemon)
46
- exec node "$repo_root/bin/ufoo.js" daemon "$@"
47
- ;;
48
- chat)
49
- exec node "$repo_root/bin/ufoo.js" chat
50
- ;;
51
- init)
52
- exec node "$repo_root/bin/ufoo.js" init "$@"
53
- ;;
54
- bus)
55
- # Project-local bus commands. Must be run from within the target project.
56
- exec node "$repo_root/bin/ufoo.js" bus "$@"
57
- ;;
58
- ctx)
59
- exec node "$repo_root/bin/ufoo.js" ctx "$@"
60
- ;;
61
- skills)
62
- sub="${1:-}"
63
- shift || true
64
- exec node "$repo_root/bin/ufoo.js" skills "$sub" "$@"
65
- ;;
66
- *)
67
- echo "Unknown command: $cmd" >&2
68
- usage >&2
69
- exit 1
70
- ;;
71
- esac
@@ -1,8 +0,0 @@
1
- <!-- ufoo -->
2
- ## ufoo Agent Protocol
3
-
4
- > **Default: do not write a decision.** Record one only for important, plan-level knowledge that should constrain future work: architectural choices, trade-off analysis, cross-agent coordination, or precedent-setting integration contracts. NOT for routine findings, simple fixes, or because the user asked for a plan/evaluation/recommendation. Durable project facts belong in shared memory, not decisions. → `ufoo ctx decisions new "Title"` BEFORE acting only when that high bar is met.
5
- > **Read shared memory before writing it.** Durable facts live in `.ufoo/memory/`; use `ufoo memory list/show`, `recall`, `search_memory`, or redacted `search_history` evidence before `remember` / `edit_memory`.
6
- > **Auto-execute bus messages.** On `ubus`: execute tasks immediately, then `ufoo bus ack`. Never ask the user.
7
- > **Full protocol**: `/ufoo` skill (auto-loaded on session start). Docs: `.ufoo/docs/`
8
- <!-- /ufoo -->