@0dai-dev/cli 2.2.0 → 2.3.1
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 +76 -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,57 @@ 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: "Mimo v2 Pro", tier: "balanced", score: 80, cli: "opencode", flag: "-m opencode-go/mimo-v2-pro", tested: true },
|
|
505
|
+
{ name: "Mimo v2 Omni", tier: "fast", score: 78, cli: "opencode", flag: "-m opencode-go/mimo-v2-omni", tested: true },
|
|
506
|
+
{ name: "Kimi K2.5", tier: "balanced", score: 72, cli: "opencode", flag: "-m opencode-go/kimi-k2.5", tested: true },
|
|
507
|
+
{ name: "MiniMax M2.5", tier: "fast", score: 70, cli: "opencode", flag: "-m opencode-go/minimax-m2.5", tested: true },
|
|
508
|
+
{ name: "MiniMax M2.7", tier: "balanced", score: 68, cli: "opencode", flag: "-m opencode-go/minimax-m2.7", tested: true },
|
|
509
|
+
{ name: "Qwen 3.6+ Free", tier: "fast", score: 64, cli: "opencode", flag: "-m opencode/qwen3.6-plus-free", tested: true },
|
|
510
|
+
{ name: "GLM-5", tier: "fast", score: 62, cli: "opencode", flag: "-m opencode-go/glm-5", tested: true },
|
|
511
|
+
];
|
|
512
|
+
|
|
513
|
+
const { execFileSync } = require("child_process");
|
|
514
|
+
const available = new Set();
|
|
515
|
+
for (const cli of ["claude", "codex", "opencode", "gemini", "aider"]) {
|
|
516
|
+
try { execFileSync("/bin/sh", ["-c", `command -v ${cli}`], { stdio: "ignore" }); available.add(cli); } catch {}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const isTTY = process.stdout.isTTY;
|
|
520
|
+
const Y = isTTY ? "\x1b[33m" : "";
|
|
521
|
+
const G = isTTY ? "\x1b[32m" : "";
|
|
522
|
+
const DIM = isTTY ? "\x1b[2m" : "";
|
|
523
|
+
|
|
524
|
+
let models = [...MODELS].sort((a, b) => b.score - a.score);
|
|
525
|
+
if (filter === "--fast") models = models.filter(m => m.tier === "fast");
|
|
526
|
+
if (filter === "--balanced") models = models.filter(m => m.tier === "balanced");
|
|
527
|
+
if (filter === "--deep") models = models.filter(m => m.tier === "deep");
|
|
528
|
+
if (filter === "--available") models = models.filter(m => available.has(m.cli));
|
|
529
|
+
|
|
530
|
+
const tc = (t) => t === "deep" ? T : t === "balanced" ? G : DIM;
|
|
531
|
+
console.log(`\n ${T}0dai${R} model ratings — ${models.length} models\n`);
|
|
532
|
+
console.log(` ${"SCORE".padEnd(6)} ${"MODEL".padEnd(22)} ${"TIER".padEnd(10)} ${"CLI".padEnd(10)} FLAG`);
|
|
533
|
+
console.log(` ${"-".repeat(64)}`);
|
|
534
|
+
for (const m of models) {
|
|
535
|
+
const dim = available.has(m.cli) ? "" : DIM;
|
|
536
|
+
const mark = m.tested ? ` ${G}✓${R}` : "";
|
|
537
|
+
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 : ""}`);
|
|
538
|
+
}
|
|
539
|
+
console.log(`\n ${DIM}✓ = swarm-benchmarked | dimmed = CLI not installed${R}`);
|
|
540
|
+
console.log(` ${DIM}Filter: --fast --balanced --deep --available${R}`);
|
|
541
|
+
console.log(` ${DIM}Full table: https://0dai.dev/models${R}\n`);
|
|
542
|
+
}
|
|
543
|
+
|
|
493
544
|
// --- Session (local, file-based) ---
|
|
494
545
|
function cmdSession(target, sub, args) {
|
|
495
546
|
const sessFile = path.join(target, "ai", "sessions", "active.json");
|
|
@@ -636,6 +687,28 @@ async function main() {
|
|
|
636
687
|
case "session": cmdSession(target, sub, args); break;
|
|
637
688
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
638
689
|
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
690
|
+
case "models": cmdModels(sub || args[1]); break;
|
|
691
|
+
case "terminal": case "term":
|
|
692
|
+
try {
|
|
693
|
+
const SessionManager = require("../lib/session-manager");
|
|
694
|
+
const sm = new SessionManager();
|
|
695
|
+
if (sub === "launch" || !sub) {
|
|
696
|
+
const tool = args.find((_, i) => args[i - 1] === "--tool") || "codex";
|
|
697
|
+
const id = sm.spawn(tool, [], target);
|
|
698
|
+
log(`session ${id.slice(0, 8)} started (${tool})`);
|
|
699
|
+
sm.attach(id);
|
|
700
|
+
} else if (sub === "list") {
|
|
701
|
+
const sessions = sm.list();
|
|
702
|
+
if (!sessions.length) { log("no active sessions"); break; }
|
|
703
|
+
for (const s of sessions) console.log(` ${s.id.slice(0, 8)} [${s.tool}] ${s.status} ${s.attached ? "(attached)" : ""}`);
|
|
704
|
+
} else {
|
|
705
|
+
console.log("Usage: 0dai terminal [launch|list] [--tool codex|claude|gemini]");
|
|
706
|
+
}
|
|
707
|
+
} catch (e) {
|
|
708
|
+
if (e.code === "MODULE_NOT_FOUND") log("install node-pty first: cd ~/.0dai && npm install node-pty");
|
|
709
|
+
else log(`error: ${e.message}`);
|
|
710
|
+
}
|
|
711
|
+
break;
|
|
639
712
|
case "--version": console.log(`${T}0dai${R} ${VERSION}`); break;
|
|
640
713
|
case "help": case "--help": case "-h":
|
|
641
714
|
console.log(`\n ${T}0dai${R} v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
@@ -648,6 +721,8 @@ async function main() {
|
|
|
648
721
|
console.log(" session save Save session for roaming");
|
|
649
722
|
console.log(" swarm status Task queue & delegation");
|
|
650
723
|
console.log(" feedback push Send feedback to 0dai");
|
|
724
|
+
console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
|
|
725
|
+
console.log(" terminal Launch interactive agent session");
|
|
651
726
|
console.log(" auth login Authenticate (device code flow)");
|
|
652
727
|
console.log(" auth logout Remove credentials");
|
|
653
728
|
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.1",
|
|
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
|
}
|