@0dai-dev/cli 2.6.0 → 2.8.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 +258 -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.8.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
|
|
@@ -145,7 +145,9 @@ function writeFiles(target, files) {
|
|
|
145
145
|
return created + updated;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
async function cmdInit(target) {
|
|
148
|
+
async function cmdInit(target, args = []) {
|
|
149
|
+
const dryRun = args.includes("--dry-run");
|
|
150
|
+
|
|
149
151
|
if (fs.existsSync(path.join(target, "ai", "VERSION"))) {
|
|
150
152
|
const v = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim();
|
|
151
153
|
log(`ai/ layer already exists (v${v}). Run '0dai sync' to update.`);
|
|
@@ -159,12 +161,14 @@ async function cmdInit(target) {
|
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
+
if (dryRun) log(`${D}dry-run: would generate ai/ layer (${projectFiles.length} files, ${clis.length} CLIs)${R}`);
|
|
165
|
+
if (spinner) spinner.start(`${dryRun ? "[dry-run] " : ""}Generating ai/ layer (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
166
|
+
else if (!dryRun) log(`sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
164
167
|
const result = await apiCall("/v1/init", {
|
|
165
168
|
project_files: projectFiles,
|
|
166
169
|
file_contents: fileContents,
|
|
167
170
|
available_clis: clis,
|
|
171
|
+
dry_run: dryRun,
|
|
168
172
|
});
|
|
169
173
|
|
|
170
174
|
if (result.error) {
|
|
@@ -177,8 +181,15 @@ async function cmdInit(target) {
|
|
|
177
181
|
process.exit(1);
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
if (spinner) spinner.stop(
|
|
184
|
+
if (spinner) spinner.stop(`${dryRun ? "[dry-run] " : ""}Detected: ${result.stack || "?"}`);
|
|
181
185
|
else log(`detected: ${result.stack || "?"}`);
|
|
186
|
+
if (dryRun) {
|
|
187
|
+
const files = Object.keys(result.files || {});
|
|
188
|
+
log(`${D}dry-run: would write ${files.length} files:${R}`);
|
|
189
|
+
for (const f of files.slice(0, 20)) console.log(` ${D}+ ${f}${R}`);
|
|
190
|
+
if (files.length > 20) console.log(` ${D}… and ${files.length - 20} more${R}`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
182
193
|
writeFiles(target, result.files || {});
|
|
183
194
|
|
|
184
195
|
// Add to .gitignore
|
|
@@ -203,11 +214,13 @@ async function cmdInit(target) {
|
|
|
203
214
|
console.log(` ${D}0dai feedback push${R}`);
|
|
204
215
|
}
|
|
205
216
|
|
|
206
|
-
async function cmdSync(target) {
|
|
207
|
-
|
|
217
|
+
async function cmdSync(target, args = []) {
|
|
218
|
+
const dryRun = args.includes("--dry-run");
|
|
219
|
+
|
|
220
|
+
// Quick local check: skip API if already at current version (unless dry-run)
|
|
208
221
|
let version = "unknown";
|
|
209
222
|
try { version = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim(); } catch {}
|
|
210
|
-
if (version === VERSION) {
|
|
223
|
+
if (!dryRun && version === VERSION) {
|
|
211
224
|
log("already up to date (v" + version + ")");
|
|
212
225
|
return;
|
|
213
226
|
}
|
|
@@ -239,24 +252,49 @@ async function cmdSync(target) {
|
|
|
239
252
|
walk(aiDir);
|
|
240
253
|
}
|
|
241
254
|
|
|
255
|
+
if (dryRun) log(`${D}dry-run: checking what sync would change...${R}`);
|
|
256
|
+
|
|
242
257
|
const result = await apiCall("/v1/sync", {
|
|
243
258
|
ai_version: version, stack, agents: agents.length ? agents : clis,
|
|
244
259
|
current_files: currentFiles, file_contents: fileContents,
|
|
260
|
+
dry_run: dryRun,
|
|
245
261
|
});
|
|
246
262
|
|
|
247
263
|
if (result.error) { log(`error: ${result.error}`); process.exit(1); }
|
|
248
264
|
|
|
249
265
|
const updated = result.files_updated || {};
|
|
266
|
+
if (dryRun) {
|
|
267
|
+
const files = Object.keys(updated);
|
|
268
|
+
if (files.length) {
|
|
269
|
+
log(`${D}dry-run: would update ${files.length} file(s):${R}`);
|
|
270
|
+
for (const f of files) console.log(` ${D}~ ${f}${R}`);
|
|
271
|
+
} else {
|
|
272
|
+
log(`${D}dry-run: nothing to update${R}`);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
250
276
|
if (Object.keys(updated).length) writeFiles(target, updated);
|
|
251
277
|
else log("already up to date");
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
async function cmdDetect(target) {
|
|
281
|
+
const OPTIONAL_CLIS = ["gemini", "aider", "opencode"];
|
|
255
282
|
const { projectFiles } = collectMetadata(target);
|
|
256
283
|
const result = await apiCall("/v1/detect", { files: projectFiles });
|
|
257
284
|
if (result.error) { log(`error: ${result.error}`); return; }
|
|
258
285
|
console.log(`stack: ${result.stack || "?"}`);
|
|
259
|
-
|
|
286
|
+
const clis = result.available_clis || [];
|
|
287
|
+
if (clis.length) {
|
|
288
|
+
console.log(`clis: ${clis.join(", ")}`);
|
|
289
|
+
} else {
|
|
290
|
+
console.log(`clis: none detected`);
|
|
291
|
+
console.log(` ${D}install claude, codex, or opencode to use 0dai${R}`);
|
|
292
|
+
}
|
|
293
|
+
// Explain optional CLIs so missing doesn't alarm users
|
|
294
|
+
const missing = OPTIONAL_CLIS.filter(c => !clis.includes(c));
|
|
295
|
+
if (missing.length && clis.length) {
|
|
296
|
+
console.log(` ${D}optional (not installed): ${missing.join(", ")}${R}`);
|
|
297
|
+
}
|
|
260
298
|
}
|
|
261
299
|
|
|
262
300
|
function cmdAudit(target) {
|
|
@@ -427,13 +465,27 @@ function cmdDoctor(target) {
|
|
|
427
465
|
let errors = 0, warnings = 0;
|
|
428
466
|
log(`v${v} | stack: ${stack}\n`);
|
|
429
467
|
|
|
468
|
+
const missingConfigs = [];
|
|
430
469
|
console.log(" ai/ layer:");
|
|
431
470
|
for (const [name, { path: p, sev }] of Object.entries(layerChecks)) {
|
|
432
471
|
const exists = fs.existsSync(p);
|
|
433
|
-
if (!exists)
|
|
472
|
+
if (!exists) {
|
|
473
|
+
sev === "error" ? errors++ : warnings++;
|
|
474
|
+
if (sev === "warn") missingConfigs.push(name);
|
|
475
|
+
}
|
|
434
476
|
const mark = exists ? `${G}ok${R2}` : sev === "error" ? `${E}MISSING${R2}` : `${W}missing${R2}`;
|
|
435
477
|
console.log(` ${mark.padEnd(22)} ${name}`);
|
|
436
478
|
}
|
|
479
|
+
// Explain WHY native configs are missing and what to do
|
|
480
|
+
if (missingConfigs.length > 0) {
|
|
481
|
+
const hasDiscovery = fs.existsSync(path.join(ai, "manifest", "discovery.json"));
|
|
482
|
+
if (hasDiscovery) {
|
|
483
|
+
console.log(`\n ${W}→ Native configs not generated yet.${R2}`);
|
|
484
|
+
console.log(` ${D}Run: 0dai sync --target .${R2}`);
|
|
485
|
+
} else {
|
|
486
|
+
console.log(`\n ${W}→ ai/ layer incomplete — run '0dai init' first.${R2}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
437
489
|
|
|
438
490
|
console.log("\n credentials:");
|
|
439
491
|
for (const c of credChecks) {
|
|
@@ -458,6 +510,50 @@ function cmdDoctor(target) {
|
|
|
458
510
|
if (errors) process.exitCode = 1;
|
|
459
511
|
}
|
|
460
512
|
|
|
513
|
+
function cmdValidate(target) {
|
|
514
|
+
const ai = path.join(target, "ai");
|
|
515
|
+
if (!fs.existsSync(ai)) {
|
|
516
|
+
log("no ai/ layer. Run '0dai init' first.");
|
|
517
|
+
process.exitCode = 1;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const E = process.stdout.isTTY ? "\x1b[31m" : "";
|
|
521
|
+
const G = process.stdout.isTTY ? "\x1b[32m" : "";
|
|
522
|
+
const D2 = process.stdout.isTTY ? "\x1b[2m" : "";
|
|
523
|
+
const R2 = process.stdout.isTTY ? "\x1b[0m" : "";
|
|
524
|
+
|
|
525
|
+
const required = [
|
|
526
|
+
"ai/VERSION", "ai/VERSION_SCHEMA",
|
|
527
|
+
"ai/manifest/project.yaml", "ai/manifest/discovery.json",
|
|
528
|
+
"ai/manifest/applied-lock.json", "ai/manifest/environment.yaml",
|
|
529
|
+
"ai/manifest/commands.yaml",
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
let agents = [];
|
|
533
|
+
try {
|
|
534
|
+
agents = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")).selected_agents || [];
|
|
535
|
+
} catch {}
|
|
536
|
+
|
|
537
|
+
const agentFiles = {
|
|
538
|
+
codex: ["AGENTS.md", ".codex/config.toml"],
|
|
539
|
+
claude: [".claude/settings.json", ".claude/CLAUDE.md", ".mcp.json"],
|
|
540
|
+
opencode: ["opencode.json"],
|
|
541
|
+
};
|
|
542
|
+
for (const agent of agents) {
|
|
543
|
+
for (const f of agentFiles[agent] || []) required.push(f);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const missing = required.filter(f => !fs.existsSync(path.join(target, f)));
|
|
547
|
+
if (missing.length) {
|
|
548
|
+
log(`Validation FAILED: ${missing.length} missing file(s)\n`);
|
|
549
|
+
for (const f of missing) console.log(` ${E}✗${R2} ${f}`);
|
|
550
|
+
console.log(`\n ${D2}Run: 0dai sync --target . to fix missing files${R2}`);
|
|
551
|
+
process.exitCode = 1;
|
|
552
|
+
} else {
|
|
553
|
+
log(`${G}validate ok${R2} — all required files present`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
461
557
|
// --- Session reflection --- (dogfood feedback #36)
|
|
462
558
|
function cmdReflect(target, args) {
|
|
463
559
|
const ai = path.join(target, "ai");
|
|
@@ -526,15 +622,77 @@ function cmdReflect(target, args) {
|
|
|
526
622
|
if (totalPending) console.log(` ${B}Remaining${R2} ${W}${totalPending}${R2} tasks still pending`);
|
|
527
623
|
if (successRate !== null) console.log(` ${B}Rate${R2} ${successRate >= 80 ? G : W}${successRate}%${R2} delegation success rate`);
|
|
528
624
|
|
|
529
|
-
// By agent breakdown
|
|
625
|
+
// By agent breakdown with per-agent completion rate
|
|
626
|
+
const allPendingByAgent = {};
|
|
627
|
+
for (const t of [...allQueue, ...allActive]) {
|
|
628
|
+
const a = t.assigned_to || "unknown";
|
|
629
|
+
allPendingByAgent[a] = (allPendingByAgent[a] || 0) + 1;
|
|
630
|
+
}
|
|
631
|
+
|
|
530
632
|
if (Object.keys(byAgent).length) {
|
|
531
633
|
console.log(`\n ${B}By agent:${R2}`);
|
|
532
634
|
for (const [agent, data] of Object.entries(byAgent).sort((a, b) => b[1].done - a[1].done)) {
|
|
635
|
+
const pending = allPendingByAgent[agent] || 0;
|
|
636
|
+
const total = data.done + pending;
|
|
637
|
+
const rate = total > 0 ? Math.round((data.done / total) * 100) : 100;
|
|
533
638
|
const bar = "█".repeat(Math.min(data.done, 20));
|
|
534
|
-
|
|
639
|
+
const rateStr = total > 1 ? ` (${rate >= 80 ? G : W}${rate}%${R2})` : "";
|
|
640
|
+
console.log(` ${(agent + " ").padEnd(14)} ${G}${bar}${R2} ${data.done}/${total}${rateStr}`);
|
|
641
|
+
}
|
|
642
|
+
// Agents with only pending tasks (never completed anything in window)
|
|
643
|
+
for (const [agent, count] of Object.entries(allPendingByAgent)) {
|
|
644
|
+
if (!byAgent[agent]) {
|
|
645
|
+
console.log(` ${(agent + " ").padEnd(14)} ${W}${"░".repeat(Math.min(count, 20))}${R2} 0/${count} ${W}(pending)${R2}`);
|
|
646
|
+
}
|
|
535
647
|
}
|
|
536
648
|
}
|
|
537
649
|
|
|
650
|
+
// Budget summary from budget.json
|
|
651
|
+
const budgetFile = path.join(ai, "swarm", "budget.json");
|
|
652
|
+
try {
|
|
653
|
+
const budget = JSON.parse(fs.readFileSync(budgetFile, "utf8"));
|
|
654
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
655
|
+
const sessionKey = process.env.ODAI_SESSION_ID ||
|
|
656
|
+
new Date().toISOString().slice(0, 13).replace("T", "-"); // YYYY-MM-DD-HH
|
|
657
|
+
const dailySpent = budget.daily?.[today] || 0;
|
|
658
|
+
const totalSpent = budget.total_spent || 0;
|
|
659
|
+
const sess = budget.sessions?.[sessionKey];
|
|
660
|
+
|
|
661
|
+
// Tier distribution across all tasks
|
|
662
|
+
const tierCount = { fast: 0, balanced: 0, deep: 0 };
|
|
663
|
+
for (const t of Object.values(budget.tasks || {})) {
|
|
664
|
+
if (t.tier && tierCount[t.tier] !== undefined) tierCount[t.tier]++;
|
|
665
|
+
}
|
|
666
|
+
const tieredTotal = tierCount.fast + tierCount.balanced + tierCount.deep;
|
|
667
|
+
|
|
668
|
+
if (dailySpent > 0 || totalSpent > 0 || sess) {
|
|
669
|
+
console.log(`\n ${B}Budget:${R2}`);
|
|
670
|
+
if (sess && sess.total_cost > 0) {
|
|
671
|
+
const taskCount = (sess.tasks || []).length;
|
|
672
|
+
const avgCost = taskCount > 0 ? (sess.total_cost / taskCount).toFixed(4) : "0";
|
|
673
|
+
console.log(` ${B}This session${R2} $${sess.total_cost.toFixed(4)} · ${taskCount} tasks · avg $${avgCost}/task`);
|
|
674
|
+
if (sess.tiers) {
|
|
675
|
+
const tiers = Object.entries(sess.tiers).filter(([, n]) => n > 0).map(([t, n]) => `${n}×${t}`).join(" ");
|
|
676
|
+
if (tiers) console.log(` ${D}Tiers ${tiers}${R2}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (dailySpent > 0) {
|
|
680
|
+
const dailyLimit = parseFloat(process.env.ODAI_DAILY_BUDGET || "5");
|
|
681
|
+
const pct = Math.round((dailySpent / dailyLimit) * 100);
|
|
682
|
+
const bar = "█".repeat(Math.round(pct / 5)).padEnd(20, "░");
|
|
683
|
+
const col = pct < 50 ? G : pct < 80 ? W : "\x1b[31m";
|
|
684
|
+
console.log(` ${B}Today${R2} ${col}$${dailySpent.toFixed(4)}${R2} / $${dailyLimit.toFixed(2)} ${D}${bar} ${pct}%${R2}`);
|
|
685
|
+
}
|
|
686
|
+
if (totalSpent > 0) {
|
|
687
|
+
console.log(` ${B}All time${R2} ${D}$${totalSpent.toFixed(4)}${R2}`);
|
|
688
|
+
}
|
|
689
|
+
if (tieredTotal > 0) {
|
|
690
|
+
const fastPct = Math.round((tierCount.fast / tieredTotal) * 100);
|
|
691
|
+
console.log(` ${D}Model routing ${tierCount.fast}×fast ${tierCount.balanced}×balanced ${tierCount.deep}×deep (${fastPct}% cheap)${R2}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch {}
|
|
695
|
+
|
|
538
696
|
// Remaining blockers
|
|
539
697
|
if (allQueue.length) {
|
|
540
698
|
console.log(`\n ${B}Blockers / queue:${R2}`);
|
|
@@ -722,6 +880,33 @@ function cmdAuthLogout() {
|
|
|
722
880
|
log("Logged out");
|
|
723
881
|
}
|
|
724
882
|
|
|
883
|
+
async function cmdRedeem(code) {
|
|
884
|
+
if (!code) {
|
|
885
|
+
console.log("Usage: 0dai redeem <CODE>");
|
|
886
|
+
console.log("Example: 0dai redeem ESSE-ABCD-1234");
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
891
|
+
} catch {
|
|
892
|
+
log("Not logged in. Run: 0dai auth login");
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
log(`Redeeming code ${T}${code.toUpperCase()}${R}...`);
|
|
896
|
+
const result = await apiCall("/v1/redeem", { code: code.toUpperCase().trim() });
|
|
897
|
+
if (result.ok) {
|
|
898
|
+
log(`${T}✓${R} ${result.message}`);
|
|
899
|
+
if (result.duration_days) {
|
|
900
|
+
log(` Plan active for ${result.duration_days} days`);
|
|
901
|
+
}
|
|
902
|
+
log(` Run ${D}0dai auth status${R} to see updated limits`);
|
|
903
|
+
} else {
|
|
904
|
+
log(`error: ${result.error || "unknown"}`);
|
|
905
|
+
if (result.hint) log(`hint: ${result.hint}`);
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
725
910
|
async function cmdAuthStatus() {
|
|
726
911
|
try {
|
|
727
912
|
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
@@ -949,7 +1134,59 @@ function cmdSwarm(target, sub, args) {
|
|
|
949
1134
|
const budgetFile = path.join(swarmDir, "budget.json");
|
|
950
1135
|
if (!fs.existsSync(budgetFile)) { log("no budget data yet"); return; }
|
|
951
1136
|
const b = JSON.parse(fs.readFileSync(budgetFile, "utf8"));
|
|
952
|
-
|
|
1137
|
+
const B2 = process.stdout.isTTY ? "\x1b[1m" : "";
|
|
1138
|
+
const R2 = process.stdout.isTTY ? "\x1b[0m" : "";
|
|
1139
|
+
const D2 = process.stdout.isTTY ? "\x1b[2m" : "";
|
|
1140
|
+
const G2 = process.stdout.isTTY ? "\x1b[32m" : "";
|
|
1141
|
+
const W2 = process.stdout.isTTY ? "\x1b[33m" : "";
|
|
1142
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1143
|
+
const sessionKey = process.env.ODAI_SESSION_ID ||
|
|
1144
|
+
new Date().toISOString().slice(0, 13).replace("T", "-");
|
|
1145
|
+
const dailySpent = b.daily?.[today] || 0;
|
|
1146
|
+
const totalSpent = b.total_spent || 0;
|
|
1147
|
+
const sess = b.sessions?.[sessionKey];
|
|
1148
|
+
// Tier distribution across all tasks
|
|
1149
|
+
const tierCount = { fast: 0, balanced: 0, deep: 0 };
|
|
1150
|
+
for (const t of Object.values(b.tasks || {})) {
|
|
1151
|
+
if (t.tier && tierCount[t.tier] !== undefined) tierCount[t.tier]++;
|
|
1152
|
+
}
|
|
1153
|
+
const tieredTotal = tierCount.fast + tierCount.balanced + tierCount.deep;
|
|
1154
|
+
console.log(`\n ${B2}Swarm Budget${R2}`);
|
|
1155
|
+
if (sess && sess.total_cost > 0) {
|
|
1156
|
+
const taskCount = (sess.tasks || []).length;
|
|
1157
|
+
const avgCost = taskCount > 0 ? (sess.total_cost / taskCount).toFixed(4) : "0";
|
|
1158
|
+
console.log(` ${B2}This session${R2} $${sess.total_cost.toFixed(4)} · ${taskCount} tasks · avg $${avgCost}/task`);
|
|
1159
|
+
if (sess.tiers) {
|
|
1160
|
+
const tiers = Object.entries(sess.tiers).filter(([, n]) => n > 0).map(([t, n]) => `${n}×${t}`).join(" ");
|
|
1161
|
+
if (tiers) console.log(` ${D2}Tiers ${tiers}${R2}`);
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
console.log(` ${D2}This session no tracked spend${R2}`);
|
|
1165
|
+
}
|
|
1166
|
+
if (dailySpent > 0) {
|
|
1167
|
+
const dailyLimit = parseFloat(process.env.ODAI_DAILY_BUDGET || "5");
|
|
1168
|
+
const pct = Math.round((dailySpent / dailyLimit) * 100);
|
|
1169
|
+
const bar = "█".repeat(Math.round(pct / 5)).padEnd(20, "░");
|
|
1170
|
+
const col = pct < 50 ? G2 : pct < 80 ? W2 : "\x1b[31m";
|
|
1171
|
+
console.log(` ${B2}Today${R2} ${col}$${dailySpent.toFixed(4)}${R2} / $${dailyLimit.toFixed(2)} ${D2}${bar} ${pct}%${R2}`);
|
|
1172
|
+
}
|
|
1173
|
+
console.log(` ${B2}All time${R2} ${D2}$${totalSpent.toFixed(4)} (${Object.keys(b.tasks || {}).length} tasks)${R2}`);
|
|
1174
|
+
if (tieredTotal > 0) {
|
|
1175
|
+
const fastPct = Math.round((tierCount.fast / tieredTotal) * 100);
|
|
1176
|
+
console.log(` ${D2}Model routing ${tierCount.fast}×fast ${tierCount.balanced}×balanced ${tierCount.deep}×deep (${fastPct}% cheap)${R2}`);
|
|
1177
|
+
}
|
|
1178
|
+
// Recent sessions (last 5)
|
|
1179
|
+
const sessions = Object.entries(b.sessions || {})
|
|
1180
|
+
.sort(([a], [bb]) => bb.localeCompare(a))
|
|
1181
|
+
.slice(0, 5);
|
|
1182
|
+
if (sessions.length > 1) {
|
|
1183
|
+
console.log(` ${D2}Recent sessions:${R2}`);
|
|
1184
|
+
for (const [key, s] of sessions) {
|
|
1185
|
+
const tasks = (s.tasks || []).length;
|
|
1186
|
+
console.log(` ${D2}${key} $${(s.total_cost || 0).toFixed(4)} · ${tasks} tasks${R2}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
console.log();
|
|
953
1190
|
return;
|
|
954
1191
|
}
|
|
955
1192
|
console.log("Usage: 0dai swarm [status|add|delegate|budget] [--task '...'] [--to agent]");
|
|
@@ -1002,10 +1239,11 @@ async function main() {
|
|
|
1002
1239
|
|
|
1003
1240
|
switch (cmd) {
|
|
1004
1241
|
case "audit": cmdAudit(target); break;
|
|
1005
|
-
case "init": await cmdInit(target); break;
|
|
1006
|
-
case "sync": await cmdSync(target); break;
|
|
1242
|
+
case "init": await cmdInit(target, args); break;
|
|
1243
|
+
case "sync": await cmdSync(target, args); break;
|
|
1007
1244
|
case "detect": await cmdDetect(target); break;
|
|
1008
1245
|
case "doctor": cmdDoctor(target); break;
|
|
1246
|
+
case "validate": cmdValidate(target); break;
|
|
1009
1247
|
case "reflect": cmdReflect(target, args); break;
|
|
1010
1248
|
case "status": cmdStatus(target); break;
|
|
1011
1249
|
case "auth":
|
|
@@ -1020,6 +1258,7 @@ async function main() {
|
|
|
1020
1258
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
1021
1259
|
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
1022
1260
|
case "models": cmdModels(sub || args[1]); break;
|
|
1261
|
+
case "redeem": await cmdRedeem(sub || args[1]); break;
|
|
1023
1262
|
case "terminal": case "term":
|
|
1024
1263
|
try {
|
|
1025
1264
|
const SessionManager = require("../lib/session-manager");
|
|
@@ -1046,10 +1285,11 @@ async function main() {
|
|
|
1046
1285
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
1047
1286
|
console.log("Commands:");
|
|
1048
1287
|
console.log(" audit Scan ai/ and agent configs for leaked secrets");
|
|
1049
|
-
console.log(" init Initialize ai/ layer (via API)");
|
|
1050
|
-
console.log(" sync Update ai/ layer (via API)");
|
|
1288
|
+
console.log(" init Initialize ai/ layer (via API) [--dry-run]");
|
|
1289
|
+
console.log(" sync Update ai/ layer (via API) [--dry-run]");
|
|
1051
1290
|
console.log(" detect Show detected stack");
|
|
1052
1291
|
console.log(" doctor Check health + credentials checklist");
|
|
1292
|
+
console.log(" validate Validate ai/ layer completeness");
|
|
1053
1293
|
console.log(" reflect Session reflection: delivered, delegation rate, blockers");
|
|
1054
1294
|
console.log(" status Show maturity, swarm, session");
|
|
1055
1295
|
console.log(" session save Save session for roaming");
|
|
@@ -1063,6 +1303,7 @@ async function main() {
|
|
|
1063
1303
|
console.log(" auth login Authenticate (device code flow)");
|
|
1064
1304
|
console.log(" auth logout Remove credentials");
|
|
1065
1305
|
console.log(" auth status Show account and usage");
|
|
1306
|
+
console.log(" redeem <CODE> Redeem a plan upgrade code");
|
|
1066
1307
|
console.log(" --version\n");
|
|
1067
1308
|
console.log("https://0dai.dev");
|
|
1068
1309
|
break;
|