@0dai-dev/cli 2.2.0 → 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 +74 -1
- package/lib/session-manager.js +103 -0
- package/package.json +3 -2
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
|
|
@@ -490,6 +490,55 @@ async function cmdFeedbackPush(target) {
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
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
|
+
|
|
493
542
|
// --- Session (local, file-based) ---
|
|
494
543
|
function cmdSession(target, sub, args) {
|
|
495
544
|
const sessFile = path.join(target, "ai", "sessions", "active.json");
|
|
@@ -636,6 +685,28 @@ async function main() {
|
|
|
636
685
|
case "session": cmdSession(target, sub, args); break;
|
|
637
686
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
638
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;
|
|
639
710
|
case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
|
|
640
711
|
case "help": case "--help": case "-h":
|
|
641
712
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
@@ -648,6 +719,8 @@ async function main() {
|
|
|
648
719
|
console.log(" session save Save session for roaming");
|
|
649
720
|
console.log(" swarm status Task queue & delegation");
|
|
650
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");
|
|
651
724
|
console.log(" auth login Authenticate (device code flow)");
|
|
652
725
|
console.log(" auth logout Remove credentials");
|
|
653
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,6 +1,6 @@
|
|
|
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"
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@clack/prompts": "^1.2.0"
|
|
34
|
+
"@clack/prompts": "^1.2.0",
|
|
35
|
+
"node-pty": "^1.0.0"
|
|
35
36
|
}
|
|
36
37
|
}
|