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