@0dai-dev/cli 2.3.0 → 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 +174 -17
- 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) {
|
|
@@ -501,11 +654,13 @@ function cmdModels(filter) {
|
|
|
501
654
|
{ name: "GPT-5.4-mini", tier: "fast", score: 76, cli: "codex", flag: "-m gpt-5.4-mini", tested: true },
|
|
502
655
|
{ name: "Gemini 3.1 Pro", tier: "balanced", score: 85, cli: "gemini", flag: "-m gemini-3.1-pro" },
|
|
503
656
|
{ name: "Gemini 3 Flash", tier: "fast", score: 77, cli: "gemini", flag: "-m gemini-3-flash" },
|
|
504
|
-
{ name: "
|
|
505
|
-
{ name: "
|
|
506
|
-
{ name: "
|
|
507
|
-
{ name: "MiniMax M2.5", tier: "fast", score:
|
|
657
|
+
{ name: "Mimo v2 Pro", tier: "balanced", score: 80, cli: "opencode", flag: "-m opencode-go/mimo-v2-pro", tested: true },
|
|
658
|
+
{ name: "Mimo v2 Omni", tier: "fast", score: 78, cli: "opencode", flag: "-m opencode-go/mimo-v2-omni", tested: true },
|
|
659
|
+
{ name: "Kimi K2.5", tier: "balanced", score: 72, cli: "opencode", flag: "-m opencode-go/kimi-k2.5", tested: true },
|
|
660
|
+
{ name: "MiniMax M2.5", tier: "fast", score: 70, cli: "opencode", flag: "-m opencode-go/minimax-m2.5", tested: true },
|
|
661
|
+
{ name: "MiniMax M2.7", tier: "balanced", score: 68, cli: "opencode", flag: "-m opencode-go/minimax-m2.7", tested: true },
|
|
508
662
|
{ name: "Qwen 3.6+ Free", tier: "fast", score: 64, cli: "opencode", flag: "-m opencode/qwen3.6-plus-free", tested: true },
|
|
663
|
+
{ name: "GLM-5", tier: "fast", score: 62, cli: "opencode", flag: "-m opencode-go/glm-5", tested: true },
|
|
509
664
|
];
|
|
510
665
|
|
|
511
666
|
const { execFileSync } = require("child_process");
|
|
@@ -673,6 +828,7 @@ async function main() {
|
|
|
673
828
|
case "sync": await cmdSync(target); break;
|
|
674
829
|
case "detect": await cmdDetect(target); break;
|
|
675
830
|
case "doctor": cmdDoctor(target); break;
|
|
831
|
+
case "reflect": cmdReflect(target, args); break;
|
|
676
832
|
case "status": cmdStatus(target); break;
|
|
677
833
|
case "auth":
|
|
678
834
|
if (sub === "login") await cmdAuthLogin();
|
|
@@ -714,7 +870,8 @@ async function main() {
|
|
|
714
870
|
console.log(" init Initialize ai/ layer (via API)");
|
|
715
871
|
console.log(" sync Update ai/ layer (via API)");
|
|
716
872
|
console.log(" detect Show detected stack");
|
|
717
|
-
console.log(" doctor Check health");
|
|
873
|
+
console.log(" doctor Check health + credentials checklist");
|
|
874
|
+
console.log(" reflect Session reflection: delivered, delegation rate, blockers");
|
|
718
875
|
console.log(" status Show maturity, swarm, session");
|
|
719
876
|
console.log(" session save Save session for roaming");
|
|
720
877
|
console.log(" swarm status Task queue & delegation");
|