@0dai-dev/cli 4.0.0 → 4.2.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 +82 -7
- package/lib/commands/auth.js +67 -28
- package/lib/commands/doctor.js +59 -13
- package/lib/commands/graph.js +103 -4
- package/lib/commands/init.js +142 -9
- package/lib/commands/models.js +49 -10
- package/lib/commands/persona-simulate.js +19 -0
- package/lib/commands/provider.js +18 -0
- package/lib/commands/ssh.js +416 -0
- package/lib/commands/status.js +105 -35
- package/lib/commands/swarm.js +39 -1
- package/lib/commands/tui.js +49 -0
- package/lib/commands/workspace.js +297 -0
- package/lib/onboarding.js +21 -7
- package/lib/shared.js +123 -4
- package/lib/tui/index.mjs +34610 -0
- package/lib/utils/model_ratings.js +77 -0
- package/lib/wizard.js +1 -1
- package/package.json +18 -5
- package/scripts/build-tui.js +77 -0
package/bin/0dai.js
CHANGED
|
@@ -32,6 +32,11 @@ const { cmdFeedback, cmdFeedbackPush } = require("../lib/commands/feedback");
|
|
|
32
32
|
const { cmdGraph } = require("../lib/commands/graph");
|
|
33
33
|
const { cmdReport } = require("../lib/commands/report");
|
|
34
34
|
const { cmdExperience } = require("../lib/commands/experience");
|
|
35
|
+
const { cmdWorkspace } = require("../lib/commands/workspace");
|
|
36
|
+
const { cmdSsh } = require("../lib/commands/ssh");
|
|
37
|
+
const { cmdTui } = require("../lib/commands/tui");
|
|
38
|
+
const { cmdPersonaSimulate } = require("../lib/commands/persona-simulate");
|
|
39
|
+
const { cmdProvider } = require("../lib/commands/provider");
|
|
35
40
|
|
|
36
41
|
async function main() {
|
|
37
42
|
const args = process.argv.slice(2);
|
|
@@ -67,6 +72,29 @@ async function main() {
|
|
|
67
72
|
case "watch": cmdWatch(target, args.slice(1)); break;
|
|
68
73
|
case "audit": cmdAudit(target); break;
|
|
69
74
|
case "security": {
|
|
75
|
+
const subSec = args[1] || "";
|
|
76
|
+
if (subSec === "install-hook") {
|
|
77
|
+
const hooksDir = path.join(target, ".git", "hooks");
|
|
78
|
+
if (!fs.existsSync(hooksDir)) { log("not a git repo"); break; }
|
|
79
|
+
const hookPath = path.join(hooksDir, "pre-commit");
|
|
80
|
+
const hookSource = path.join(__dirname, "..", "..", "..", "scripts", "hooks", "pre-commit.sh");
|
|
81
|
+
if (fs.existsSync(hookSource)) {
|
|
82
|
+
fs.copyFileSync(hookSource, hookPath);
|
|
83
|
+
fs.chmodSync(hookPath, 0o755);
|
|
84
|
+
log("pre-commit hook installed: " + hookPath);
|
|
85
|
+
} else {
|
|
86
|
+
// Inline install: copy from repo scripts if available
|
|
87
|
+
const repoHook = findRepoScript(target, "hooks/pre-commit.sh");
|
|
88
|
+
if (repoHook) {
|
|
89
|
+
fs.copyFileSync(repoHook, hookPath);
|
|
90
|
+
fs.chmodSync(hookPath, 0o755);
|
|
91
|
+
log("pre-commit hook installed: " + hookPath);
|
|
92
|
+
} else {
|
|
93
|
+
log("hook source not found — run: 0dai security install-hook from repo root");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
70
98
|
const secScript = findRepoScript(target, "scan_secrets.py");
|
|
71
99
|
if (!secScript) { log("secret scanner unavailable"); break; }
|
|
72
100
|
const fwd = [secScript, "--target", target];
|
|
@@ -79,13 +107,21 @@ async function main() {
|
|
|
79
107
|
case "init": await cmdInit(target, args); break;
|
|
80
108
|
case "sync": await cmdSync(target, args); break;
|
|
81
109
|
case "detect": await cmdDetect(target); break;
|
|
82
|
-
case "doctor":
|
|
83
|
-
|
|
110
|
+
case "doctor": {
|
|
111
|
+
const driftMode = args.includes("--drift");
|
|
112
|
+
cmdDoctor(target, { drift: driftMode });
|
|
84
113
|
if (args.includes("--drift")) {
|
|
85
114
|
const ds = findRepoScript(target, "drift_detector.py");
|
|
86
|
-
|
|
115
|
+
console.log("\n drift report:");
|
|
116
|
+
if (ds) {
|
|
117
|
+
const result = spawnSync("python3", [ds, "report", "--target", target], { stdio: "inherit" });
|
|
118
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(` ${D}drift detector unavailable in this environment${R}`);
|
|
121
|
+
}
|
|
87
122
|
}
|
|
88
123
|
break;
|
|
124
|
+
}
|
|
89
125
|
case "drift": {
|
|
90
126
|
const ds = findRepoScript(target, "drift_detector.py");
|
|
91
127
|
if (!ds) { log("drift detector unavailable"); break; }
|
|
@@ -103,7 +139,7 @@ async function main() {
|
|
|
103
139
|
case "update": cmdUpdate(args); break;
|
|
104
140
|
case "metrics": cmdMetrics(target); break;
|
|
105
141
|
case "portfolio": cmdPortfolio(); break;
|
|
106
|
-
case "status": cmdStatus(target); break;
|
|
142
|
+
case "status": cmdStatus(target, { json: args.includes("--json") }); break;
|
|
107
143
|
case "auth":
|
|
108
144
|
if (sub === "login") await cmdAuthLogin();
|
|
109
145
|
else if (sub === "logout") cmdAuthLogout();
|
|
@@ -117,11 +153,42 @@ async function main() {
|
|
|
117
153
|
break;
|
|
118
154
|
case "session": cmdSession(target, sub, args); break;
|
|
119
155
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
156
|
+
case "workspace": cmdWorkspace(target, sub, args.slice(2)); break;
|
|
157
|
+
case "ssh": await cmdSsh(target, sub, args); break;
|
|
158
|
+
case "provider": cmdProvider(target, args.slice(1)); break;
|
|
159
|
+
case "tui": case "dashboard": await cmdTui(target, args.slice(1)); break;
|
|
120
160
|
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
121
161
|
case "report": cmdReport(target, sub, args); break;
|
|
122
162
|
case "experience": cmdExperience(target, sub, args); break;
|
|
163
|
+
case "persona-simulate": cmdPersonaSimulate(target, args.slice(1)); break;
|
|
123
164
|
case "graph": await cmdGraph(target, sub, args); break;
|
|
124
165
|
case "models": cmdModels(sub || args[1]); break;
|
|
166
|
+
case "delegate": case "delegation": {
|
|
167
|
+
const deScript = findRepoScript(target, "delegation_engine.py");
|
|
168
|
+
if (!deScript) { log("delegation engine unavailable"); break; }
|
|
169
|
+
const deCmd = cmd === "delegate" ? "delegate" : (sub || "show");
|
|
170
|
+
const fwd = [deScript, deCmd, "--target", target];
|
|
171
|
+
if (deCmd === "delegate") {
|
|
172
|
+
// Find goal: first non-flag arg after delegate, or --goal value
|
|
173
|
+
let goal = "";
|
|
174
|
+
for (let i = 0; i < args.length; i++) {
|
|
175
|
+
if (args[i] === "--goal" && args[i + 1]) { goal = args[i + 1]; i++; }
|
|
176
|
+
else if (!args[i].startsWith("-") && !goal) goal = args[i];
|
|
177
|
+
}
|
|
178
|
+
if (sub && !sub.startsWith("-")) goal = goal || sub;
|
|
179
|
+
if (goal) fwd.push(goal);
|
|
180
|
+
for (let i = 0; i < args.length; i++) {
|
|
181
|
+
if (args[i] === "--agent" && args[i + 1]) { fwd.push("--agent", args[i + 1]); i++; }
|
|
182
|
+
else if (args[i] === "--model" && args[i + 1]) { fwd.push("--model", args[i + 1]); i++; }
|
|
183
|
+
else if (args[i] === "--task-type" && args[i + 1]) { fwd.push("--task-type", args[i + 1]); i++; }
|
|
184
|
+
}
|
|
185
|
+
if (args.includes("--dry-run")) fwd.push("--dry-run");
|
|
186
|
+
}
|
|
187
|
+
if (args.includes("--json")) fwd.push("--json");
|
|
188
|
+
const result = spawnSync("python3", fwd, { stdio: "inherit", timeout: 15000 });
|
|
189
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
125
192
|
case "redeem": await cmdRedeem(sub || args[1]); break;
|
|
126
193
|
case "terminal": case "term":
|
|
127
194
|
try {
|
|
@@ -192,30 +259,38 @@ async function main() {
|
|
|
192
259
|
console.log(" watch Live task monitor: queue, active, recently done [--interval N]");
|
|
193
260
|
console.log(" audit Scan ai/ and agent configs for leaked secrets");
|
|
194
261
|
console.log(" init Initialize ai/ layer (via API) [--dry-run] [--minimal]");
|
|
195
|
-
console.log(" sync Update ai/ layer (via API) [--dry-run] [--quiet]");
|
|
262
|
+
console.log(" sync Update ai/ layer (via API) [--dry-run] [--quiet] [--force]");
|
|
196
263
|
console.log(" detect Show detected stack");
|
|
197
|
-
console.log(" doctor Check health + credentials checklist");
|
|
264
|
+
console.log(" doctor Check health + credentials checklist [--drift]");
|
|
198
265
|
console.log(" update Update all installed agent CLIs to latest [--dry-run]");
|
|
199
266
|
console.log(" validate Validate ai/ layer completeness");
|
|
200
267
|
console.log(" reflect Session reflection: delivered, delegation rate, blockers");
|
|
201
268
|
console.log(" metrics Effectiveness score: adoption funnel, sessions, delegation");
|
|
202
269
|
console.log(" portfolio All tracked projects: score, sessions, agents, last activity");
|
|
203
|
-
console.log(" status Show maturity, swarm, session");
|
|
270
|
+
console.log(" status Show maturity, swarm, session [--json]");
|
|
204
271
|
console.log(" session save Save session for roaming");
|
|
205
272
|
console.log(" swarm status Task queue & delegation");
|
|
206
273
|
console.log(" swarm webhook add Register webhook (fires on task done/failed)");
|
|
207
274
|
console.log(" swarm webhook list Show registered webhooks");
|
|
275
|
+
console.log(" ssh Manage SSH keys, hosts, grants, and host-side sync");
|
|
276
|
+
console.log(" provider Local provider profiles, bindings, and direct invoke");
|
|
208
277
|
console.log(" swarm webhook test Send test ping to a webhook URL");
|
|
278
|
+
console.log(" workspace init Create tmux workspace config (auto-detect services)");
|
|
279
|
+
console.log(" workspace up Start all workspace sessions");
|
|
280
|
+
console.log(" workspace status Show session status table");
|
|
209
281
|
console.log(" feedback push Send feedback to 0dai");
|
|
210
282
|
console.log(" report preview Preview privacy-safe project report");
|
|
211
283
|
console.log(" report push Send report to 0dai (with offline queue)");
|
|
212
284
|
console.log(" report status Show last report, queue, and auto-report status");
|
|
285
|
+
console.log(" persona-simulate Produce a focus-group report and optional issue drafts");
|
|
213
286
|
console.log(" experience list Show recent structured experience events");
|
|
214
287
|
console.log(" experience stats Show success and cost stats by agent/model/type");
|
|
215
288
|
console.log(" graph push Upload local graph to server (Pro: edges, Free: nodes)");
|
|
216
289
|
console.log(" graph pull Download server graph and merge locally");
|
|
217
290
|
console.log(" graph status Show local graph stats and sync state");
|
|
218
291
|
console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
|
|
292
|
+
console.log(" delegate Auto-route task to best agent/model (0dai delegate 'goal')");
|
|
293
|
+
console.log(" delegation show Show current delegation policy");
|
|
219
294
|
console.log(" terminal Launch interactive agent session");
|
|
220
295
|
console.log(" auth login Authenticate (device code flow)");
|
|
221
296
|
console.log(" auth logout Remove credentials");
|
package/lib/commands/auth.js
CHANGED
|
@@ -8,6 +8,40 @@ const {
|
|
|
8
8
|
makeEnsureAuthenticated, ensureLicenseActivation,
|
|
9
9
|
} = shared;
|
|
10
10
|
|
|
11
|
+
async function resolveSessionState() {
|
|
12
|
+
const auth = loadAuthState();
|
|
13
|
+
const hasCachedToken = !!(auth && (auth.api_key || auth.access_token || auth.token));
|
|
14
|
+
if (!hasCachedToken) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
degraded: false,
|
|
18
|
+
auth: null,
|
|
19
|
+
status: null,
|
|
20
|
+
message: "Not logged in. Run: 0dai auth login",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const status = await fetchAuthStatus();
|
|
25
|
+
if (!status || status.error || !status.email) {
|
|
26
|
+
const email = auth.email || auth.user || "unknown";
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
degraded: true,
|
|
30
|
+
auth,
|
|
31
|
+
status,
|
|
32
|
+
message: `Saved session for ${email}, but cloud validation failed. Run: 0dai auth login`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
ok: true,
|
|
38
|
+
degraded: false,
|
|
39
|
+
auth,
|
|
40
|
+
status,
|
|
41
|
+
message: "",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
async function cmdAuthLogin() {
|
|
12
46
|
const isTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
13
47
|
|
|
@@ -131,8 +165,8 @@ async function cmdAuthLogin() {
|
|
|
131
165
|
process.exit(1);
|
|
132
166
|
}
|
|
133
167
|
}
|
|
134
|
-
s.stop("
|
|
135
|
-
p.log.error("
|
|
168
|
+
s.stop("Device code expired");
|
|
169
|
+
p.log.error("The code expired after 10 minutes. Run '0dai auth login' to get a new code.");
|
|
136
170
|
process.exit(1);
|
|
137
171
|
|
|
138
172
|
} else {
|
|
@@ -158,7 +192,7 @@ async function cmdAuthLogin() {
|
|
|
158
192
|
return;
|
|
159
193
|
}
|
|
160
194
|
}
|
|
161
|
-
log("
|
|
195
|
+
log("Device code expired after 10 minutes. Run '0dai auth login' again.");
|
|
162
196
|
process.exit(1);
|
|
163
197
|
}
|
|
164
198
|
}
|
|
@@ -196,26 +230,25 @@ async function cmdRedeem(code) {
|
|
|
196
230
|
}
|
|
197
231
|
|
|
198
232
|
async function cmdAuthStatus() {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
log("Not logged in. Run: 0dai auth login");
|
|
233
|
+
const session = await resolveSessionState();
|
|
234
|
+
if (!session.ok) {
|
|
235
|
+
log(session.message);
|
|
236
|
+
process.exitCode = 1;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const { auth, status } = session;
|
|
241
|
+
const email = status.email || auth.email || auth.user || "unknown";
|
|
242
|
+
log(`${email} (${status.plan || auth.plan || "free"} plan)`);
|
|
243
|
+
if (status.usage_today) {
|
|
244
|
+
console.log(" Usage today:");
|
|
245
|
+
for (const [k, v] of Object.entries(status.usage_today))
|
|
246
|
+
console.log(` ${k}: ${v} / ${status.limits[k]}`);
|
|
247
|
+
}
|
|
248
|
+
const license = status.license || auth.license || { status: "inactive" };
|
|
249
|
+
console.log(` Activation: ${license.status || "inactive"}${license.activation_id ? ` (${license.activation_id})` : ""}`);
|
|
250
|
+
if (status.projects && status.projects.length) {
|
|
251
|
+
console.log(` Projects bound: ${status.projects.length} / ${status.project_limit || "?"}`);
|
|
219
252
|
}
|
|
220
253
|
}
|
|
221
254
|
|
|
@@ -229,13 +262,19 @@ async function cmdActivateFree() {
|
|
|
229
262
|
}
|
|
230
263
|
|
|
231
264
|
async function cmdActivateStatus() {
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
265
|
+
const session = await resolveSessionState();
|
|
266
|
+
if (!session.ok) {
|
|
267
|
+
log(session.message);
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const licenseResult = await apiCall("/v1/licenses/status");
|
|
273
|
+
const license = licenseResult.license || session.status.license || session.auth.license || { status: "inactive" };
|
|
235
274
|
updateAuthState({ license });
|
|
236
275
|
log(`license ${license.status || "inactive"}`);
|
|
237
276
|
if (license.activation_id) console.log(` activation id: ${license.activation_id}`);
|
|
238
|
-
console.log(` plan: ${license.plan || status.plan || "free"}`);
|
|
277
|
+
console.log(` plan: ${license.plan || session.status.plan || session.auth.plan || "free"}`);
|
|
239
278
|
}
|
|
240
279
|
|
|
241
|
-
module.exports = { cmdAuthLogin, cmdAuthLogout, cmdRedeem, cmdAuthStatus, cmdActivateFree, cmdActivateStatus };
|
|
280
|
+
module.exports = { cmdAuthLogin, cmdAuthLogout, cmdRedeem, cmdAuthStatus, cmdActivateFree, cmdActivateStatus, resolveSessionState };
|
package/lib/commands/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const shared = require("../shared");
|
|
3
3
|
const { log, T, R, D, fs, path, spawnSync, findRepoScript, SUPPORTED_CLIS, recordExperienceEvent } = shared;
|
|
4
4
|
|
|
5
|
-
function cmdDoctor(target) {
|
|
5
|
+
function cmdDoctor(target, options = {}) {
|
|
6
6
|
const ai = path.join(target, "ai");
|
|
7
7
|
if (!fs.existsSync(ai)) { log("No 0dai config found. Run: 0dai init"); return; }
|
|
8
8
|
let v = "?", stack = "generic";
|
|
@@ -108,6 +108,36 @@ function cmdDoctor(target) {
|
|
|
108
108
|
console.log(` ${mark.padEnd(22)} ${c.name}${hint}`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
console.log("\n provider profiles:");
|
|
112
|
+
try {
|
|
113
|
+
const providerScript = findRepoScript(target, "provider_profiles.py");
|
|
114
|
+
if (!providerScript) {
|
|
115
|
+
console.log(` ${D}—${R2} unavailable in this environment`);
|
|
116
|
+
} else {
|
|
117
|
+
const pr = spawnSync(
|
|
118
|
+
"python3",
|
|
119
|
+
[providerScript, "status", "--target", target, "--json"],
|
|
120
|
+
{ stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", timeout: 5000 },
|
|
121
|
+
);
|
|
122
|
+
if (pr.stdout) {
|
|
123
|
+
const payload = JSON.parse(pr.stdout);
|
|
124
|
+
const bindings = payload.bindings && typeof payload.bindings === "object" ? payload.bindings : {};
|
|
125
|
+
const resolved = Object.entries(bindings).filter(([, profileId]) => Boolean(profileId));
|
|
126
|
+
if (!resolved.length) {
|
|
127
|
+
console.log(` ${D}—${R2} no provider profiles bound`);
|
|
128
|
+
} else {
|
|
129
|
+
for (const [agent, profileId] of resolved.sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
130
|
+
console.log(` ${G}ok${R2} ${agent} ${D}→ ${profileId}${R2}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
console.log(` ${D}—${R2} no provider profiles bound`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
console.log(` ${D}—${R2} unable to resolve provider profile status`);
|
|
139
|
+
}
|
|
140
|
+
|
|
111
141
|
// --- agent CLIs check ---
|
|
112
142
|
const { execFileSync: _ef2 } = require("child_process");
|
|
113
143
|
let updatesAvailable = 0;
|
|
@@ -172,23 +202,39 @@ function cmdDoctor(target) {
|
|
|
172
202
|
review_needed: warnings > 0,
|
|
173
203
|
},
|
|
174
204
|
});
|
|
175
|
-
if (errors) process.exitCode = 1;
|
|
205
|
+
if (errors && !options.suppressExitCode) process.exitCode = 1;
|
|
176
206
|
|
|
177
207
|
// Drift summary (lightweight — full report via --drift flag)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
if (!options.drift) {
|
|
209
|
+
try {
|
|
210
|
+
const ds = findRepoScript(target, "drift_detector.py");
|
|
211
|
+
if (ds) {
|
|
212
|
+
const dr = spawnSync("python3", [ds, "report", "--target", target],
|
|
213
|
+
{ stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", timeout: 5000 });
|
|
214
|
+
if (dr.stdout && dr.stdout.includes("MODIFIED")) {
|
|
215
|
+
const lines = dr.stdout.trim().split("\n");
|
|
216
|
+
const driftCount = lines.filter(l => l.includes("MODIFIED") || l.includes("CONTRADICTS")).length;
|
|
217
|
+
if (driftCount > 0) {
|
|
218
|
+
console.log(`\n config drift: ${driftCount} issue(s) — run: 0dai doctor --drift`);
|
|
219
|
+
}
|
|
188
220
|
}
|
|
189
221
|
}
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --fix: auto-repair by running sync
|
|
226
|
+
if (process.argv.includes("--fix") && errors) {
|
|
227
|
+
console.log(`\n ${G}auto-fix:${R2} running 0dai sync to regenerate missing files...`);
|
|
228
|
+
try {
|
|
229
|
+
const { cmdSync } = require("./init");
|
|
230
|
+
cmdSync(target, ["--quiet"]);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.log(` ${E}auto-fix failed:${R2} ${e.message}`);
|
|
233
|
+
console.log(` ${D}Run manually: 0dai sync${R2}`);
|
|
190
234
|
}
|
|
191
|
-
}
|
|
235
|
+
} else if (errors) {
|
|
236
|
+
console.log(`\n ${D}Tip: run 0dai doctor --fix to auto-repair${R2}`);
|
|
237
|
+
}
|
|
192
238
|
}
|
|
193
239
|
|
|
194
240
|
module.exports = { cmdDoctor };
|
package/lib/commands/graph.js
CHANGED
|
@@ -162,10 +162,109 @@ async function cmdGraph(target, sub, args) {
|
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
if (sub === "history") {
|
|
166
|
+
const authFile = path.join(require("os").homedir(), ".0dai", "auth.json");
|
|
167
|
+
let auth = {};
|
|
168
|
+
try { auth = JSON.parse(fs.readFileSync(authFile, "utf8")); } catch {}
|
|
169
|
+
const plan = (auth.plan || "free").toLowerCase();
|
|
170
|
+
const limitIdx = args.indexOf("--limit");
|
|
171
|
+
const limit = limitIdx >= 0 && args[limitIdx + 1] ? parseInt(args[limitIdx + 1], 10) || 50 : 50;
|
|
172
|
+
const sinceIdx = args.indexOf("--since");
|
|
173
|
+
const since = sinceIdx >= 0 ? args[sinceIdx + 1] : null;
|
|
174
|
+
const nodeIdx = args.indexOf("--node");
|
|
175
|
+
const nodeId = nodeIdx >= 0 ? args[nodeIdx + 1] : null;
|
|
176
|
+
|
|
177
|
+
if (["pro", "team", "enterprise"].includes(plan) && auth.access_token) {
|
|
178
|
+
const identity = buildProjectIdentity(target, collectMetadata(target));
|
|
179
|
+
const params = new URLSearchParams({
|
|
180
|
+
project_id: identity.project_id,
|
|
181
|
+
limit: String(Math.max(1, limit)),
|
|
182
|
+
});
|
|
183
|
+
if (since) params.set("since", since);
|
|
184
|
+
if (nodeId) params.set("target_id", nodeId);
|
|
185
|
+
const result = await apiCall(`/v1/graph/history?${params.toString()}`);
|
|
186
|
+
if (result.error) {
|
|
187
|
+
log(`Error: ${result.error}`);
|
|
188
|
+
if (result.hint) log(result.hint);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const entries = result.mutations || [];
|
|
192
|
+
if (!entries.length) { console.log(" No graph history yet."); return; }
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
const actor = (entry.agent || "unknown").padEnd(7);
|
|
195
|
+
const context = entry.context ? ` | "${entry.context}"` : "";
|
|
196
|
+
console.log(` ${entry.timestamp || "?"} | ${actor} | ${entry.action.padEnd(13)} | ${entry.target_id}${context}`);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
log(`${D}Graph history requires Pro plan. Upgrade: 0dai upgrade${R}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (sub === "context") {
|
|
205
|
+
if (!fs.existsSync(graphFile)) {
|
|
206
|
+
log("No local graph found. Run: 0dai graph init or create ai/manifest/project_graph.json");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const slicerScript = shared.findRepoScript(target, "graph_slicer_cli.py");
|
|
210
|
+
if (!slicerScript) {
|
|
211
|
+
log("graph slicer unavailable in this environment");
|
|
212
|
+
console.log(` ${D}Expected scripts/graph_slicer_cli.py in repo checkout${R}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const forwarded = [slicerScript, "--target", target];
|
|
216
|
+
let i = 0;
|
|
217
|
+
while (i < args.length) {
|
|
218
|
+
if (args[i] === "--scope" && args[i + 1]) { forwarded.push("--scope", args[i + 1]); i += 2; }
|
|
219
|
+
else if (args[i] === "--task" && args[i + 1]) { forwarded.push("--task", args[i + 1]); i += 2; }
|
|
220
|
+
else if (args[i] === "--depth" && args[i + 1]) { forwarded.push("--depth", args[i + 1]); i += 2; }
|
|
221
|
+
else if (args[i] === "--budget" && args[i + 1]) { forwarded.push("--budget", args[i + 1]); i += 2; }
|
|
222
|
+
else if (args[i] === "--json") { forwarded.push("--json"); i += 1; }
|
|
223
|
+
else { i += 1; }
|
|
224
|
+
}
|
|
225
|
+
const result = shared.spawnSync("python3", forwarded, { stdio: "inherit" });
|
|
226
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (sub === "outcomes") {
|
|
231
|
+
const authFile = path.join(require("os").homedir(), ".0dai", "auth.json");
|
|
232
|
+
let auth = {};
|
|
233
|
+
try { auth = JSON.parse(fs.readFileSync(authFile, "utf8")); } catch {}
|
|
234
|
+
const plan = (auth.plan || "free").toLowerCase();
|
|
235
|
+
if (!["pro", "team", "enterprise"].includes(plan)) {
|
|
236
|
+
log(`${D}Outcomes require Pro plan. Upgrade: 0dai upgrade${R}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!auth.access_token) {
|
|
240
|
+
log("Graph outcomes requires an account. Run: 0dai auth login");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const identity = buildProjectIdentity(target, collectMetadata(target));
|
|
244
|
+
const params = new URLSearchParams({ project_id: identity.project_id });
|
|
245
|
+
const limitIdx = args.indexOf("--limit");
|
|
246
|
+
if (limitIdx >= 0 && args[limitIdx + 1]) params.set("limit", args[limitIdx + 1]);
|
|
247
|
+
const sinceIdx = args.indexOf("--since");
|
|
248
|
+
if (sinceIdx >= 0 && args[sinceIdx + 1]) params.set("since", args[sinceIdx + 1]);
|
|
249
|
+
const result = await apiCall(`/v1/outcomes?${params.toString()}`);
|
|
250
|
+
if (result.error) { log(`Error: ${result.error}`); return; }
|
|
251
|
+
const stats = result.stats || {};
|
|
252
|
+
log(`Outcomes: ${stats.total || 0} total, ${stats.success_rate || 0}% success rate`);
|
|
253
|
+
if (result.outcomes && result.outcomes.length) {
|
|
254
|
+
for (const o of result.outcomes.slice(0, 10)) {
|
|
255
|
+
console.log(` ${o.result || "?"} | ${o.agent || "?"} | ${o.title || o.task_id || "?"}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.log("Usage: 0dai graph [push|pull|status|history|context|outcomes]");
|
|
262
|
+
console.log(" push Upload local graph to server (Pro: edges, Free: nodes only)");
|
|
263
|
+
console.log(" pull Download server graph and merge locally");
|
|
264
|
+
console.log(" status Show local graph stats and sync state");
|
|
265
|
+
console.log(" history Show graph mutation timeline [--since 7d] [--node ID] [--limit 50]");
|
|
266
|
+
console.log(" context Get relevant graph context for a task [--scope FILE] [--task TYPE]");
|
|
267
|
+
console.log(" outcomes Show outcome analytics (Pro only)");
|
|
169
268
|
}
|
|
170
269
|
|
|
171
270
|
module.exports = { cmdGraph };
|