@0dai-dev/cli 2.0.1 → 2.1.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 +183 -7
- 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.0
|
|
10
|
+
const VERSION = "2.1.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
|
|
@@ -58,7 +58,7 @@ function apiCall(endpoint, data) {
|
|
|
58
58
|
const url = new URL(endpoint, API_URL);
|
|
59
59
|
const mod = url.protocol === "https:" ? https : http;
|
|
60
60
|
const body = data ? JSON.stringify(data) : null;
|
|
61
|
-
const headers = { "Content-Type": "application/json", "X-Device-ID": deviceFingerprint() };
|
|
61
|
+
const headers = { "Content-Type": "application/json", "X-Device-ID": deviceFingerprint(), "X-CLI-Version": VERSION };
|
|
62
62
|
|
|
63
63
|
try {
|
|
64
64
|
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
@@ -115,10 +115,10 @@ function collectMetadata(target) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const clis = [];
|
|
118
|
+
const { execSync } = require("child_process");
|
|
118
119
|
for (const cli of ["claude", "codex", "opencode", "gemini", "aider"]) {
|
|
119
120
|
try {
|
|
120
|
-
|
|
121
|
-
execSync(`which ${cli}`, { stdio: "ignore" });
|
|
121
|
+
execSync(`command -v ${cli}`, { stdio: "ignore", shell: "/bin/sh", env: process.env });
|
|
122
122
|
clis.push(cli);
|
|
123
123
|
} catch {}
|
|
124
124
|
}
|
|
@@ -184,13 +184,36 @@ async function cmdInit(target) {
|
|
|
184
184
|
|
|
185
185
|
log(`initialized (${result.file_count || "?"} files)`);
|
|
186
186
|
console.log(" skills: /build /review /status /feedback /bugfix /delegate");
|
|
187
|
+
|
|
188
|
+
// Auto-feedback for free tiers (non-optional — provides usage data for product improvement)
|
|
189
|
+
const plan = result.plan || "trial";
|
|
190
|
+
if (plan === "trial" || plan === "free") {
|
|
191
|
+
const autoReport = {
|
|
192
|
+
project: path.basename(target),
|
|
193
|
+
stack_detected: result.stack || "?",
|
|
194
|
+
agent_cli: "cli",
|
|
195
|
+
verdict: "auto",
|
|
196
|
+
_auto: true,
|
|
197
|
+
_plan: plan,
|
|
198
|
+
_cli_version: VERSION,
|
|
199
|
+
_files_generated: result.file_count || 0,
|
|
200
|
+
};
|
|
201
|
+
apiCall("/v1/feedback", { report: autoReport }).catch(() => {});
|
|
202
|
+
console.log(` ${D}usage data sent to improve 0dai (disable with Essential+ plan)${R}`);
|
|
203
|
+
}
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
async function cmdSync(target) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let version = "unknown", stack = "generic", agents = [];
|
|
207
|
+
// Quick local check: skip API if already at current version
|
|
208
|
+
let version = "unknown";
|
|
193
209
|
try { version = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim(); } catch {}
|
|
210
|
+
if (version === VERSION) {
|
|
211
|
+
log("already up to date (v" + version + ")");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
216
|
+
let stack = "generic", agents = [];
|
|
194
217
|
try {
|
|
195
218
|
const d = JSON.parse(fs.readFileSync(path.join(target, "ai", "manifest", "discovery.json"), "utf8"));
|
|
196
219
|
stack = d.stack || "generic";
|
|
@@ -353,6 +376,153 @@ async function cmdAuthStatus() {
|
|
|
353
376
|
}
|
|
354
377
|
}
|
|
355
378
|
|
|
379
|
+
async function cmdFeedbackPush(target) {
|
|
380
|
+
const ai = path.join(target, "ai", "feedback");
|
|
381
|
+
const reports = [];
|
|
382
|
+
try {
|
|
383
|
+
for (const f of fs.readdirSync(ai)) {
|
|
384
|
+
if (f.endsWith("-report.json") || (f.endsWith(".json") && f.match(/^\d{8}/))) {
|
|
385
|
+
try {
|
|
386
|
+
const d = JSON.parse(fs.readFileSync(path.join(ai, f), "utf8"));
|
|
387
|
+
if (d.project || d.verdict) reports.push(d);
|
|
388
|
+
} catch {}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} catch {}
|
|
392
|
+
|
|
393
|
+
if (!reports.length) { log("no feedback reports found"); return; }
|
|
394
|
+
|
|
395
|
+
// Send each report via API
|
|
396
|
+
for (const report of reports) {
|
|
397
|
+
log(`pushing: ${report.project || "?"} (${report.verdict || "?"})`);
|
|
398
|
+
const result = await apiCall("/v1/feedback", { report });
|
|
399
|
+
if (result.issue) {
|
|
400
|
+
log(`issue created: ${result.issue}`);
|
|
401
|
+
} else if (result.received) {
|
|
402
|
+
log("received by server");
|
|
403
|
+
} else {
|
|
404
|
+
log(`error: ${result.error || "unknown"}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// --- Session (local, file-based) ---
|
|
410
|
+
function cmdSession(target, sub, args) {
|
|
411
|
+
const sessFile = path.join(target, "ai", "sessions", "active.json");
|
|
412
|
+
const sessDir = path.dirname(sessFile);
|
|
413
|
+
|
|
414
|
+
if (sub === "save") {
|
|
415
|
+
fs.mkdirSync(sessDir, { recursive: true });
|
|
416
|
+
const goal = args.find((_, i) => args[i - 1] === "--goal") || "";
|
|
417
|
+
const summary = args.find((_, i) => args[i - 1] === "--summary") || "";
|
|
418
|
+
const session = {
|
|
419
|
+
id: `sess-${Date.now()}`,
|
|
420
|
+
started: new Date().toISOString(),
|
|
421
|
+
current_agent: "cli",
|
|
422
|
+
task: { goal: goal || summary || "active session", status: "in_progress" },
|
|
423
|
+
handoff_notes: summary,
|
|
424
|
+
context: { files_touched: [] },
|
|
425
|
+
};
|
|
426
|
+
if (fs.existsSync(sessFile)) {
|
|
427
|
+
const existing = JSON.parse(fs.readFileSync(sessFile, "utf8"));
|
|
428
|
+
existing.handoff_notes = summary || existing.handoff_notes;
|
|
429
|
+
if (goal) existing.task.goal = goal;
|
|
430
|
+
existing.updated = new Date().toISOString();
|
|
431
|
+
fs.writeFileSync(sessFile, JSON.stringify(existing, null, 2));
|
|
432
|
+
log("session updated");
|
|
433
|
+
} else {
|
|
434
|
+
fs.writeFileSync(sessFile, JSON.stringify(session, null, 2));
|
|
435
|
+
log(`session started: ${session.id}`);
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (sub === "status") {
|
|
440
|
+
if (!fs.existsSync(sessFile)) { log("no active session"); return; }
|
|
441
|
+
const s = JSON.parse(fs.readFileSync(sessFile, "utf8"));
|
|
442
|
+
log(`session: ${(s.task || {}).goal || "?"}`);
|
|
443
|
+
console.log(` agent: ${s.current_agent || "?"}`);
|
|
444
|
+
if (s.handoff_notes) console.log(` handoff: ${s.handoff_notes}`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (sub === "complete") {
|
|
448
|
+
if (!fs.existsSync(sessFile)) { log("no active session"); return; }
|
|
449
|
+
const archiveDir = path.join(target, "ai", "sessions", "archive");
|
|
450
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
451
|
+
const s = JSON.parse(fs.readFileSync(sessFile, "utf8"));
|
|
452
|
+
fs.writeFileSync(path.join(archiveDir, `${s.id || "session"}.json`), JSON.stringify(s, null, 2));
|
|
453
|
+
fs.unlinkSync(sessFile);
|
|
454
|
+
log(`session ${s.id} archived`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
console.log("Usage: 0dai session [save|status|complete] [--goal '...'] [--summary '...']");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// --- Swarm (local, file-based) ---
|
|
461
|
+
function cmdSwarm(target, sub, args) {
|
|
462
|
+
const swarmDir = path.join(target, "ai", "swarm");
|
|
463
|
+
const queueDir = path.join(swarmDir, "queue");
|
|
464
|
+
|
|
465
|
+
if (sub === "status") {
|
|
466
|
+
const count = (d) => { try { return fs.readdirSync(d).filter(f => f.endsWith(".json")).length; } catch { return 0; } };
|
|
467
|
+
const q = count(path.join(swarmDir, "queue"));
|
|
468
|
+
const a = count(path.join(swarmDir, "active"));
|
|
469
|
+
const d = count(path.join(swarmDir, "done"));
|
|
470
|
+
log(`swarm: ${q} queued, ${a} active, ${d} done`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (sub === "add" || sub === "delegate") {
|
|
474
|
+
fs.mkdirSync(queueDir, { recursive: true });
|
|
475
|
+
const task = args.find((_, i) => args[i - 1] === "--task") || "untitled";
|
|
476
|
+
const forAgent = args.find((_, i) => ["--for", "--to"].includes(args[i - 1])) || "any";
|
|
477
|
+
const id = `swarm-${Date.now()}`;
|
|
478
|
+
const t = { id, title: task, assigned_to: forAgent, status: "pending", created_at: new Date().toISOString(), created_by: "cli" };
|
|
479
|
+
fs.writeFileSync(path.join(queueDir, `${id}.json`), JSON.stringify(t, null, 2));
|
|
480
|
+
log(`task created: ${id} → ${forAgent}`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (sub === "budget") {
|
|
484
|
+
const budgetFile = path.join(swarmDir, "budget.json");
|
|
485
|
+
if (!fs.existsSync(budgetFile)) { log("no budget data yet"); return; }
|
|
486
|
+
const b = JSON.parse(fs.readFileSync(budgetFile, "utf8"));
|
|
487
|
+
log(`total: $${(b.total_spent || 0).toFixed(4)} (${Object.keys(b.tasks || {}).length} tasks)`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
console.log("Usage: 0dai swarm [status|add|delegate|budget] [--task '...'] [--to agent]");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// --- Feedback (local + API push) ---
|
|
494
|
+
async function cmdFeedback(target, sub, args) {
|
|
495
|
+
const fbDir = path.join(target, "ai", "feedback");
|
|
496
|
+
|
|
497
|
+
if (sub === "push") {
|
|
498
|
+
return cmdFeedbackPush(target);
|
|
499
|
+
}
|
|
500
|
+
if (sub === "log") {
|
|
501
|
+
const type = args.find((_, i) => args[i - 1] === "--type") || "suggestion";
|
|
502
|
+
const detail = args.find((_, i) => args[i - 1] === "--detail") || "";
|
|
503
|
+
if (!detail) { console.log("Usage: 0dai feedback log --type bug|suggestion|friction|positive --detail '...'"); return; }
|
|
504
|
+
fs.mkdirSync(fbDir, { recursive: true });
|
|
505
|
+
const entry = JSON.stringify({ ts: new Date().toISOString(), type, detail, agent: "cli" });
|
|
506
|
+
fs.appendFileSync(path.join(fbDir, "operational.jsonl"), entry + "\n");
|
|
507
|
+
log(`logged: [${type}] ${detail.slice(0, 60)}`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (sub === "list") {
|
|
511
|
+
try {
|
|
512
|
+
const files = fs.readdirSync(fbDir).filter(f => f.endsWith("-report.json"));
|
|
513
|
+
if (!files.length) { log("no reports"); return; }
|
|
514
|
+
for (const f of files) {
|
|
515
|
+
try {
|
|
516
|
+
const d = JSON.parse(fs.readFileSync(path.join(fbDir, f), "utf8"));
|
|
517
|
+
console.log(` ${f}: ${d.verdict || "?"} (${d.project || "?"})`);
|
|
518
|
+
} catch {}
|
|
519
|
+
}
|
|
520
|
+
} catch { log("no feedback directory"); }
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
console.log("Usage: 0dai feedback [push|log|list] [--type ...] [--detail '...']");
|
|
524
|
+
}
|
|
525
|
+
|
|
356
526
|
async function main() {
|
|
357
527
|
const args = process.argv.slice(2);
|
|
358
528
|
let target = process.cwd();
|
|
@@ -379,6 +549,9 @@ async function main() {
|
|
|
379
549
|
console.log("Usage: 0dai auth [login|logout|status]");
|
|
380
550
|
}
|
|
381
551
|
break;
|
|
552
|
+
case "session": cmdSession(target, sub, args); break;
|
|
553
|
+
case "swarm": cmdSwarm(target, sub, args); break;
|
|
554
|
+
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
382
555
|
case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
|
|
383
556
|
case "help": case "--help": case "-h":
|
|
384
557
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
@@ -388,6 +561,9 @@ async function main() {
|
|
|
388
561
|
console.log(" detect Show detected stack");
|
|
389
562
|
console.log(" doctor Check health");
|
|
390
563
|
console.log(" status Show maturity, swarm, session");
|
|
564
|
+
console.log(" session save Save session for roaming");
|
|
565
|
+
console.log(" swarm status Task queue & delegation");
|
|
566
|
+
console.log(" feedback push Send feedback to 0dai");
|
|
391
567
|
console.log(" auth login Authenticate (device code flow)");
|
|
392
568
|
console.log(" auth logout Remove credentials");
|
|
393
569
|
console.log(" auth status Show account and usage");
|