@0dai-dev/cli 2.1.1 → 2.3.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 +202 -38
- package/lib/session-manager.js +103 -0
- package/package.json +16 -3
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.3.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
|
|
@@ -152,10 +152,15 @@ async function cmdInit(target) {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
const isTTY = process.stdout.isTTY;
|
|
156
|
+
let spinner = null;
|
|
157
|
+
if (isTTY) {
|
|
158
|
+
try { spinner = require("@clack/prompts").spinner(); } catch {}
|
|
159
|
+
}
|
|
157
160
|
|
|
158
|
-
|
|
161
|
+
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
162
|
+
if (spinner) spinner.start(`Generating ai/ layer (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
163
|
+
else log(`sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
159
164
|
const result = await apiCall("/v1/init", {
|
|
160
165
|
project_files: projectFiles,
|
|
161
166
|
file_contents: fileContents,
|
|
@@ -172,7 +177,8 @@ async function cmdInit(target) {
|
|
|
172
177
|
process.exit(1);
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
if (spinner) spinner.stop(`Detected: ${result.stack || "?"}`);
|
|
181
|
+
else log(`detected: ${result.stack || "?"}`);
|
|
176
182
|
writeFiles(target, result.files || {});
|
|
177
183
|
|
|
178
184
|
// Add to .gitignore
|
|
@@ -310,43 +316,128 @@ async function checkVersion() {
|
|
|
310
316
|
}
|
|
311
317
|
|
|
312
318
|
async function cmdAuthLogin() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (
|
|
319
|
+
const isTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
320
|
+
|
|
321
|
+
if (isTTY) {
|
|
322
|
+
// Interactive TUI flow
|
|
323
|
+
const p = require("@clack/prompts");
|
|
324
|
+
p.intro(`${T}0dai${R} authentication`);
|
|
325
|
+
|
|
326
|
+
const method = await p.select({
|
|
327
|
+
message: "How would you like to sign in?",
|
|
328
|
+
options: [
|
|
329
|
+
{ value: "github", label: "GitHub", hint: "recommended" },
|
|
330
|
+
{ value: "google", label: "Google" },
|
|
331
|
+
{ value: "device", label: "Device code", hint: "no browser needed" },
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(method)) { p.cancel("Cancelled"); process.exit(0); }
|
|
335
|
+
|
|
336
|
+
if (method === "github" || method === "google") {
|
|
337
|
+
const url = `${API_URL}/v1/auth/${method}?cli=true`;
|
|
338
|
+
p.log.info(`Opening browser: ${url}`);
|
|
339
|
+
try {
|
|
340
|
+
const { execSync } = require("child_process");
|
|
341
|
+
const cmd = os.platform() === "darwin" ? "open" : os.platform() === "win32" ? "start" : "xdg-open";
|
|
342
|
+
execSync(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
343
|
+
} catch {
|
|
344
|
+
p.log.warn(`Could not open browser. Visit manually:\n ${url}`);
|
|
345
|
+
}
|
|
316
346
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
const s = p.spinner();
|
|
348
|
+
s.start("Waiting for browser confirmation...");
|
|
349
|
+
|
|
350
|
+
// Poll auth/status until we get a new token (check every 3s, 5min timeout)
|
|
351
|
+
// For now, ask user to paste token from success page
|
|
352
|
+
s.stop("Browser opened");
|
|
353
|
+
const token = await p.text({
|
|
354
|
+
message: "Paste your token from the success page (or press Enter to skip):",
|
|
355
|
+
placeholder: "0dai_at_...",
|
|
356
|
+
});
|
|
357
|
+
if (token && !p.isCancel(token) && token.startsWith("0dai_at_")) {
|
|
358
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
359
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
360
|
+
access_token: token,
|
|
361
|
+
authenticated_at: new Date().toISOString(),
|
|
362
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
363
|
+
// Fetch profile
|
|
364
|
+
const status = await apiCall("/v1/auth/status");
|
|
365
|
+
if (status.email) {
|
|
366
|
+
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
367
|
+
auth.email = status.email;
|
|
368
|
+
auth.plan = status.plan;
|
|
369
|
+
auth.name = status.name;
|
|
370
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2) + "\n", { mode: 0o600 });
|
|
371
|
+
p.outro(`${T}Logged in${R} as ${status.email} (${status.plan} plan)`);
|
|
372
|
+
} else {
|
|
373
|
+
p.outro(`${T}Token saved${R}`);
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
p.log.info("Skipped. You can also use device code flow:");
|
|
341
378
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
379
|
+
|
|
380
|
+
// Device code fallback
|
|
381
|
+
const result = await apiCall("/v1/auth/device", { client_id: "cli" });
|
|
382
|
+
if (result.error) { p.log.error(result.error); process.exit(1); }
|
|
383
|
+
|
|
384
|
+
p.log.step(`Open: ${result.verification_uri}`);
|
|
385
|
+
p.log.step(`Code: ${T}${result.user_code}${R}`);
|
|
386
|
+
|
|
387
|
+
const s = p.spinner();
|
|
388
|
+
s.start("Waiting for confirmation...");
|
|
389
|
+
|
|
390
|
+
const interval = (result.interval || 5) * 1000;
|
|
391
|
+
const deadline = Date.now() + (result.expires_in || 600) * 1000;
|
|
392
|
+
while (Date.now() < deadline) {
|
|
393
|
+
await new Promise(r => setTimeout(r, interval));
|
|
394
|
+
const poll = await apiCall("/v1/auth/token", { device_code: result.device_code });
|
|
395
|
+
if (poll.access_token) {
|
|
396
|
+
s.stop("Authorized!");
|
|
397
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
398
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
399
|
+
access_token: poll.access_token, email: poll.email,
|
|
400
|
+
plan: poll.plan || "free", authenticated_at: new Date().toISOString(),
|
|
401
|
+
expires_at: poll.expires_at,
|
|
402
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
403
|
+
p.outro(`${T}Logged in${R} as ${poll.email} (${poll.plan} plan)`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (poll.error && poll.error !== "authorization_pending") {
|
|
407
|
+
s.stop("Failed");
|
|
408
|
+
p.log.error(poll.error);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
s.stop("Timed out");
|
|
413
|
+
p.log.error("Try again.");
|
|
414
|
+
process.exit(1);
|
|
415
|
+
|
|
416
|
+
} else {
|
|
417
|
+
// Non-interactive: device code only
|
|
418
|
+
const result = await apiCall("/v1/auth/device", { client_id: "cli" });
|
|
419
|
+
if (result.error) { log(`error: ${result.error}`); process.exit(1); }
|
|
420
|
+
log(`Open: ${result.verification_uri}`);
|
|
421
|
+
log(`Code: ${result.user_code}`);
|
|
422
|
+
log("Waiting...");
|
|
423
|
+
const interval = (result.interval || 5) * 1000;
|
|
424
|
+
const deadline = Date.now() + (result.expires_in || 600) * 1000;
|
|
425
|
+
while (Date.now() < deadline) {
|
|
426
|
+
await new Promise(r => setTimeout(r, interval));
|
|
427
|
+
const poll = await apiCall("/v1/auth/token", { device_code: result.device_code });
|
|
428
|
+
if (poll.access_token) {
|
|
429
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
430
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
431
|
+
access_token: poll.access_token, email: poll.email,
|
|
432
|
+
plan: poll.plan || "free", authenticated_at: new Date().toISOString(),
|
|
433
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
434
|
+
log(`Logged in as ${poll.email}`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
345
437
|
}
|
|
346
|
-
|
|
438
|
+
log("Timed out");
|
|
439
|
+
process.exit(1);
|
|
347
440
|
}
|
|
348
|
-
log("Authorization timed out. Try again.");
|
|
349
|
-
process.exit(1);
|
|
350
441
|
}
|
|
351
442
|
|
|
352
443
|
function cmdAuthLogout() {
|
|
@@ -399,6 +490,55 @@ async function cmdFeedbackPush(target) {
|
|
|
399
490
|
}
|
|
400
491
|
}
|
|
401
492
|
|
|
493
|
+
// --- Models ---
|
|
494
|
+
function cmdModels(filter) {
|
|
495
|
+
const MODELS = [
|
|
496
|
+
{ name: "Claude Opus 4.6", tier: "deep", score: 95, cli: "claude", flag: "--model opus" },
|
|
497
|
+
{ name: "Claude Sonnet 4.6", tier: "balanced", score: 90, cli: "claude", flag: "--model sonnet" },
|
|
498
|
+
{ name: "Claude Haiku 4.5", tier: "fast", score: 78, cli: "claude", flag: "--model haiku" },
|
|
499
|
+
{ name: "GPT-5.3 Codex", tier: "deep", score: 91, cli: "codex", flag: "-m gpt-5.3-codex" },
|
|
500
|
+
{ name: "GPT-5.4", tier: "balanced", score: 89, cli: "codex", flag: "-m gpt-5.4", tested: true },
|
|
501
|
+
{ name: "GPT-5.4-mini", tier: "fast", score: 76, cli: "codex", flag: "-m gpt-5.4-mini", tested: true },
|
|
502
|
+
{ name: "Gemini 3.1 Pro", tier: "balanced", score: 85, cli: "gemini", flag: "-m gemini-3.1-pro" },
|
|
503
|
+
{ name: "Gemini 3 Flash", tier: "fast", score: 77, cli: "gemini", flag: "-m gemini-3-flash" },
|
|
504
|
+
{ name: "Kimi K2.5", tier: "balanced", score: 74, cli: "opencode", flag: "-m opencode-go/kimi-k2.5", tested: true },
|
|
505
|
+
{ name: "MiniMax M2.7", tier: "balanced", score: 72, cli: "opencode", flag: "-m opencode-go/minimax-m2.7", tested: true },
|
|
506
|
+
{ name: "GLM-5", tier: "fast", score: 68, cli: "opencode", flag: "-m opencode-go/glm-5", tested: true },
|
|
507
|
+
{ name: "MiniMax M2.5", tier: "fast", score: 66, cli: "opencode", flag: "-m opencode-go/minimax-m2.5", tested: true },
|
|
508
|
+
{ name: "Qwen 3.6+ Free", tier: "fast", score: 64, cli: "opencode", flag: "-m opencode/qwen3.6-plus-free", tested: true },
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
const { execFileSync } = require("child_process");
|
|
512
|
+
const available = new Set();
|
|
513
|
+
for (const cli of ["claude", "codex", "opencode", "gemini", "aider"]) {
|
|
514
|
+
try { execFileSync("/bin/sh", ["-c", `command -v ${cli}`], { stdio: "ignore" }); available.add(cli); } catch {}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const isTTY = process.stdout.isTTY;
|
|
518
|
+
const Y = isTTY ? "\x1b[33m" : "";
|
|
519
|
+
const G = isTTY ? "\x1b[32m" : "";
|
|
520
|
+
const DIM = isTTY ? "\x1b[2m" : "";
|
|
521
|
+
|
|
522
|
+
let models = [...MODELS].sort((a, b) => b.score - a.score);
|
|
523
|
+
if (filter === "--fast") models = models.filter(m => m.tier === "fast");
|
|
524
|
+
if (filter === "--balanced") models = models.filter(m => m.tier === "balanced");
|
|
525
|
+
if (filter === "--deep") models = models.filter(m => m.tier === "deep");
|
|
526
|
+
if (filter === "--available") models = models.filter(m => available.has(m.cli));
|
|
527
|
+
|
|
528
|
+
const tc = (t) => t === "deep" ? T : t === "balanced" ? G : DIM;
|
|
529
|
+
console.log(`\n ${T}0dai${R} model ratings — ${models.length} models\n`);
|
|
530
|
+
console.log(` ${"SCORE".padEnd(6)} ${"MODEL".padEnd(22)} ${"TIER".padEnd(10)} ${"CLI".padEnd(10)} FLAG`);
|
|
531
|
+
console.log(` ${"-".repeat(64)}`);
|
|
532
|
+
for (const m of models) {
|
|
533
|
+
const dim = available.has(m.cli) ? "" : DIM;
|
|
534
|
+
const mark = m.tested ? ` ${G}✓${R}` : "";
|
|
535
|
+
console.log(`${dim} ${Y}${String(m.score).padEnd(6)}${R} ${m.name.padEnd(22)} ${tc(m.tier)}${m.tier.padEnd(10)}${R} ${m.cli.padEnd(10)} ${DIM}${m.flag}${R}${mark}${dim ? R : ""}`);
|
|
536
|
+
}
|
|
537
|
+
console.log(`\n ${DIM}✓ = swarm-benchmarked | dimmed = CLI not installed${R}`);
|
|
538
|
+
console.log(` ${DIM}Filter: --fast --balanced --deep --available${R}`);
|
|
539
|
+
console.log(` ${DIM}Full table: https://0dai.dev/models${R}\n`);
|
|
540
|
+
}
|
|
541
|
+
|
|
402
542
|
// --- Session (local, file-based) ---
|
|
403
543
|
function cmdSession(target, sub, args) {
|
|
404
544
|
const sessFile = path.join(target, "ai", "sessions", "active.json");
|
|
@@ -545,6 +685,28 @@ async function main() {
|
|
|
545
685
|
case "session": cmdSession(target, sub, args); break;
|
|
546
686
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
547
687
|
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
688
|
+
case "models": cmdModels(sub || args[1]); break;
|
|
689
|
+
case "terminal": case "term":
|
|
690
|
+
try {
|
|
691
|
+
const SessionManager = require("../lib/session-manager");
|
|
692
|
+
const sm = new SessionManager();
|
|
693
|
+
if (sub === "launch" || !sub) {
|
|
694
|
+
const tool = args.find((_, i) => args[i - 1] === "--tool") || "codex";
|
|
695
|
+
const id = sm.spawn(tool, [], target);
|
|
696
|
+
log(`session ${id.slice(0, 8)} started (${tool})`);
|
|
697
|
+
sm.attach(id);
|
|
698
|
+
} else if (sub === "list") {
|
|
699
|
+
const sessions = sm.list();
|
|
700
|
+
if (!sessions.length) { log("no active sessions"); break; }
|
|
701
|
+
for (const s of sessions) console.log(` ${s.id.slice(0, 8)} [${s.tool}] ${s.status} ${s.attached ? "(attached)" : ""}`);
|
|
702
|
+
} else {
|
|
703
|
+
console.log("Usage: 0dai terminal [launch|list] [--tool codex|claude|gemini]");
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
if (e.code === "MODULE_NOT_FOUND") log("install node-pty first: cd ~/.0dai && npm install node-pty");
|
|
707
|
+
else log(`error: ${e.message}`);
|
|
708
|
+
}
|
|
709
|
+
break;
|
|
548
710
|
case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
|
|
549
711
|
case "help": case "--help": case "-h":
|
|
550
712
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
@@ -557,6 +719,8 @@ async function main() {
|
|
|
557
719
|
console.log(" session save Save session for roaming");
|
|
558
720
|
console.log(" swarm status Task queue & delegation");
|
|
559
721
|
console.log(" feedback push Send feedback to 0dai");
|
|
722
|
+
console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
|
|
723
|
+
console.log(" terminal Launch interactive agent session");
|
|
560
724
|
console.log(" auth login Authenticate (device code flow)");
|
|
561
725
|
console.log(" auth logout Remove credentials");
|
|
562
726
|
console.log(" auth status Show account and usage");
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require("crypto");
|
|
4
|
+
const pty = require("node-pty");
|
|
5
|
+
|
|
6
|
+
class SessionManager {
|
|
7
|
+
constructor(opts = {}) {
|
|
8
|
+
this.sessions = new Map();
|
|
9
|
+
this.activeId = null;
|
|
10
|
+
this.bufferSize = opts.bufferSize || 65536;
|
|
11
|
+
this._stdin = null;
|
|
12
|
+
this._resize = null;
|
|
13
|
+
this._cleanup = () => this.detach();
|
|
14
|
+
process.once("exit", this._cleanup);
|
|
15
|
+
process.once("SIGINT", () => { this.detach(); process.exit(130); });
|
|
16
|
+
process.once("SIGTERM", () => { this.detach(); process.exit(143); });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
spawn(tool, args = [], cwd = process.cwd()) {
|
|
20
|
+
const id = randomUUID();
|
|
21
|
+
const proc = pty.spawn(tool, args, {
|
|
22
|
+
cwd,
|
|
23
|
+
env: process.env,
|
|
24
|
+
name: "xterm-color",
|
|
25
|
+
cols: process.stdout.columns || 80,
|
|
26
|
+
rows: process.stdout.rows || 24,
|
|
27
|
+
});
|
|
28
|
+
const session = {
|
|
29
|
+
id,
|
|
30
|
+
tool,
|
|
31
|
+
command: [tool].concat(args).join(" "),
|
|
32
|
+
cwd,
|
|
33
|
+
status: "running",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
lastActivityAt: new Date().toISOString(),
|
|
36
|
+
buffer: "",
|
|
37
|
+
exitCode: null,
|
|
38
|
+
signal: null,
|
|
39
|
+
proc,
|
|
40
|
+
};
|
|
41
|
+
proc.onData((data) => {
|
|
42
|
+
session.lastActivityAt = new Date().toISOString();
|
|
43
|
+
session.buffer = (session.buffer + data).slice(-this.bufferSize);
|
|
44
|
+
if (this.activeId === id) process.stdout.write(data);
|
|
45
|
+
});
|
|
46
|
+
proc.onExit(({ exitCode, signal }) => {
|
|
47
|
+
session.status = "exited";
|
|
48
|
+
session.exitCode = exitCode;
|
|
49
|
+
session.signal = signal;
|
|
50
|
+
if (this.activeId === id) this.detach();
|
|
51
|
+
});
|
|
52
|
+
this.sessions.set(id, session);
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
attach(sessionId) {
|
|
57
|
+
const session = this.sessions.get(sessionId);
|
|
58
|
+
if (!session) throw new Error(`Unknown session: ${sessionId}`);
|
|
59
|
+
if (session.status !== "running") throw new Error(`Session is ${session.status}: ${sessionId}`);
|
|
60
|
+
if (this.activeId && this.activeId !== sessionId) this.detach();
|
|
61
|
+
this.activeId = sessionId;
|
|
62
|
+
if (session.buffer) process.stdout.write(session.buffer);
|
|
63
|
+
this._stdin = (data) => session.proc.write(data);
|
|
64
|
+
this._resize = () => session.proc.resize(process.stdout.columns || 80, process.stdout.rows || 24);
|
|
65
|
+
if (process.stdin.isTTY) {
|
|
66
|
+
process.stdin.setRawMode(true);
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
}
|
|
69
|
+
process.stdin.on("data", this._stdin);
|
|
70
|
+
process.stdout.on("resize", this._resize);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
detach() {
|
|
74
|
+
if (!this.activeId) return;
|
|
75
|
+
if (this._stdin) process.stdin.off("data", this._stdin);
|
|
76
|
+
if (this._resize) process.stdout.off("resize", this._resize);
|
|
77
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
78
|
+
this._stdin = null;
|
|
79
|
+
this._resize = null;
|
|
80
|
+
this.activeId = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
list() {
|
|
84
|
+
return Array.from(this.sessions.values())
|
|
85
|
+
.filter((session) => session.status === "running")
|
|
86
|
+
.map(({ proc, buffer, ...session }) => ({
|
|
87
|
+
...session,
|
|
88
|
+
attached: session.id === this.activeId,
|
|
89
|
+
bufferedBytes: Buffer.byteLength(buffer),
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
kill(sessionId) {
|
|
94
|
+
const session = this.sessions.get(sessionId);
|
|
95
|
+
if (!session) return false;
|
|
96
|
+
if (this.activeId === sessionId) this.detach();
|
|
97
|
+
session.proc.kill();
|
|
98
|
+
this.sessions.delete(sessionId);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = SessionManager;
|
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0dai-dev/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider",
|
|
5
5
|
"bin": {
|
|
6
6
|
"0dai": "./bin/0dai.js"
|
|
7
7
|
},
|
|
8
|
-
"keywords": [
|
|
8
|
+
"keywords": [
|
|
9
|
+
"ai",
|
|
10
|
+
"agents",
|
|
11
|
+
"claude",
|
|
12
|
+
"codex",
|
|
13
|
+
"gemini",
|
|
14
|
+
"aider",
|
|
15
|
+
"developer-tools",
|
|
16
|
+
"mcp"
|
|
17
|
+
],
|
|
9
18
|
"author": "0dai-dev <dev@0dai.dev>",
|
|
10
19
|
"license": "MIT",
|
|
11
20
|
"repository": {
|
|
@@ -20,5 +29,9 @@
|
|
|
20
29
|
"bin/",
|
|
21
30
|
"lib/",
|
|
22
31
|
"README.md"
|
|
23
|
-
]
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.2.0",
|
|
35
|
+
"node-pty": "^1.0.0"
|
|
36
|
+
}
|
|
24
37
|
}
|