@0dai-dev/cli 2.3.1 → 2.4.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 (2) hide show
  1. package/bin/0dai.js +168 -13
  2. package/package.json +1 -1
package/bin/0dai.js CHANGED
@@ -7,7 +7,7 @@ const fs = require("fs");
7
7
  const path = require("path");
8
8
  const os = require("os");
9
9
 
10
- const VERSION = "2.3.0";
10
+ const VERSION = "2.4.0";
11
11
  const API_URL = process.env.ODAI_API_URL || "https://api.0dai.dev";
12
12
  const T = process.stdout.isTTY ? "\x1b[38;2;45;212;168m" : ""; // teal
13
13
  const R = process.stdout.isTTY ? "\x1b[0m" : ""; // reset
@@ -262,19 +262,172 @@ async function cmdDetect(target) {
262
262
  function cmdDoctor(target) {
263
263
  const ai = path.join(target, "ai");
264
264
  if (!fs.existsSync(ai)) { log("no ai/ layer. Run '0dai init' first."); return; }
265
- let v = "?";
265
+ let v = "?", stack = "generic";
266
266
  try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
267
- const checks = {
268
- "ai/VERSION": fs.existsSync(path.join(ai, "VERSION")),
269
- "ai/manifest/project.yaml": fs.existsSync(path.join(ai, "manifest", "project.yaml")),
270
- "ai/manifest/commands.yaml": fs.existsSync(path.join(ai, "manifest", "commands.yaml")),
271
- "ai/manifest/discovery.json": fs.existsSync(path.join(ai, "manifest", "discovery.json")),
272
- ".claude/settings.json": fs.existsSync(path.join(target, ".claude", "settings.json")),
273
- "AGENTS.md": fs.existsSync(path.join(target, "AGENTS.md")),
267
+ try { stack = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")).stack || "generic"; } catch {}
268
+
269
+ const W = process.stdout.isTTY ? "\x1b[33m" : ""; // yellow
270
+ const E = process.stdout.isTTY ? "\x1b[31m" : ""; // red
271
+ const G = process.stdout.isTTY ? "\x1b[32m" : ""; // green
272
+ const R2 = process.stdout.isTTY ? "\x1b[0m" : "";
273
+
274
+ // --- ai/ layer checks ---
275
+ const layerChecks = {
276
+ "ai/VERSION": { path: path.join(ai, "VERSION"), sev: "error" },
277
+ "ai/manifest/project.yaml": { path: path.join(ai, "manifest", "project.yaml"), sev: "error" },
278
+ "ai/manifest/commands.yaml": { path: path.join(ai, "manifest", "commands.yaml"), sev: "warn" },
279
+ "ai/manifest/discovery.json": { path: path.join(ai, "manifest", "discovery.json"),sev: "warn" },
280
+ ".claude/settings.json": { path: path.join(target, ".claude", "settings.json"), sev: "warn" },
281
+ "AGENTS.md": { path: path.join(target, "AGENTS.md"), sev: "warn" },
274
282
  };
275
- const ok = Object.values(checks).every(Boolean);
276
- log(`v${v} ${ok ? "healthy" : "issues found"}`);
277
- for (const [name, exists] of Object.entries(checks)) console.log(` ${exists ? "ok" : "MISSING"}: ${name}`);
283
+
284
+ // --- credentials checklist ---
285
+ const credChecks = [
286
+ { name: "ANTHROPIC_API_KEY", env: "ANTHROPIC_API_KEY", needed: true, sev: "error", hint: "Required for Claude Code — get at console.anthropic.com" },
287
+ { name: "OPENAI_API_KEY", env: "OPENAI_API_KEY", needed: false, sev: "warn", hint: "Required for Codex CLI — get at platform.openai.com" },
288
+ { name: "GITHUB_TOKEN", env: "GITHUB_TOKEN", needed: false, sev: "warn", hint: "Needed for gh CLI, PR creation, swarm delegation" },
289
+ ];
290
+
291
+ // Stack-specific creds
292
+ if (stack.includes("vercel") || stack.includes("next")) {
293
+ credChecks.push({ name: "VERCEL_TOKEN", env: "VERCEL_TOKEN", needed: false, sev: "warn", hint: "Required for Vercel deployments — get at vercel.com/account/tokens" });
294
+ }
295
+ if (stack.includes("aws") || stack.includes("lambda") || stack.includes("cdk")) {
296
+ credChecks.push({ name: "AWS_ACCESS_KEY_ID", env: "AWS_ACCESS_KEY_ID", needed: false, sev: "warn", hint: "Required for AWS deployments" });
297
+ }
298
+ if (stack.includes("gcp") || stack.includes("firebase") || stack.includes("flutter")) {
299
+ credChecks.push({ name: "GOOGLE_APPLICATION_CREDENTIALS", env: "GOOGLE_APPLICATION_CREDENTIALS", needed: false, sev: "warn", hint: "Required for GCP/Firebase deployments" });
300
+ }
301
+
302
+ // --- run checks ---
303
+ let errors = 0, warnings = 0;
304
+ log(`v${v} | stack: ${stack}\n`);
305
+
306
+ console.log(" ai/ layer:");
307
+ for (const [name, { path: p, sev }] of Object.entries(layerChecks)) {
308
+ const exists = fs.existsSync(p);
309
+ if (!exists) sev === "error" ? errors++ : warnings++;
310
+ const mark = exists ? `${G}ok${R2}` : sev === "error" ? `${E}MISSING${R2}` : `${W}missing${R2}`;
311
+ console.log(` ${mark.padEnd(22)} ${name}`);
312
+ }
313
+
314
+ console.log("\n credentials:");
315
+ for (const c of credChecks) {
316
+ const present = !!process.env[c.env];
317
+ if (!present) c.sev === "error" ? errors++ : warnings++;
318
+ const mark = present ? `${G}set${R2}` : c.sev === "error" ? `${E}NOT SET${R2}` : `${W}not set${R2}`;
319
+ console.log(` ${mark.padEnd(22)} ${c.name}${!present ? `\n ${D}→ ${c.hint}${R2}` : ""}`);
320
+ }
321
+
322
+ // --- swarm check ---
323
+ const swarmDir = path.join(ai, "swarm");
324
+ const countDir = (d) => { try { return fs.readdirSync(d).filter(f => f.endsWith(".json")).length; } catch { return 0; } };
325
+ const qCount = countDir(path.join(swarmDir, "queue"));
326
+ const dCount = countDir(path.join(swarmDir, "done"));
327
+ if (qCount || dCount) {
328
+ console.log(`\n swarm: ${qCount} queued, ${dCount} done`);
329
+ if (qCount) console.log(` ${W}→ run '0dai reflect' to review pending tasks${R2}`);
330
+ }
331
+
332
+ const summary = errors ? `${E}${errors} error(s)${R2}` : warnings ? `${W}${warnings} warning(s)${R2}` : `${G}healthy${R2}`;
333
+ console.log(`\n status: ${summary}`);
334
+ if (errors) process.exitCode = 1;
335
+ }
336
+
337
+ // --- Session reflection --- (dogfood feedback #36)
338
+ function cmdReflect(target, args) {
339
+ const ai = path.join(target, "ai");
340
+ const W = process.stdout.isTTY ? "\x1b[33m" : "";
341
+ const G = process.stdout.isTTY ? "\x1b[32m" : "";
342
+ const R2 = process.stdout.isTTY ? "\x1b[0m" : "";
343
+ const B = process.stdout.isTTY ? "\x1b[1m" : "";
344
+
345
+ // Collect swarm done tasks
346
+ const doneDir = path.join(ai, "swarm", "done");
347
+ const queueDir = path.join(ai, "swarm", "queue");
348
+ const activeDir = path.join(ai, "swarm", "active");
349
+ const doneTasks = [], queueTasks = [];
350
+
351
+ // -- how many days to look back (default 7)
352
+ const daysArg = args.find((_, i) => args[i - 1] === "--days");
353
+ const days = parseInt(daysArg || "7");
354
+ const since = Date.now() - days * 24 * 60 * 60 * 1000;
355
+
356
+ const readJsonDir = (dir) => {
357
+ const out = [];
358
+ try {
359
+ for (const f of fs.readdirSync(dir).filter(f => f.endsWith(".json"))) {
360
+ try { out.push(JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"))); } catch {}
361
+ }
362
+ } catch {}
363
+ return out;
364
+ };
365
+
366
+ const allDone = readJsonDir(doneDir).filter(t => {
367
+ const ts = t.completed_at || t.created_at;
368
+ return !ts || new Date(ts).getTime() >= since;
369
+ });
370
+ const allQueue = readJsonDir(queueDir);
371
+ const allActive = readJsonDir(activeDir);
372
+
373
+ // Aggregate by agent
374
+ const byAgent = {};
375
+ for (const t of allDone) {
376
+ const a = t.assigned_to || t.agent || "unknown";
377
+ if (!byAgent[a]) byAgent[a] = { done: 0, tasks: [] };
378
+ byAgent[a].done++;
379
+ byAgent[a].tasks.push(t.title || t.id || "?");
380
+ }
381
+
382
+ // Session data
383
+ let sessionGoal = "?";
384
+ try {
385
+ const active = JSON.parse(fs.readFileSync(path.join(ai, "sessions", "active.json"), "utf8"));
386
+ sessionGoal = (active.task || {}).goal || "?";
387
+ } catch {}
388
+
389
+ console.log(`\n ${B}${T}0dai reflect${R2}${R} — last ${days} days\n`);
390
+
391
+ // Goal
392
+ if (sessionGoal !== "?") console.log(` ${B}Goal${R2} ${sessionGoal}`);
393
+
394
+ // Delegation stats
395
+ const totalDone = allDone.length;
396
+ const totalPending = allQueue.length + allActive.length;
397
+ const successRate = totalDone + totalPending > 0
398
+ ? Math.round((totalDone / (totalDone + totalPending)) * 100)
399
+ : null;
400
+
401
+ console.log(` ${B}Delivered${R2} ${G}${totalDone}${R2} tasks completed`);
402
+ if (totalPending) console.log(` ${B}Remaining${R2} ${W}${totalPending}${R2} tasks still pending`);
403
+ if (successRate !== null) console.log(` ${B}Rate${R2} ${successRate >= 80 ? G : W}${successRate}%${R2} delegation success rate`);
404
+
405
+ // By agent breakdown
406
+ if (Object.keys(byAgent).length) {
407
+ console.log(`\n ${B}By agent:${R2}`);
408
+ for (const [agent, data] of Object.entries(byAgent).sort((a, b) => b[1].done - a[1].done)) {
409
+ const bar = "█".repeat(Math.min(data.done, 20));
410
+ console.log(` ${(agent + " ").padEnd(14)} ${G}${bar}${R2} ${data.done}`);
411
+ }
412
+ }
413
+
414
+ // Remaining blockers
415
+ if (allQueue.length) {
416
+ console.log(`\n ${B}Blockers / queue:${R2}`);
417
+ for (const t of allQueue.slice(0, 8)) {
418
+ const agent = t.assigned_to ? ` → ${t.assigned_to}` : "";
419
+ console.log(` ${W}•${R2} ${(t.title || t.id || "?").slice(0, 60)}${agent}`);
420
+ }
421
+ if (allQueue.length > 8) console.log(` ${D}… and ${allQueue.length - 8} more${R2}`);
422
+ }
423
+
424
+ // No data
425
+ if (!totalDone && !totalPending) {
426
+ console.log(` ${D}No swarm tasks found in the last ${days} days.${R2}`);
427
+ console.log(` ${D}Use '0dai swarm add --task "..." --to codex' to delegate tasks.${R2}`);
428
+ }
429
+
430
+ console.log();
278
431
  }
279
432
 
280
433
  function cmdStatus(target) {
@@ -675,6 +828,7 @@ async function main() {
675
828
  case "sync": await cmdSync(target); break;
676
829
  case "detect": await cmdDetect(target); break;
677
830
  case "doctor": cmdDoctor(target); break;
831
+ case "reflect": cmdReflect(target, args); break;
678
832
  case "status": cmdStatus(target); break;
679
833
  case "auth":
680
834
  if (sub === "login") await cmdAuthLogin();
@@ -716,7 +870,8 @@ async function main() {
716
870
  console.log(" init Initialize ai/ layer (via API)");
717
871
  console.log(" sync Update ai/ layer (via API)");
718
872
  console.log(" detect Show detected stack");
719
- console.log(" doctor Check health");
873
+ console.log(" doctor Check health + credentials checklist");
874
+ console.log(" reflect Session reflection: delivered, delegation rate, blockers");
720
875
  console.log(" status Show maturity, swarm, session");
721
876
  console.log(" session save Save session for roaming");
722
877
  console.log(" swarm status Task queue & delegation");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0dai-dev/cli",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider",
5
5
  "bin": {
6
6
  "0dai": "./bin/0dai.js"