@0dai-dev/cli 2.0.0 → 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 +219 -38
- package/package.json +1 -1
package/bin/0dai.js
CHANGED
|
@@ -7,8 +7,12 @@ 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.1.0";
|
|
11
11
|
const API_URL = process.env.ODAI_API_URL || "https://api.0dai.dev";
|
|
12
|
+
const T = process.stdout.isTTY ? "\x1b[38;2;45;212;168m" : ""; // teal
|
|
13
|
+
const R = process.stdout.isTTY ? "\x1b[0m" : ""; // reset
|
|
14
|
+
const D = process.stdout.isTTY ? "\x1b[2m" : ""; // dim
|
|
15
|
+
const log = (msg) => console.log(`${T}[0dai]${R} ${msg}`);
|
|
12
16
|
const CONFIG_DIR = path.join(os.homedir(), ".0dai");
|
|
13
17
|
const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
14
18
|
const VERSION_CHECK_FILE = path.join(CONFIG_DIR, ".version_check");
|
|
@@ -54,7 +58,7 @@ function apiCall(endpoint, data) {
|
|
|
54
58
|
const url = new URL(endpoint, API_URL);
|
|
55
59
|
const mod = url.protocol === "https:" ? https : http;
|
|
56
60
|
const body = data ? JSON.stringify(data) : null;
|
|
57
|
-
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 };
|
|
58
62
|
|
|
59
63
|
try {
|
|
60
64
|
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
@@ -111,10 +115,10 @@ function collectMetadata(target) {
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const clis = [];
|
|
118
|
+
const { execSync } = require("child_process");
|
|
114
119
|
for (const cli of ["claude", "codex", "opencode", "gemini", "aider"]) {
|
|
115
120
|
try {
|
|
116
|
-
|
|
117
|
-
execSync(`which ${cli}`, { stdio: "ignore" });
|
|
121
|
+
execSync(`command -v ${cli}`, { stdio: "ignore", shell: "/bin/sh", env: process.env });
|
|
118
122
|
clis.push(cli);
|
|
119
123
|
} catch {}
|
|
120
124
|
}
|
|
@@ -137,21 +141,21 @@ function writeFiles(target, files) {
|
|
|
137
141
|
} catch { created++; }
|
|
138
142
|
fs.writeFileSync(p, content, "utf8");
|
|
139
143
|
}
|
|
140
|
-
|
|
144
|
+
log(`${created} created, ${updated} updated, ${unchanged} unchanged`);
|
|
141
145
|
return created + updated;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
async function cmdInit(target) {
|
|
145
149
|
if (fs.existsSync(path.join(target, "ai", "VERSION"))) {
|
|
146
150
|
const v = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim();
|
|
147
|
-
|
|
151
|
+
log(`ai/ layer already exists (v${v}). Run '0dai sync' to update.`);
|
|
148
152
|
return;
|
|
149
153
|
}
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
log("collecting project metadata...");
|
|
152
156
|
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
153
157
|
|
|
154
|
-
|
|
158
|
+
log(`sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
155
159
|
const result = await apiCall("/v1/init", {
|
|
156
160
|
project_files: projectFiles,
|
|
157
161
|
file_contents: fileContents,
|
|
@@ -160,15 +164,15 @@ async function cmdInit(target) {
|
|
|
160
164
|
|
|
161
165
|
if (result.error) {
|
|
162
166
|
if (result.hint) {
|
|
163
|
-
|
|
167
|
+
log(`${result.message || result.error}`);
|
|
164
168
|
console.log(` ${result.hint}\n`);
|
|
165
169
|
} else {
|
|
166
|
-
|
|
170
|
+
log(`error: ${result.error}`);
|
|
167
171
|
}
|
|
168
172
|
process.exit(1);
|
|
169
173
|
}
|
|
170
174
|
|
|
171
|
-
|
|
175
|
+
log(`detected: ${result.stack || "?"}`);
|
|
172
176
|
writeFiles(target, result.files || {});
|
|
173
177
|
|
|
174
178
|
// Add to .gitignore
|
|
@@ -178,15 +182,38 @@ async function cmdInit(target) {
|
|
|
178
182
|
if (!text.includes(".0dai")) fs.appendFileSync(gi, "\n.0dai/\n");
|
|
179
183
|
} catch {}
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
log(`initialized (${result.file_count || "?"} files)`);
|
|
182
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
|
+
}
|
|
183
204
|
}
|
|
184
205
|
|
|
185
206
|
async function cmdSync(target) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
let version = "unknown", stack = "generic", agents = [];
|
|
207
|
+
// Quick local check: skip API if already at current version
|
|
208
|
+
let version = "unknown";
|
|
189
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 = [];
|
|
190
217
|
try {
|
|
191
218
|
const d = JSON.parse(fs.readFileSync(path.join(target, "ai", "manifest", "discovery.json"), "utf8"));
|
|
192
219
|
stack = d.stack || "generic";
|
|
@@ -217,24 +244,24 @@ async function cmdSync(target) {
|
|
|
217
244
|
current_files: currentFiles, file_contents: fileContents,
|
|
218
245
|
});
|
|
219
246
|
|
|
220
|
-
if (result.error) {
|
|
247
|
+
if (result.error) { log(`error: ${result.error}`); process.exit(1); }
|
|
221
248
|
|
|
222
249
|
const updated = result.files_updated || {};
|
|
223
250
|
if (Object.keys(updated).length) writeFiles(target, updated);
|
|
224
|
-
else
|
|
251
|
+
else log("already up to date");
|
|
225
252
|
}
|
|
226
253
|
|
|
227
254
|
async function cmdDetect(target) {
|
|
228
255
|
const { projectFiles } = collectMetadata(target);
|
|
229
256
|
const result = await apiCall("/v1/detect", { files: projectFiles });
|
|
230
|
-
if (result.error) {
|
|
257
|
+
if (result.error) { log(`error: ${result.error}`); return; }
|
|
231
258
|
console.log(`stack: ${result.stack || "?"}`);
|
|
232
259
|
console.log(`clis: ${(result.available_clis || []).join(",")}`);
|
|
233
260
|
}
|
|
234
261
|
|
|
235
262
|
function cmdDoctor(target) {
|
|
236
263
|
const ai = path.join(target, "ai");
|
|
237
|
-
if (!fs.existsSync(ai)) {
|
|
264
|
+
if (!fs.existsSync(ai)) { log("no ai/ layer. Run '0dai init' first."); return; }
|
|
238
265
|
let v = "?";
|
|
239
266
|
try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
|
|
240
267
|
const checks = {
|
|
@@ -246,7 +273,7 @@ function cmdDoctor(target) {
|
|
|
246
273
|
"AGENTS.md": fs.existsSync(path.join(target, "AGENTS.md")),
|
|
247
274
|
};
|
|
248
275
|
const ok = Object.values(checks).every(Boolean);
|
|
249
|
-
|
|
276
|
+
log(`v${v} — ${ok ? "healthy" : "issues found"}`);
|
|
250
277
|
for (const [name, exists] of Object.entries(checks)) console.log(` ${exists ? "ok" : "MISSING"}: ${name}`);
|
|
251
278
|
}
|
|
252
279
|
|
|
@@ -255,7 +282,7 @@ function cmdStatus(target) {
|
|
|
255
282
|
let v = "?", stack = "?";
|
|
256
283
|
try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
|
|
257
284
|
try { stack = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")).stack || "?"; } catch {}
|
|
258
|
-
|
|
285
|
+
log(`v${v} | stack: ${stack}`);
|
|
259
286
|
|
|
260
287
|
const count = (dir) => { try { return fs.readdirSync(dir).filter(f => f.endsWith(".json")).length; } catch { return 0; } };
|
|
261
288
|
const q = count(path.join(ai, "swarm", "queue"));
|
|
@@ -271,17 +298,18 @@ function cmdStatus(target) {
|
|
|
271
298
|
|
|
272
299
|
async function checkVersion() {
|
|
273
300
|
try {
|
|
274
|
-
//
|
|
301
|
+
// Check interval: 1 hour during debug, configurable via env
|
|
302
|
+
const intervalSec = parseInt(process.env.ODAI_UPDATE_CHECK_INTERVAL || "3600");
|
|
275
303
|
let lastCheck = 0;
|
|
276
304
|
try { lastCheck = parseFloat(fs.readFileSync(VERSION_CHECK_FILE, "utf8")); } catch {}
|
|
277
|
-
if (Date.now() / 1000 - lastCheck <
|
|
305
|
+
if (Date.now() / 1000 - lastCheck < intervalSec) return;
|
|
278
306
|
|
|
279
307
|
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
280
308
|
fs.writeFileSync(VERSION_CHECK_FILE, String(Date.now() / 1000));
|
|
281
309
|
|
|
282
310
|
const result = await apiCall("/v1/version");
|
|
283
311
|
if (result.version && result.version !== VERSION) {
|
|
284
|
-
|
|
312
|
+
log(`Update available: ${VERSION} → ${result.version}`);
|
|
285
313
|
console.log(` Run: npm update -g @0dai-dev/cli\n`);
|
|
286
314
|
}
|
|
287
315
|
} catch {}
|
|
@@ -290,12 +318,12 @@ async function checkVersion() {
|
|
|
290
318
|
async function cmdAuthLogin() {
|
|
291
319
|
// Step 1: request device code
|
|
292
320
|
const result = await apiCall("/v1/auth/device", { client_id: "cli" });
|
|
293
|
-
if (result.error) {
|
|
321
|
+
if (result.error) { log(`error: ${result.error}`); process.exit(1); }
|
|
294
322
|
|
|
295
|
-
|
|
323
|
+
log(`Open this URL in your browser:\n`);
|
|
296
324
|
console.log(` ${result.verification_uri}\n`);
|
|
297
|
-
|
|
298
|
-
|
|
325
|
+
log(`Enter code: ${result.user_code}\n`);
|
|
326
|
+
log("Waiting for authorization...");
|
|
299
327
|
|
|
300
328
|
// Step 2: poll for token
|
|
301
329
|
const interval = (result.interval || 5) * 1000;
|
|
@@ -314,28 +342,28 @@ async function cmdAuthLogin() {
|
|
|
314
342
|
authenticated_at: new Date().toISOString(),
|
|
315
343
|
expires_at: poll.expires_at,
|
|
316
344
|
}, null, 2) + "\n", { mode: 0o600 });
|
|
317
|
-
|
|
345
|
+
log(`Logged in as ${poll.email} (${poll.plan} plan)`);
|
|
318
346
|
return;
|
|
319
347
|
}
|
|
320
348
|
if (poll.error && poll.error !== "authorization_pending") {
|
|
321
|
-
|
|
349
|
+
log(`${poll.error}`);
|
|
322
350
|
process.exit(1);
|
|
323
351
|
}
|
|
324
352
|
process.stdout.write(".");
|
|
325
353
|
}
|
|
326
|
-
|
|
354
|
+
log("Authorization timed out. Try again.");
|
|
327
355
|
process.exit(1);
|
|
328
356
|
}
|
|
329
357
|
|
|
330
358
|
function cmdAuthLogout() {
|
|
331
359
|
try { fs.unlinkSync(AUTH_FILE); } catch {}
|
|
332
|
-
|
|
360
|
+
log("Logged out");
|
|
333
361
|
}
|
|
334
362
|
|
|
335
363
|
async function cmdAuthStatus() {
|
|
336
364
|
try {
|
|
337
365
|
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
338
|
-
|
|
366
|
+
log(`${auth.email} (${auth.plan} plan)`);
|
|
339
367
|
// Get usage from API
|
|
340
368
|
const status = await apiCall("/v1/auth/status");
|
|
341
369
|
if (status.usage_today) {
|
|
@@ -344,8 +372,155 @@ async function cmdAuthStatus() {
|
|
|
344
372
|
console.log(` ${k}: ${v} / ${status.limits[k]}`);
|
|
345
373
|
}
|
|
346
374
|
} catch {
|
|
347
|
-
|
|
375
|
+
log("Not logged in. Run: 0dai auth login");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
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;
|
|
348
522
|
}
|
|
523
|
+
console.log("Usage: 0dai feedback [push|log|list] [--type ...] [--detail '...']");
|
|
349
524
|
}
|
|
350
525
|
|
|
351
526
|
async function main() {
|
|
@@ -374,15 +549,21 @@ async function main() {
|
|
|
374
549
|
console.log("Usage: 0dai auth [login|logout|status]");
|
|
375
550
|
}
|
|
376
551
|
break;
|
|
377
|
-
case "
|
|
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;
|
|
555
|
+
case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
|
|
378
556
|
case "help": case "--help": case "-h":
|
|
379
|
-
console.log(
|
|
557
|
+
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
380
558
|
console.log("Commands:");
|
|
381
559
|
console.log(" init Initialize ai/ layer (via API)");
|
|
382
560
|
console.log(" sync Update ai/ layer (via API)");
|
|
383
561
|
console.log(" detect Show detected stack");
|
|
384
562
|
console.log(" doctor Check health");
|
|
385
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");
|
|
386
567
|
console.log(" auth login Authenticate (device code flow)");
|
|
387
568
|
console.log(" auth logout Remove credentials");
|
|
388
569
|
console.log(" auth status Show account and usage");
|
|
@@ -390,9 +571,9 @@ async function main() {
|
|
|
390
571
|
console.log("https://0dai.dev");
|
|
391
572
|
break;
|
|
392
573
|
default:
|
|
393
|
-
|
|
574
|
+
log(`unknown command: ${cmd}. Run '0dai --help'`);
|
|
394
575
|
process.exit(1);
|
|
395
576
|
}
|
|
396
577
|
}
|
|
397
578
|
|
|
398
|
-
main().catch((e) => {
|
|
579
|
+
main().catch((e) => { log(`${e.message}`); process.exit(1); });
|