@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 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.2.0";
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.2.0",
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
  }