u-foo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +163 -0
  3. package/README.zh-CN.md +163 -0
  4. package/bin/uclaude +65 -0
  5. package/bin/ucodex +65 -0
  6. package/bin/ufoo +93 -0
  7. package/bin/ufoo.js +35 -0
  8. package/modules/AGENTS.template.md +87 -0
  9. package/modules/bus/README.md +132 -0
  10. package/modules/bus/SKILLS/ubus/SKILL.md +209 -0
  11. package/modules/bus/scripts/bus-alert.sh +185 -0
  12. package/modules/bus/scripts/bus-listen.sh +117 -0
  13. package/modules/context/ASSUMPTIONS.md +7 -0
  14. package/modules/context/CONSTRAINTS.md +7 -0
  15. package/modules/context/CONTEXT-STRUCTURE.md +49 -0
  16. package/modules/context/DECISION-PROTOCOL.md +62 -0
  17. package/modules/context/HANDOFF.md +33 -0
  18. package/modules/context/README.md +82 -0
  19. package/modules/context/RULES.md +15 -0
  20. package/modules/context/SKILLS/README.md +14 -0
  21. package/modules/context/SKILLS/uctx/SKILL.md +91 -0
  22. package/modules/context/SYSTEM.md +18 -0
  23. package/modules/context/TEMPLATES/assumptions.md +4 -0
  24. package/modules/context/TEMPLATES/constraints.md +4 -0
  25. package/modules/context/TEMPLATES/decision.md +16 -0
  26. package/modules/context/TEMPLATES/project-context-readme.md +6 -0
  27. package/modules/context/TEMPLATES/system.md +3 -0
  28. package/modules/context/TEMPLATES/terminology.md +4 -0
  29. package/modules/context/TERMINOLOGY.md +10 -0
  30. package/modules/resources/ICONS/README.md +12 -0
  31. package/modules/resources/ICONS/libraries/README.md +17 -0
  32. package/modules/resources/ICONS/libraries/heroicons/LICENSE +22 -0
  33. package/modules/resources/ICONS/libraries/heroicons/README.md +15 -0
  34. package/modules/resources/ICONS/libraries/heroicons/arrow-right.svg +4 -0
  35. package/modules/resources/ICONS/libraries/heroicons/check.svg +4 -0
  36. package/modules/resources/ICONS/libraries/heroicons/chevron-down.svg +4 -0
  37. package/modules/resources/ICONS/libraries/heroicons/cog-6-tooth.svg +5 -0
  38. package/modules/resources/ICONS/libraries/heroicons/magnifying-glass.svg +4 -0
  39. package/modules/resources/ICONS/libraries/heroicons/x-mark.svg +4 -0
  40. package/modules/resources/ICONS/libraries/lucide/LICENSE +40 -0
  41. package/modules/resources/ICONS/libraries/lucide/README.md +15 -0
  42. package/modules/resources/ICONS/libraries/lucide/arrow-right.svg +15 -0
  43. package/modules/resources/ICONS/libraries/lucide/check.svg +14 -0
  44. package/modules/resources/ICONS/libraries/lucide/chevron-down.svg +14 -0
  45. package/modules/resources/ICONS/libraries/lucide/search.svg +15 -0
  46. package/modules/resources/ICONS/libraries/lucide/settings.svg +15 -0
  47. package/modules/resources/ICONS/libraries/lucide/x.svg +15 -0
  48. package/modules/resources/ICONS/rules.md +7 -0
  49. package/modules/resources/README.md +9 -0
  50. package/modules/resources/UI/ANTI-PATTERNS.md +6 -0
  51. package/modules/resources/UI/TONE.md +6 -0
  52. package/package.json +40 -0
  53. package/scripts/banner.sh +89 -0
  54. package/scripts/bus-alert.sh +6 -0
  55. package/scripts/bus-autotrigger.sh +6 -0
  56. package/scripts/bus-daemon.sh +231 -0
  57. package/scripts/bus-inject.sh +144 -0
  58. package/scripts/bus-listen.sh +6 -0
  59. package/scripts/bus.sh +984 -0
  60. package/scripts/context-decisions.sh +167 -0
  61. package/scripts/context-doctor.sh +72 -0
  62. package/scripts/context-lint.sh +110 -0
  63. package/scripts/doctor.sh +22 -0
  64. package/scripts/init.sh +247 -0
  65. package/scripts/skills.sh +113 -0
  66. package/scripts/status.sh +125 -0
  67. package/src/agent/cliRunner.js +190 -0
  68. package/src/agent/internalRunner.js +212 -0
  69. package/src/agent/normalizeOutput.js +41 -0
  70. package/src/agent/ufooAgent.js +222 -0
  71. package/src/chat/index.js +1603 -0
  72. package/src/cli.js +349 -0
  73. package/src/config.js +37 -0
  74. package/src/daemon/index.js +501 -0
  75. package/src/daemon/ops.js +120 -0
  76. package/src/daemon/run.js +41 -0
  77. package/src/daemon/status.js +78 -0
package/src/cli.js ADDED
@@ -0,0 +1,349 @@
1
+ const path = require("path");
2
+ const { spawnSync } = require("child_process");
3
+
4
+ function getPackageRoot() {
5
+ return path.resolve(__dirname, "..");
6
+ }
7
+
8
+ function run(cmd, args, options = {}) {
9
+ const res = spawnSync(cmd, args, {
10
+ stdio: "inherit",
11
+ ...options,
12
+ });
13
+ if (res.error) throw res.error;
14
+ if (typeof res.status === "number" && res.status !== 0) {
15
+ const e = new Error(`${cmd} exited with code ${res.status}`);
16
+ e.code = res.status;
17
+ throw e;
18
+ }
19
+ }
20
+
21
+ function getPackageScript(rel) {
22
+ return path.join(getPackageRoot(), rel);
23
+ }
24
+
25
+ function requireOptional(name) {
26
+ try {
27
+ // eslint-disable-next-line global-require, import/no-dynamic-require
28
+ return require(name);
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ async function runCli(argv) {
35
+ const pkg = require(path.resolve(getPackageRoot(), "package.json"));
36
+
37
+ const commander = requireOptional("commander");
38
+ const chalk = requireOptional("chalk") || { cyan: (s) => s, red: (s) => s };
39
+
40
+ if (commander && commander.Command) {
41
+ const { Command } = commander;
42
+ const program = new Command();
43
+
44
+ program
45
+ .name("ufoo")
46
+ .description("ufoo CLI (wrapper-first; prefers project-local scripts).")
47
+ .version(pkg.version);
48
+
49
+ program
50
+ .command("doctor")
51
+ .description("Run repo doctor checks")
52
+ .action(() => {
53
+ const repoRoot = getPackageRoot();
54
+ run("bash", [path.join(repoRoot, "scripts/doctor.sh")]);
55
+ });
56
+ program
57
+ .command("status")
58
+ .description("Show project status (banner, unread bus, open decisions)")
59
+ .action(() => {
60
+ const repoRoot = getPackageRoot();
61
+ run("bash", [path.join(repoRoot, "scripts/status.sh")]);
62
+ });
63
+ program
64
+ .command("daemon")
65
+ .description("Start/stop ufoo daemon")
66
+ .option("--start", "Start daemon")
67
+ .option("--stop", "Stop daemon")
68
+ .option("--status", "Check daemon status")
69
+ .action((opts) => {
70
+ const repoRoot = getPackageRoot();
71
+ const args = ["daemon"];
72
+ if (opts.start) args.push("start");
73
+ else if (opts.stop) args.push("stop");
74
+ else if (opts.status) args.push("status");
75
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), ...args]);
76
+ });
77
+ program
78
+ .command("chat")
79
+ .description("Launch ufoo chat UI")
80
+ .action(() => {
81
+ const repoRoot = getPackageRoot();
82
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "chat"]);
83
+ });
84
+
85
+ program
86
+ .command("init")
87
+ .description("Initialize modules in a project")
88
+ .option("--modules <list>", "Comma-separated modules (context,bus,resources)", "context")
89
+ .option("--project <dir>", "Target project directory", process.cwd())
90
+ .action((opts) => {
91
+ const repoRoot = getPackageRoot();
92
+ run("bash", [
93
+ path.join(repoRoot, "scripts/init.sh"),
94
+ "--modules",
95
+ opts.modules,
96
+ "--project",
97
+ opts.project,
98
+ ]);
99
+ });
100
+
101
+ const skills = program.command("skills").description("Manage skills templates");
102
+ skills
103
+ .command("list")
104
+ .description("List available skills")
105
+ .action(() => {
106
+ const repoRoot = getPackageRoot();
107
+ run("bash", [path.join(repoRoot, "scripts/skills.sh"), "list"]);
108
+ });
109
+ skills
110
+ .command("install")
111
+ .description("Install one skill or all skills")
112
+ .argument("<name>", "Skill name or 'all'")
113
+ .option("--target <dir>", "Install target directory")
114
+ .option("--codex", "Install into ~/.codex/skills")
115
+ .option("--agents", "Install into ~/.agents/skills")
116
+ .action((name, opts) => {
117
+ 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);
123
+ });
124
+
125
+ const bus = program.command("bus").description("Project bus commands");
126
+ bus
127
+ .command("alert")
128
+ .description("Start/stop background notification daemon")
129
+ .argument("<subscriber>", "Subscriber ID (e.g., claude-code:abc123)")
130
+ .argument("[interval]", "Poll interval in seconds", "2")
131
+ .option("--notify", "Enable macOS Notification Center")
132
+ .option("--daemon", "Run in background")
133
+ .option("--stop", "Stop running alert for this subscriber")
134
+ .option("--no-title", "Disable terminal title badge")
135
+ .option("--no-bell", "Disable terminal bell")
136
+ .allowUnknownOption(true)
137
+ .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);
146
+ });
147
+ bus
148
+ .command("listen")
149
+ .description("Foreground listener for incoming messages")
150
+ .argument("<subscriber>", "Subscriber ID")
151
+ .option("--from-beginning", "Print existing queued messages first")
152
+ .option("--reset", "Truncate pending queue before listening")
153
+ .option("--auto-join", "Auto-join bus to get subscriber ID")
154
+ .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);
161
+ });
162
+ bus
163
+ .command("daemon")
164
+ .description("Start/stop daemon that auto-injects /bus into terminals")
165
+ .option("--interval <n>", "Poll interval in seconds", "2")
166
+ .option("--daemon", "Run in background")
167
+ .option("--stop", "Stop running daemon")
168
+ .option("--status", "Check daemon status")
169
+ .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);
177
+ });
178
+ bus
179
+ .command("inject")
180
+ .description("Inject /bus into a Terminal.app tab by subscriber ID")
181
+ .argument("<subscriber>", "Subscriber ID to inject into")
182
+ .action((subscriber) => {
183
+ const script = getPackageScript("scripts/bus-inject.sh");
184
+ run("bash", [script, subscriber]);
185
+ });
186
+ bus
187
+ .command("run", { isDefault: true })
188
+ .description("Run bus.sh commands (join, check, send, status, etc.)")
189
+ .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]);
194
+ });
195
+
196
+ program
197
+ .command("ctx")
198
+ .description("Project ctx commands (delegates to ./scripts/context-*.sh)")
199
+ .argument("[subcmd]", "Subcommand (doctor|lint|decisions)", "doctor")
200
+ .allowUnknownOption(true)
201
+ .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
+ );
215
+ process.exitCode = 1;
216
+ return;
217
+ }
218
+ const script = getPackageScript(rel);
219
+ run("bash", [script, ...subargs]);
220
+ });
221
+
222
+ program.addHelpText(
223
+ "after",
224
+ `\nNotes:\n - If 'ufoo' isn't in PATH, run it via ${chalk.cyan(
225
+ "./bin/ufoo"
226
+ )} (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`
229
+ );
230
+
231
+ await program.parseAsync(argv);
232
+ return;
233
+ }
234
+
235
+ // Dependency-free fallback parser (good for local testing without npm install).
236
+ const cmd = argv[2] || "";
237
+ const rest = argv.slice(3);
238
+ const repoRoot = getPackageRoot();
239
+
240
+ const help = () => {
241
+ console.log(`ufoo ${pkg.version}`);
242
+ console.log("");
243
+ console.log("Usage:");
244
+ console.log(" ufoo doctor");
245
+ console.log(" ufoo status");
246
+ console.log(" ufoo daemon --start|--stop|--status");
247
+ console.log(" ufoo chat");
248
+ console.log(" ufoo init [--modules <list>] [--project <dir>]");
249
+ console.log(" ufoo skills list");
250
+ 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)");
253
+ console.log("");
254
+ console.log("Notes:");
255
+ console.log(" - For Codex notifications, use scripts/bus-alert.sh / scripts/bus-listen.sh");
256
+ };
257
+
258
+ if (cmd === "" || cmd === "--help" || cmd === "-h") {
259
+ help();
260
+ return;
261
+ }
262
+
263
+ if (cmd === "doctor") {
264
+ run("bash", [path.join(repoRoot, "scripts/doctor.sh")]);
265
+ return;
266
+ }
267
+ if (cmd === "status") {
268
+ run("bash", [path.join(repoRoot, "scripts/status.sh")]);
269
+ return;
270
+ }
271
+ if (cmd === "daemon") {
272
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "daemon", ...rest]);
273
+ return;
274
+ }
275
+ if (cmd === "chat") {
276
+ run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), "chat"]);
277
+ return;
278
+ }
279
+ if (cmd === "init") {
280
+ const getOpt = (name, def) => {
281
+ const i = rest.indexOf(name);
282
+ if (i === -1) return def;
283
+ if (i + 1 >= rest.length) throw new Error(`Missing value for ${name}`);
284
+ return rest[i + 1];
285
+ };
286
+ run("bash", [
287
+ path.join(repoRoot, "scripts/init.sh"),
288
+ "--modules",
289
+ getOpt("--modules", "context"),
290
+ "--project",
291
+ getOpt("--project", process.cwd()),
292
+ ]);
293
+ return;
294
+ }
295
+ if (cmd === "skills") {
296
+ const sub = rest[0] || "";
297
+ if (sub === "list") {
298
+ run("bash", [path.join(repoRoot, "scripts/skills.sh"), "list"]);
299
+ return;
300
+ }
301
+ if (sub === "install") {
302
+ const name = rest[1];
303
+ if (!name) throw new Error("skills install requires <name|all>");
304
+ run("bash", [path.join(repoRoot, "scripts/skills.sh"), "install", ...rest.slice(1)]);
305
+ return;
306
+ }
307
+ help();
308
+ process.exitCode = 1;
309
+ return;
310
+ }
311
+ if (cmd === "bus") {
312
+ const sub = rest[0] || "";
313
+ if (sub === "alert") {
314
+ run("bash", [getPackageScript("scripts/bus-alert.sh"), ...rest.slice(1)]);
315
+ return;
316
+ }
317
+ if (sub === "listen") {
318
+ run("bash", [getPackageScript("scripts/bus-listen.sh"), ...rest.slice(1)]);
319
+ return;
320
+ }
321
+ if (sub === "daemon") {
322
+ run("bash", [getPackageScript("scripts/bus-daemon.sh"), ...rest.slice(1)]);
323
+ return;
324
+ }
325
+ if (sub === "inject") {
326
+ run("bash", [getPackageScript("scripts/bus-inject.sh"), ...rest.slice(1)]);
327
+ return;
328
+ }
329
+ run("bash", [getPackageScript("scripts/bus.sh"), ...rest]);
330
+ return;
331
+ }
332
+ if (cmd === "ctx") {
333
+ 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)]);
342
+ return;
343
+ }
344
+
345
+ help();
346
+ process.exitCode = 1;
347
+ }
348
+
349
+ module.exports = { runCli };
package/src/config.js ADDED
@@ -0,0 +1,37 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const DEFAULT_CONFIG = {
5
+ launchMode: "terminal",
6
+ };
7
+
8
+ function normalizeLaunchMode(value) {
9
+ return value === "internal" ? "internal" : "terminal";
10
+ }
11
+
12
+ function configPath(projectRoot) {
13
+ return path.join(projectRoot, ".ufoo", "config.json");
14
+ }
15
+
16
+ function loadConfig(projectRoot) {
17
+ try {
18
+ const raw = JSON.parse(fs.readFileSync(configPath(projectRoot), "utf8"));
19
+ return { ...DEFAULT_CONFIG, ...raw, launchMode: normalizeLaunchMode(raw.launchMode) };
20
+ } catch {
21
+ return { ...DEFAULT_CONFIG };
22
+ }
23
+ }
24
+
25
+ function saveConfig(projectRoot, config) {
26
+ const target = configPath(projectRoot);
27
+ fs.mkdirSync(path.dirname(target), { recursive: true });
28
+ const merged = {
29
+ ...DEFAULT_CONFIG,
30
+ ...config,
31
+ };
32
+ merged.launchMode = normalizeLaunchMode(merged.launchMode);
33
+ fs.writeFileSync(target, JSON.stringify(merged, null, 2));
34
+ return merged;
35
+ }
36
+
37
+ module.exports = { loadConfig, saveConfig, normalizeLaunchMode };