@0dai-dev/cli 4.1.0 → 4.3.4
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/README.md +30 -5
- package/bin/0dai.js +308 -60
- package/lib/commands/audit.js +13 -0
- package/lib/commands/auth.js +404 -122
- package/lib/commands/boneyard.js +44 -0
- package/lib/commands/ci.js +329 -0
- package/lib/commands/compliance.js +20 -0
- package/lib/commands/doctor.js +79 -14
- package/lib/commands/experience.js +5 -1
- package/lib/commands/feedback.js +92 -5
- package/lib/commands/gh.js +506 -0
- package/lib/commands/graph.js +78 -10
- package/lib/commands/heatmap.js +17 -0
- package/lib/commands/import_claude_code_agents.js +367 -0
- package/lib/commands/init.js +553 -53
- package/lib/commands/loop.js +108 -0
- package/lib/commands/mcp.js +410 -0
- package/lib/commands/models.js +42 -12
- package/lib/commands/paste.js +114 -0
- package/lib/commands/persona-simulate.js +19 -0
- package/lib/commands/play.js +173 -0
- package/lib/commands/provider.js +87 -0
- package/lib/commands/quota.js +76 -0
- package/lib/commands/receipt.js +53 -0
- package/lib/commands/report.js +29 -2
- package/lib/commands/run.js +44 -4
- package/lib/commands/runner.js +527 -0
- package/lib/commands/session.js +1 -7
- package/lib/commands/ssh.js +416 -0
- package/lib/commands/standup.js +40 -0
- package/lib/commands/status.js +131 -36
- package/lib/commands/swarm.js +97 -4
- package/lib/commands/tui.js +117 -0
- package/lib/commands/usage.js +87 -0
- package/lib/commands/vault.js +246 -0
- package/lib/commands/workspace.js +1 -0
- package/lib/onboarding.js +30 -10
- package/lib/shared.js +153 -96
- package/lib/tui/index.mjs +34994 -0
- package/lib/utils/auth.js +1 -0
- package/lib/utils/canonical-counts.js +54 -0
- package/lib/utils/diff-preview.js +192 -0
- package/lib/utils/identity.js +76 -18
- package/lib/utils/mcp-auth.js +607 -0
- package/lib/utils/model_ratings.js +77 -0
- package/lib/utils/plan.js +37 -2
- package/lib/vault/cipher.js +125 -0
- package/lib/vault/identity.js +122 -0
- package/lib/vault/index.js +184 -0
- package/lib/vault/storage.js +84 -0
- package/lib/wizard.js +19 -12
- package/package.json +13 -5
- package/scripts/build-tui.js +77 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const shared = require("../shared");
|
|
3
|
+
const { log, T, R, D, apiCall } = shared;
|
|
4
|
+
|
|
5
|
+
const MAX_PASTE_BYTES = 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
function formatBytes(bytes) {
|
|
8
|
+
if (bytes >= 1024 * 1024) {
|
|
9
|
+
const mib = bytes / (1024 * 1024);
|
|
10
|
+
return `${Number.isInteger(mib) ? mib : mib.toFixed(1)} MiB`;
|
|
11
|
+
}
|
|
12
|
+
if (bytes >= 1024) {
|
|
13
|
+
const kib = bytes / 1024;
|
|
14
|
+
return `${Number.isInteger(kib) ? kib : kib.toFixed(1)} KiB`;
|
|
15
|
+
}
|
|
16
|
+
return `${bytes} B`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readStdin({ maxBytes = MAX_PASTE_BYTES } = {}) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
if (process.stdin.isTTY) {
|
|
22
|
+
reject(new Error("no stdin — pipe text in, e.g. `cat prompt.md | 0dai paste`"));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let bytes = 0;
|
|
27
|
+
let settled = false;
|
|
28
|
+
|
|
29
|
+
const cleanup = () => {
|
|
30
|
+
process.stdin.off("data", onData);
|
|
31
|
+
process.stdin.off("end", onEnd);
|
|
32
|
+
process.stdin.off("error", onError);
|
|
33
|
+
};
|
|
34
|
+
const fail = (error) => {
|
|
35
|
+
if (settled) return;
|
|
36
|
+
settled = true;
|
|
37
|
+
cleanup();
|
|
38
|
+
try { process.stdin.pause(); } catch {}
|
|
39
|
+
reject(error);
|
|
40
|
+
};
|
|
41
|
+
const onData = (chunk) => {
|
|
42
|
+
const text = String(chunk);
|
|
43
|
+
bytes += Buffer.byteLength(text, "utf8");
|
|
44
|
+
if (bytes > maxBytes) {
|
|
45
|
+
fail(new Error(`paste body exceeds ${formatBytes(maxBytes)} limit (received at least ${formatBytes(bytes)})`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
chunks.push(text);
|
|
49
|
+
};
|
|
50
|
+
const onEnd = () => {
|
|
51
|
+
if (settled) return;
|
|
52
|
+
settled = true;
|
|
53
|
+
cleanup();
|
|
54
|
+
resolve(chunks.join(""));
|
|
55
|
+
};
|
|
56
|
+
const onError = (error) => fail(error);
|
|
57
|
+
|
|
58
|
+
process.stdin.setEncoding("utf8");
|
|
59
|
+
process.stdin.on("data", onData);
|
|
60
|
+
process.stdin.on("end", onEnd);
|
|
61
|
+
process.stdin.on("error", onError);
|
|
62
|
+
process.stdin.resume();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseArgs(args) {
|
|
67
|
+
const out = { title: "", author: "", tags: [], quiet: false };
|
|
68
|
+
for (let i = 0; i < args.length; i++) {
|
|
69
|
+
const a = args[i];
|
|
70
|
+
if (a === "--title") out.title = args[++i] || "";
|
|
71
|
+
else if (a === "--author") out.author = args[++i] || "";
|
|
72
|
+
else if (a === "--tag") out.tags.push(args[++i] || "");
|
|
73
|
+
else if (a === "-q" || a === "--quiet") out.quiet = true;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function cmdPaste(args) {
|
|
79
|
+
const opts = parseArgs(args);
|
|
80
|
+
let body;
|
|
81
|
+
try {
|
|
82
|
+
body = await readStdin();
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`error: ${e.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
if (!body || !body.trim()) {
|
|
88
|
+
console.error("error: paste body is empty");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const result = await apiCall("/v1/paste", {
|
|
93
|
+
body,
|
|
94
|
+
title: opts.title,
|
|
95
|
+
author: opts.author,
|
|
96
|
+
tags: opts.tags,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (result.error) {
|
|
100
|
+
console.error(`error: ${result.error}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (opts.quiet) {
|
|
105
|
+
console.log(result.url || result.slug || "");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(`${T}slug:${R} ${result.slug}`);
|
|
109
|
+
console.log(`${T}url: ${R} ${result.url}`);
|
|
110
|
+
console.log(`${T}raw: ${R} ${result.raw_url}`);
|
|
111
|
+
console.log(`${D}size: ${result.size_bytes} bytes${R}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { cmdPaste, readStdin, parseArgs, MAX_PASTE_BYTES, formatBytes };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const shared = require("../shared");
|
|
3
|
+
const { log, D, R, spawnSync, findRepoScript } = shared;
|
|
4
|
+
|
|
5
|
+
function cmdPersonaSimulate(target, args) {
|
|
6
|
+
const script = findRepoScript(target, "persona_simulate.py");
|
|
7
|
+
if (!script) {
|
|
8
|
+
log("persona-simulate unavailable in this environment");
|
|
9
|
+
console.log(` ${D}Expected scripts/persona_simulate.py in repo checkout${R}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const forwarded = [script, ...args, "--target", target];
|
|
14
|
+
const result = spawnSync("python3", forwarded, { stdio: "inherit" });
|
|
15
|
+
if (typeof result.status === "number") process.exit(result.status);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { cmdPersonaSimulate };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Play Book CLI — curated plays come from the server-side registry
|
|
3
|
+
// (/v1/plays, gated by plan). Local plays live under ai/plays/ inside
|
|
4
|
+
// the project and support fork/rank/preview via scripts/play_book.py.
|
|
5
|
+
//
|
|
6
|
+
// Subcommands:
|
|
7
|
+
// 0dai play list # curated plays (server) + local (disk)
|
|
8
|
+
// 0dai play get <slug> # show details of one play
|
|
9
|
+
// 0dai play search <query…> # substring search over local + server
|
|
10
|
+
// 0dai play fork <slug> --as <new> --author <handle>
|
|
11
|
+
// 0dai play run <slug> [--var K=V …]
|
|
12
|
+
// 0dai play rank # local-only ranking (uses ai/outcomes if present)
|
|
13
|
+
const shared = require("../shared");
|
|
14
|
+
const { log, T, R, D, apiCall } = shared;
|
|
15
|
+
const { spawnSync } = require("child_process");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
|
|
19
|
+
function findRepoScript(target, name) {
|
|
20
|
+
const candidates = [
|
|
21
|
+
path.join(target, "scripts", name),
|
|
22
|
+
path.join(__dirname, "../../../../scripts", name),
|
|
23
|
+
path.join("/root/0dai/scripts", name),
|
|
24
|
+
];
|
|
25
|
+
for (const c of candidates) {
|
|
26
|
+
try { if (fs.existsSync(c)) return c; } catch {}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runLocal(target, args) {
|
|
32
|
+
const script = findRepoScript(target, "play_book.py");
|
|
33
|
+
if (!script) return { ok: false, error: "play_book.py not found locally" };
|
|
34
|
+
const res = spawnSync("python3", [script, "--root", target, ...args], {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
37
|
+
});
|
|
38
|
+
if (res.status !== 0) {
|
|
39
|
+
return { ok: false, error: (res.stderr || res.stdout || "").trim() };
|
|
40
|
+
}
|
|
41
|
+
try { return { ok: true, data: JSON.parse(res.stdout || "[]") }; }
|
|
42
|
+
catch { return { ok: true, data: res.stdout }; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fmtTags(tags) {
|
|
46
|
+
if (!tags || !tags.length) return `${D}—${R}`;
|
|
47
|
+
return tags.slice(0, 6).map(t => `${D}#${t}${R}`).join(" ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function cmdList(target) {
|
|
51
|
+
log("curated (server):");
|
|
52
|
+
const remote = await apiCall("/v1/plays");
|
|
53
|
+
if (remote && Array.isArray(remote.plays) && remote.plays.length) {
|
|
54
|
+
for (const p of remote.plays) {
|
|
55
|
+
console.log(` ${T}${(p.slug || "").padEnd(28)}${R} ${p.title || ""} ${fmtTags(p.stacks)}`);
|
|
56
|
+
}
|
|
57
|
+
if (remote.plan && remote.plan !== "pro" && remote.plan !== "team") {
|
|
58
|
+
console.log(` ${D}plan: ${remote.plan} — some plays locked; upgrade at /pricing${R}`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
log(`no curated plays available (plan: ${remote && remote.plan || "?"})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const local = runLocal(target, ["list"]);
|
|
65
|
+
if (local.ok && Array.isArray(local.data) && local.data.length) {
|
|
66
|
+
console.log("");
|
|
67
|
+
log("local (this repo):");
|
|
68
|
+
for (const p of local.data) {
|
|
69
|
+
const origin = p.parent ? `← ${p.parent}` : "original";
|
|
70
|
+
console.log(` ${T}${(p.slug || "").padEnd(28)}${R} ${p.title || ""} ${D}${origin}${R}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function cmdGet(target, slug) {
|
|
76
|
+
if (!slug) { log("usage: 0dai play get <slug>"); return; }
|
|
77
|
+
// Try remote first (curated), fall back to local
|
|
78
|
+
const remote = await apiCall(`/v1/plays/${encodeURIComponent(slug)}`);
|
|
79
|
+
if (remote && !remote.error) {
|
|
80
|
+
console.log(JSON.stringify(remote, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const local = runLocal(target, ["get", slug]);
|
|
84
|
+
if (local.ok) {
|
|
85
|
+
console.log(typeof local.data === "string" ? local.data : JSON.stringify(local.data, null, 2));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
log(`not found on server or locally: ${slug}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function cmdSearch(target, query) {
|
|
93
|
+
if (!query) { log("usage: 0dai play search <query>"); return; }
|
|
94
|
+
const remote = await apiCall("/v1/plays");
|
|
95
|
+
const q = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
96
|
+
const hits = [];
|
|
97
|
+
if (remote && Array.isArray(remote.plays)) {
|
|
98
|
+
for (const p of remote.plays) {
|
|
99
|
+
const hay = [p.slug, p.title, p.description, ...(p.stacks || [])].join(" ").toLowerCase();
|
|
100
|
+
if (q.every(t => hay.includes(t))) hits.push({ origin: "server", ...p });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const local = runLocal(target, ["search", ...query.split(/\s+/)]);
|
|
104
|
+
if (local.ok && Array.isArray(local.data)) {
|
|
105
|
+
for (const p of local.data) hits.push({ origin: "local", ...p });
|
|
106
|
+
}
|
|
107
|
+
if (!hits.length) { log("no matches"); return; }
|
|
108
|
+
for (const h of hits) {
|
|
109
|
+
console.log(` ${T}[${h.origin}]${R} ${(h.slug || "").padEnd(26)} ${h.title || ""}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function cmdFork(target, slug, args) {
|
|
114
|
+
if (!slug) { log("usage: 0dai play fork <slug> --as <new-slug> --author <handle>"); return; }
|
|
115
|
+
const newSlug = argAfter(args, "--as");
|
|
116
|
+
const author = argAfter(args, "--author");
|
|
117
|
+
if (!newSlug || !author) { log("fork requires --as <new-slug> --author <handle>"); return; }
|
|
118
|
+
const res = runLocal(target, ["fork", slug, "--as", newSlug, "--author", author]);
|
|
119
|
+
if (!res.ok) { log(`fork failed: ${res.error}`); process.exit(1); }
|
|
120
|
+
console.log(typeof res.data === "string" ? res.data : JSON.stringify(res.data, null, 2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function cmdRun(target, slug, args) {
|
|
124
|
+
if (!slug) { log("usage: 0dai play run <slug> [--var K=V …]"); return; }
|
|
125
|
+
const fwdArgs = ["run", slug];
|
|
126
|
+
for (let i = 0; i < args.length; i++) {
|
|
127
|
+
if (args[i] === "--var" && args[i + 1]) { fwdArgs.push("--var", args[i + 1]); i++; }
|
|
128
|
+
}
|
|
129
|
+
const res = runLocal(target, fwdArgs);
|
|
130
|
+
if (!res.ok) { log(`run failed: ${res.error}`); process.exit(1); }
|
|
131
|
+
console.log(typeof res.data === "string" ? res.data : JSON.stringify(res.data, null, 2));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cmdRank(target, args) {
|
|
135
|
+
const fwdArgs = ["rank"];
|
|
136
|
+
const outcomes = argAfter(args, "--outcomes");
|
|
137
|
+
if (outcomes) fwdArgs.push("--outcomes", outcomes);
|
|
138
|
+
const res = runLocal(target, fwdArgs);
|
|
139
|
+
if (!res.ok) { log(`rank failed: ${res.error}`); process.exit(1); }
|
|
140
|
+
console.log(typeof res.data === "string" ? res.data : JSON.stringify(res.data, null, 2));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function argAfter(args, flag) {
|
|
144
|
+
const i = args.indexOf(flag);
|
|
145
|
+
return i >= 0 && i + 1 < args.length ? args[i + 1] : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function cmdPlay(target, sub, args) {
|
|
149
|
+
const remaining = args.slice(2);
|
|
150
|
+
if (!sub || sub === "help") {
|
|
151
|
+
console.log("Usage: 0dai play <command>");
|
|
152
|
+
console.log(" list Show curated + local plays");
|
|
153
|
+
console.log(" get <slug> Show one play");
|
|
154
|
+
console.log(" search <query…> Search curated + local");
|
|
155
|
+
console.log(" fork <slug> --as <new> --author <you> Fork into local");
|
|
156
|
+
console.log(" run <slug> [--var K=V …] Preview rendered steps");
|
|
157
|
+
console.log(" rank [--outcomes FILE] Rank local plays by success rate");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
switch (sub) {
|
|
161
|
+
case "list": return cmdList(target);
|
|
162
|
+
case "get": return cmdGet(target, args[1]);
|
|
163
|
+
case "search": return cmdSearch(target, args.slice(1).join(" "));
|
|
164
|
+
case "fork": return cmdFork(target, args[1], remaining);
|
|
165
|
+
case "run": return cmdRun(target, args[1], remaining);
|
|
166
|
+
case "rank": return cmdRank(target, remaining);
|
|
167
|
+
default:
|
|
168
|
+
log(`unknown subcommand: ${sub}`);
|
|
169
|
+
return cmdPlay(target, "help", []);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { cmdPlay };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const shared = require("../shared");
|
|
4
|
+
const { log, spawnSync, findRepoScript } = shared;
|
|
5
|
+
|
|
6
|
+
function cmdProvider(target, args) {
|
|
7
|
+
// Check for per-repo provider commands first
|
|
8
|
+
const subcommand = args[0] || "";
|
|
9
|
+
|
|
10
|
+
if (subcommand === "list" || subcommand === "switch" || subcommand === "clear") {
|
|
11
|
+
// New per-repo provider profile commands — use provider_registry.py
|
|
12
|
+
const script = findRepoScript(target, "provider_registry_cli.py");
|
|
13
|
+
if (!script) {
|
|
14
|
+
// Fallback: inline Python call to provider_registry module
|
|
15
|
+
const pyCode = `
|
|
16
|
+
import sys
|
|
17
|
+
import json
|
|
18
|
+
import pathlib
|
|
19
|
+
sys.path.insert(0, '${target}/scripts')
|
|
20
|
+
import provider_registry as pr
|
|
21
|
+
|
|
22
|
+
target = pathlib.Path('${target}')
|
|
23
|
+
args = ${JSON.stringify(args)}
|
|
24
|
+
subcmd = args[0] if args else 'list'
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
if subcmd == 'list':
|
|
28
|
+
providers = pr.list_providers_for_repo(target)
|
|
29
|
+
print('Provider Status for', target.name)
|
|
30
|
+
print('=' * 60)
|
|
31
|
+
for p in providers:
|
|
32
|
+
active = '→' if p['active'] else ' '
|
|
33
|
+
source = f"[{p['source']}]" if p['source'] else ''
|
|
34
|
+
creds = '✓' if p['has_credentials'] else '✗'
|
|
35
|
+
print(f" {active} {p['name']:<12} {creds} creds {source:<10} {p['base_url']}")
|
|
36
|
+
print()
|
|
37
|
+
try:
|
|
38
|
+
resolved = pr.resolve_for_repo(target)
|
|
39
|
+
print(f"Active: {resolved['provider_name']} (source={resolved['source']})")
|
|
40
|
+
except pr.ProviderRegistryError as e:
|
|
41
|
+
print(f"No active provider: {e}")
|
|
42
|
+
elif subcmd == 'switch':
|
|
43
|
+
provider_name = ''
|
|
44
|
+
for i, a in enumerate(args):
|
|
45
|
+
if a == '--provider' and i + 1 < len(args):
|
|
46
|
+
provider_name = args[i + 1]
|
|
47
|
+
if not provider_name:
|
|
48
|
+
print('Usage: 0dai provider switch --target . --provider NAME')
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
result = pr.switch_provider(target, provider_name)
|
|
51
|
+
print(f"Switched {target.name} to provider: {result['provider_name']}")
|
|
52
|
+
print(f" base_url: {result['base_url']}")
|
|
53
|
+
print(f" audit log: ~/.0dai/audit/provider-changes.jsonl")
|
|
54
|
+
elif subcmd == 'clear':
|
|
55
|
+
cleared = pr.clear_repo_profile(target)
|
|
56
|
+
if cleared:
|
|
57
|
+
print(f"Cleared per-repo profile for {target.name}")
|
|
58
|
+
print(f"Will fall back to global default or env vars")
|
|
59
|
+
else:
|
|
60
|
+
print(f"No per-repo profile to clear for {target.name}")
|
|
61
|
+
except pr.ProviderRegistryError as e:
|
|
62
|
+
print(f"Error: {e}")
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
`;
|
|
65
|
+
const result = spawnSync("python3", ["-c", pyCode], { stdio: "inherit" });
|
|
66
|
+
if (typeof result.status === "number") process.exit(result.status);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const forwarded = [script, ...args];
|
|
70
|
+
const result = spawnSync("python3", forwarded, { stdio: "inherit" });
|
|
71
|
+
if (typeof result.status === "number") process.exit(result.status);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Legacy provider_profiles.py commands
|
|
76
|
+
const script = findRepoScript(target, "provider_profiles.py");
|
|
77
|
+
if (!script) {
|
|
78
|
+
log("provider profiles unavailable in this environment");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const forwarded = [script, ...args];
|
|
82
|
+
const result = spawnSync("python3", forwarded, { stdio: "inherit" });
|
|
83
|
+
if (typeof result.status === "number") process.exit(result.status);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { cmdProvider };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const shared = require("../shared");
|
|
4
|
+
const { log, T, R, D, E, W, G, fs, path, spawnSync, findRepoScript } = shared;
|
|
5
|
+
|
|
6
|
+
function _color(pct) {
|
|
7
|
+
if (pct == null) return D;
|
|
8
|
+
if (pct >= 95) return E;
|
|
9
|
+
if (pct >= 80) return W;
|
|
10
|
+
return G;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function _bar(pct, width = 20) {
|
|
14
|
+
if (pct == null) return D + "—".repeat(width) + R;
|
|
15
|
+
const filled = Math.min(width, Math.max(0, Math.round((pct / 100) * width)));
|
|
16
|
+
const empty = width - filled;
|
|
17
|
+
return _color(pct) + "█".repeat(filled) + R + D + "░".repeat(empty) + R;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _pad(s, n) { s = String(s); return s + " ".repeat(Math.max(0, n - s.length)); }
|
|
21
|
+
|
|
22
|
+
function _loadEnvelope(target, opts = {}) {
|
|
23
|
+
const cachePath = path.join(target, "ai", "manifest", "agent-quotas.json");
|
|
24
|
+
const force = !!opts.refresh;
|
|
25
|
+
const script = findRepoScript(target, "agent_quotas.py");
|
|
26
|
+
if (script) {
|
|
27
|
+
const args = [script, "--target", target, "--json"];
|
|
28
|
+
if (force) args.push("--refresh");
|
|
29
|
+
const r = spawnSync("python3", args, {
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
timeout: 30000,
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
if (r.status === 0 && r.stdout) {
|
|
35
|
+
try { return JSON.parse(r.stdout); } catch {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (fs.existsSync(cachePath)) {
|
|
39
|
+
try { return JSON.parse(fs.readFileSync(cachePath, "utf8")); } catch {}
|
|
40
|
+
}
|
|
41
|
+
return { fetched_at: 0, fetched_at_iso: null, ttl_seconds: 600, agents: {} };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cmdQuota(target, args = []) {
|
|
45
|
+
const opts = {
|
|
46
|
+
refresh: args.includes("--refresh") || args.includes("-r"),
|
|
47
|
+
json: args.includes("--json"),
|
|
48
|
+
};
|
|
49
|
+
const envelope = _loadEnvelope(target, opts);
|
|
50
|
+
if (opts.json) {
|
|
51
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
52
|
+
return envelope;
|
|
53
|
+
}
|
|
54
|
+
const agents = envelope.agents || {};
|
|
55
|
+
const known = ["codex", "qoder", "claude", "opencode", "gemini", "capi"];
|
|
56
|
+
log(`Agent quotas (cached ${envelope.fetched_at_iso || "never"}, ttl=${envelope.ttl_seconds || 600}s)`);
|
|
57
|
+
console.log("");
|
|
58
|
+
console.log(
|
|
59
|
+
` ${_pad("AGENT", 10)} ${_pad("PLAN", 14)} ${_pad("WEEK%", 7)} ${_pad("HOUR%", 7)} ${_pad("BAR (weekly)", 22)} SOURCE`,
|
|
60
|
+
);
|
|
61
|
+
for (const a of known) {
|
|
62
|
+
const rec = agents[a] || { plan: "unknown", weekly_pct_used: null, hourly_pct_used: null, source: null, error: "no data" };
|
|
63
|
+
const weekly = rec.weekly_pct_used;
|
|
64
|
+
const hourly = rec.hourly_pct_used;
|
|
65
|
+
const bar = _bar(weekly);
|
|
66
|
+
const wstr = weekly == null ? `${D}—${R}` : `${_color(weekly)}${weekly}%${R}`;
|
|
67
|
+
const hstr = hourly == null ? `${D}—${R}` : `${_color(hourly)}${hourly}%${R}`;
|
|
68
|
+
const src = rec.source || rec.error || "—";
|
|
69
|
+
console.log(` ${_pad(a, 10)} ${_pad(rec.plan || "unknown", 14)} ${_pad(wstr, 7)} ${_pad(hstr, 7)} ${bar} ${D}${src}${R}`);
|
|
70
|
+
}
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(` ${D}Yellow >80% · Red >95% (skipped by dispatcher) · '0dai quota --refresh' forces re-poll${R}`);
|
|
73
|
+
return envelope;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { cmdQuota };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `0dai receipt` — shareable PNG receipt for a 0dai session.
|
|
4
|
+
*
|
|
5
|
+
* Reads session telemetry from ai/sessions + ai/experience/events via
|
|
6
|
+
* scripts/receipt_png.py, saves a 1200×630 OG image to ~/.0dai/receipts/,
|
|
7
|
+
* and copies it to the system clipboard (pbcopy / xclip / wl-copy).
|
|
8
|
+
*
|
|
9
|
+
* Issue: #529 (The Bill)
|
|
10
|
+
*/
|
|
11
|
+
const shared = require("../shared");
|
|
12
|
+
const { log, D, R, findRepoScript, spawnSync } = shared;
|
|
13
|
+
|
|
14
|
+
function _findArg(args, flag) {
|
|
15
|
+
const idx = args.indexOf(flag);
|
|
16
|
+
if (idx < 0 || !args[idx + 1]) return null;
|
|
17
|
+
return args[idx + 1];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cmdReceipt(target, args) {
|
|
21
|
+
const script = findRepoScript(target, "receipt_png.py");
|
|
22
|
+
if (!script) {
|
|
23
|
+
log("receipt generator unavailable — missing scripts/receipt_png.py");
|
|
24
|
+
console.log(` ${D}Run from a 0dai-initialized project root.${R}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fwd = [script, "--target", target];
|
|
29
|
+
|
|
30
|
+
const sessionId = _findArg(args, "--session");
|
|
31
|
+
if (sessionId) {
|
|
32
|
+
fwd.push("--session", sessionId);
|
|
33
|
+
} else if (args.includes("--active")) {
|
|
34
|
+
fwd.push("--active");
|
|
35
|
+
} else {
|
|
36
|
+
// Default: --last (matches the acceptance criterion in #529).
|
|
37
|
+
fwd.push("--last");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const output = _findArg(args, "--output");
|
|
41
|
+
if (output) fwd.push("--output", output);
|
|
42
|
+
|
|
43
|
+
if (args.includes("--no-clipboard")) fwd.push("--no-clipboard");
|
|
44
|
+
if (args.includes("--json")) fwd.push("--json");
|
|
45
|
+
if (args.includes("--stats-only")) fwd.push("--stats-only");
|
|
46
|
+
|
|
47
|
+
const result = spawnSync("python3", fwd, { stdio: "inherit" });
|
|
48
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
49
|
+
process.exit(result.status);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { cmdReceipt };
|
package/lib/commands/report.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const shared = require("../shared");
|
|
3
|
-
const { log, D, R, spawnSync, findRepoScript, VERSION } = shared;
|
|
3
|
+
const { log, D, R, T, spawnSync, findRepoScript, VERSION } = shared;
|
|
4
|
+
const { confirmOrExit, shouldAutoYes } = require("../utils/diff-preview");
|
|
4
5
|
|
|
5
|
-
function cmdReport(target, sub, args) {
|
|
6
|
+
async function cmdReport(target, sub, args) {
|
|
6
7
|
const reportScript = findRepoScript(target, "report_manager.py");
|
|
7
8
|
if (!reportScript) {
|
|
8
9
|
log("report manager unavailable in this environment");
|
|
@@ -11,6 +12,32 @@ function cmdReport(target, sub, args) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const command = sub || "preview";
|
|
15
|
+
|
|
16
|
+
if (command === "push") {
|
|
17
|
+
const skipPreview = args.includes("--no-preview");
|
|
18
|
+
const dryRun = args.includes("--dry-run");
|
|
19
|
+
if (!skipPreview) {
|
|
20
|
+
const previewArgs = [reportScript, "preview", "--target", target, "--channel", "npm", "--cli-version", VERSION];
|
|
21
|
+
log(`${T}preview of report payload before push:${R}`);
|
|
22
|
+
const preview = spawnSync("python3", previewArgs, { stdio: "inherit" });
|
|
23
|
+
if (typeof preview.status === "number" && preview.status !== 0) {
|
|
24
|
+
log("preview failed; aborting push");
|
|
25
|
+
process.exit(preview.status);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
log(`${D}dry-run: would push the report shown above. Re-run without --dry-run to send.${R}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!shouldAutoYes(args)) {
|
|
33
|
+
const ok = await confirmOrExit({ args, message: "Push this report to 0dai cloud?", defaultYes: false });
|
|
34
|
+
if (!ok) {
|
|
35
|
+
log("push aborted by user");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
const forwarded = [reportScript, command, "--target", target, "--channel", "npm", "--cli-version", VERSION];
|
|
15
42
|
|
|
16
43
|
if (args.includes("--json")) forwarded.push("--json");
|
package/lib/commands/run.js
CHANGED
|
@@ -4,7 +4,7 @@ const { log, T, R, D, fs, path, apiCall } = shared;
|
|
|
4
4
|
|
|
5
5
|
async function cmdRun(goal, target, args = []) {
|
|
6
6
|
if (!goal) {
|
|
7
|
-
console.log(`Usage: 0dai run <goal> [--dry-run] [--agent claude|codex|gemini]`);
|
|
7
|
+
console.log(`Usage: 0dai run <goal> [--dry-run] [--agent claude|codex|gemini] [--provider deepseek|gemini-direct|codex|claude-opus]`);
|
|
8
8
|
console.log(` Example: 0dai run "add dark mode to settings page"`);
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
@@ -12,6 +12,15 @@ async function cmdRun(goal, target, args = []) {
|
|
|
12
12
|
const dryRun = args.includes("--dry-run");
|
|
13
13
|
const agentIdx = args.indexOf("--agent");
|
|
14
14
|
const agentOverride = agentIdx >= 0 ? args[agentIdx + 1] : null;
|
|
15
|
+
const providerIdx = args.indexOf("--provider");
|
|
16
|
+
const providerOverride = providerIdx >= 0 ? normalizeProvider(args[providerIdx + 1] || "") : null;
|
|
17
|
+
const providerModelIdx = args.indexOf("--provider-model");
|
|
18
|
+
const providerModel = providerModelIdx >= 0 ? (args[providerModelIdx + 1] || "") : "";
|
|
19
|
+
|
|
20
|
+
if (providerIdx >= 0 && !providerOverride) {
|
|
21
|
+
log("error: unsupported provider. Supported: deepseek, gemini-direct, codex, claude-opus, claude-sonnet");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
// Read project context
|
|
17
26
|
let stack = "generic", agents = ["claude"], commands = {};
|
|
@@ -35,9 +44,10 @@ async function cmdRun(goal, target, args = []) {
|
|
|
35
44
|
|
|
36
45
|
for (let i = 0; i < tasks.length; i++) {
|
|
37
46
|
const t = tasks[i];
|
|
38
|
-
const agent = agentOverride || t.assigned_to;
|
|
47
|
+
const agent = agentOverride || providerAgent(providerOverride) || t.assigned_to;
|
|
39
48
|
console.log(` ${T}${i + 1}.${R} ${t.title}`);
|
|
40
|
-
|
|
49
|
+
const providerText = providerOverride ? ` via ${providerOverride}` : "";
|
|
50
|
+
console.log(` ${D}→ ${agent}${providerText} [${t.model_tier}]${t.description ? " " + t.description : ""}${R}`);
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
if (dryRun) {
|
|
@@ -58,12 +68,18 @@ async function cmdRun(goal, target, args = []) {
|
|
|
58
68
|
id,
|
|
59
69
|
title: t.title,
|
|
60
70
|
description: t.description || "",
|
|
61
|
-
assigned_to: agentOverride || t.assigned_to,
|
|
71
|
+
assigned_to: agentOverride || providerAgent(providerOverride) || t.assigned_to,
|
|
62
72
|
model_tier: t.model_tier,
|
|
63
73
|
created_by: "run",
|
|
64
74
|
created_at: new Date().toISOString(),
|
|
65
75
|
context: { goal },
|
|
66
76
|
};
|
|
77
|
+
if (providerOverride) {
|
|
78
|
+
entry.provider_override = providerOverride;
|
|
79
|
+
entry.provider = providerOverride;
|
|
80
|
+
entry.provider_source = "cli_override";
|
|
81
|
+
if (providerModel) entry.provider_model = providerModel;
|
|
82
|
+
}
|
|
67
83
|
try {
|
|
68
84
|
fs.writeFileSync(path.join(queueDir, `${id}.json`), JSON.stringify(entry, null, 2));
|
|
69
85
|
created.push(id);
|
|
@@ -74,4 +90,28 @@ async function cmdRun(goal, target, args = []) {
|
|
|
74
90
|
console.log(` ${D}Monitor: 0dai watch | Queue: 0dai swarm status${R}\n`);
|
|
75
91
|
}
|
|
76
92
|
|
|
93
|
+
function normalizeProvider(provider) {
|
|
94
|
+
const key = String(provider || "").trim().toLowerCase().replace(/_/g, "-");
|
|
95
|
+
const aliases = {
|
|
96
|
+
"deepseek": "deepseek",
|
|
97
|
+
"gemini": "gemini-direct",
|
|
98
|
+
"gemini-direct": "gemini-direct",
|
|
99
|
+
"gemini-1.5": "gemini-direct",
|
|
100
|
+
"codex": "codex",
|
|
101
|
+
"claude-opus": "claude-opus",
|
|
102
|
+
"opus": "claude-opus",
|
|
103
|
+
"claude-sonnet": "claude-sonnet",
|
|
104
|
+
"sonnet": "claude-sonnet",
|
|
105
|
+
};
|
|
106
|
+
return aliases[key] || "";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function providerAgent(provider) {
|
|
110
|
+
if (provider === "deepseek") return "deepseek";
|
|
111
|
+
if (provider === "gemini-direct") return "gemini";
|
|
112
|
+
if (provider === "codex") return "codex";
|
|
113
|
+
if (provider === "claude-opus" || provider === "claude-sonnet") return "claude";
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
77
117
|
module.exports = { cmdRun };
|