@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.
- package/bin/0dai.js +168 -13
- 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.
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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");
|