@0dai-dev/cli 4.3.6 → 4.3.7
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 +12 -11
- package/bin/0dai.js +127 -30
- package/lib/ai/manifest/mcp-exposure-contract.json +121 -0
- package/lib/ai/meta/manifest/mcp-tool-tiers.json +435 -0
- package/lib/ai/registry/mcp-catalog.json +98 -0
- package/lib/commands/auth.js +2 -1
- package/lib/commands/compliance.js +1 -1
- package/lib/commands/doctor.js +506 -12
- package/lib/commands/experience.js +40 -5
- package/lib/commands/feedback.js +157 -15
- package/lib/commands/gh.js +26 -0
- package/lib/commands/graph.js +9 -4
- package/lib/commands/heatmap.js +1 -1
- package/lib/commands/init.js +209 -27
- package/lib/commands/mcp.js +111 -33
- package/lib/commands/models.js +138 -41
- package/lib/commands/provider.js +30 -59
- package/lib/commands/quota.js +1 -1
- package/lib/commands/receipt.js +1 -1
- package/lib/commands/run.js +14 -6
- package/lib/commands/runner.js +31 -1
- package/lib/commands/status.js +38 -10
- package/lib/commands/swarm.js +130 -12
- package/lib/commands/update.js +184 -38
- package/lib/commands/usage.js +1 -1
- package/lib/commands/validate.js +32 -3
- package/lib/commands/vault.js +43 -8
- package/lib/python/__init__.py +0 -0
- package/lib/python/agent_quotas.py +525 -0
- package/lib/python/anomaly_alert.py +397 -0
- package/lib/python/anti_pattern_detector.py +799 -0
- package/lib/python/auth.py +443 -0
- package/lib/python/capi_profile_guard.py +477 -0
- package/lib/python/compliance_report.py +581 -0
- package/lib/python/drift_detector.py +388 -0
- package/lib/python/experience_pipeline.py +1130 -0
- package/lib/python/graph.py +19 -0
- package/lib/python/graph_core.py +293 -0
- package/lib/python/graph_io.py +179 -0
- package/lib/python/graph_legacy.py +2052 -0
- package/lib/python/graph_legacy_helpers.py +221 -0
- package/lib/python/graph_outcomes_core.py +85 -0
- package/lib/python/graph_queries.py +171 -0
- package/lib/python/graph_slice.py +198 -0
- package/lib/python/graph_slicer.py +576 -0
- package/lib/python/graph_slicer_cli.py +60 -0
- package/lib/python/graph_validation.py +64 -0
- package/lib/python/heatmap.py +934 -0
- package/lib/python/json_utils.py +193 -0
- package/lib/python/mcp_exposure_check.py +247 -0
- package/lib/python/model_router.py +1434 -0
- package/lib/python/project_manager.py +621 -0
- package/lib/python/provider_profiles.py +1618 -0
- package/lib/python/provider_registry.py +1211 -0
- package/lib/python/provider_registry_cli.py +125 -0
- package/lib/python/receipt_png.py +727 -0
- package/lib/python/structural_memory.py +325 -0
- package/lib/python/swarm_cost.py +177 -0
- package/lib/python/usage_ledger.py +569 -0
- package/lib/scripts/mcp_tier_config.py +240 -0
- package/lib/shared.js +95 -12
- package/lib/tui/index.mjs +35174 -0
- package/lib/utils/activation_telemetry.js +1 -4
- package/lib/utils/constants.js +7 -1
- package/lib/utils/identity.js +184 -0
- package/lib/utils/mcp-auth.js +81 -15
- package/lib/utils/plan.js +1 -1
- package/lib/vault/index.js +19 -3
- package/lib/vault/storage.js +21 -2
- package/lib/wizard.js +5 -2
- package/package.json +9 -3
- package/scripts/build-python-bundle.js +106 -0
- package/scripts/build-tui.js +14 -1
- package/scripts/harvest_experience.py +523 -0
- package/scripts/postinstall.js +15 -9
package/lib/commands/feedback.js
CHANGED
|
@@ -11,7 +11,9 @@ function writeFeedbackOutbox(target, report, result) {
|
|
|
11
11
|
queued_at: new Date().toISOString(),
|
|
12
12
|
project: path.basename(target),
|
|
13
13
|
report,
|
|
14
|
+
error_type: "feedback_endpoint_error",
|
|
14
15
|
error: result && result.error ? result.error : "feedback endpoint did not acknowledge receipt",
|
|
16
|
+
retry_command: "0dai feedback retry",
|
|
15
17
|
response: result || null,
|
|
16
18
|
}, null, 2) + "\n", { mode: 0o600 });
|
|
17
19
|
return outboxPath;
|
|
@@ -33,36 +35,147 @@ function feedbackAccepted(result) {
|
|
|
33
35
|
return Boolean(result && (result.received || result.ok));
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
function hasHelp(args = []) {
|
|
39
|
+
return args.includes("--help") || args.includes("-h");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function printFeedbackHelp() {
|
|
43
|
+
console.log("Usage: 0dai feedback [push|submit|retry|log|list] [--target PATH] [options]");
|
|
44
|
+
console.log("");
|
|
45
|
+
console.log("Commands:");
|
|
46
|
+
console.log(" push Send local feedback reports and operational log entries");
|
|
47
|
+
console.log(" push Send accepted feedback reports from ai/feedback/");
|
|
48
|
+
console.log(" retry Retry queued feedback from a failed push");
|
|
49
|
+
console.log(" log Append one feedback item to ai/feedback/operational.jsonl");
|
|
50
|
+
console.log(" list List local feedback report files");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printFeedbackPushHelp() {
|
|
54
|
+
console.log("Usage: 0dai feedback push --target PATH");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("Sends accepted feedback reports from ai/feedback/ and operational.jsonl entries.");
|
|
57
|
+
console.log("Accepted reports: ai/feedback/*-report.json and ai/feedback/YYYYMMDD*.json.");
|
|
58
|
+
console.log("Accepted log: ai/feedback/operational.jsonl.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printFeedbackSubmitHelp() {
|
|
62
|
+
console.log("Usage: 0dai feedback push --target PATH");
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log("Sends one accepted feedback report file. Use feedback push to send every accepted local report.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function printFeedbackRetryHelp() {
|
|
68
|
+
console.log("Usage: 0dai feedback retry [--json]");
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log("Retries queued feedback submissions from ~/.0dai/feedback-outbox/.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printFeedbackLogHelp() {
|
|
74
|
+
console.log("Usage: 0dai feedback log --type bug|suggestion|friction|positive --detail '...'");
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log("Records one local feedback entry without sending it.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printFeedbackListHelp() {
|
|
80
|
+
console.log("Usage: 0dai feedback list");
|
|
81
|
+
console.log("");
|
|
82
|
+
console.log("Lists local *-report.json feedback files.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveFeedbackSubmitFile(target, fileArg) {
|
|
86
|
+
if (!fileArg) return { error: "missing --file" };
|
|
87
|
+
const fbDir = path.resolve(target, "ai", "feedback");
|
|
88
|
+
const fullPath = path.resolve(target, fileArg);
|
|
89
|
+
if (fullPath !== fbDir && !fullPath.startsWith(`${fbDir}${path.sep}`)) {
|
|
90
|
+
return { error: "file must be under ai/feedback/" };
|
|
91
|
+
}
|
|
92
|
+
if (!fs.existsSync(fullPath)) return { error: `file not found: ${fileArg}` };
|
|
93
|
+
return { fullPath };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function cmdFeedbackPush(target, options = {}) {
|
|
37
97
|
const fbDir = path.join(target, "ai", "feedback");
|
|
98
|
+
const onlyFile = options.onlyFile ? path.resolve(options.onlyFile) : "";
|
|
38
99
|
const items = [];
|
|
100
|
+
const ignored = [];
|
|
101
|
+
let feedbackDirExists = false;
|
|
102
|
+
let jsonlStatus = "missing";
|
|
103
|
+
let includedOperationalLog = false;
|
|
104
|
+
const acceptedFormats = [
|
|
105
|
+
"ai/feedback/*-report.json",
|
|
106
|
+
"ai/feedback/YYYYMMDD*.json",
|
|
107
|
+
"ai/feedback/operational.jsonl",
|
|
108
|
+
];
|
|
39
109
|
|
|
40
110
|
// Collect from report JSON files
|
|
41
111
|
try {
|
|
112
|
+
feedbackDirExists = fs.existsSync(fbDir) && fs.statSync(fbDir).isDirectory();
|
|
42
113
|
for (const f of fs.readdirSync(fbDir)) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
114
|
+
const fullPath = path.join(fbDir, f);
|
|
115
|
+
if (onlyFile && path.resolve(fullPath) !== onlyFile) continue;
|
|
116
|
+
let stat = null;
|
|
117
|
+
try { stat = fs.statSync(fullPath); } catch {}
|
|
118
|
+
if (!stat || !stat.isFile()) {
|
|
119
|
+
ignored.push({ file: f, reason: "not a regular file" });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (f === "operational.jsonl") continue;
|
|
123
|
+
if (!(f.endsWith("-report.json") || (f.endsWith(".json") && f.match(/^\d{8}/)))) {
|
|
124
|
+
ignored.push({ file: f, reason: "unsupported report name" });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const d = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
129
|
+
if (d.project || d.verdict) items.push({ type: "report", data: d, file: f });
|
|
130
|
+
else ignored.push({ file: f, reason: "missing project or verdict" });
|
|
131
|
+
} catch (err) {
|
|
132
|
+
ignored.push({ file: f, reason: `invalid JSON: ${err.message || err}` });
|
|
48
133
|
}
|
|
49
134
|
}
|
|
50
|
-
} catch {
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (!feedbackDirExists) ignored.push({ file: "ai/feedback/", reason: "directory does not exist" });
|
|
137
|
+
else ignored.push({ file: "ai/feedback/", reason: `could not scan directory: ${err.message || err}` });
|
|
138
|
+
}
|
|
51
139
|
|
|
52
140
|
// Collect from operational.jsonl (feedback log entries)
|
|
53
141
|
const jsonlPath = path.join(fbDir, "operational.jsonl");
|
|
54
142
|
try {
|
|
55
|
-
if (fs.existsSync(jsonlPath)) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
143
|
+
if (fs.existsSync(jsonlPath) && (!onlyFile || path.resolve(jsonlPath) === onlyFile)) {
|
|
144
|
+
const raw = fs.readFileSync(jsonlPath, "utf8");
|
|
145
|
+
const lines = raw.trim().split("\n").filter(Boolean);
|
|
146
|
+
jsonlStatus = lines.length ? `${lines.length} line(s)` : "empty";
|
|
147
|
+
if (!lines.length) ignored.push({ file: "operational.jsonl", reason: "empty log" });
|
|
148
|
+
for (const [idx, line] of lines.entries()) {
|
|
149
|
+
try {
|
|
150
|
+
items.push({ type: "log", data: JSON.parse(line) });
|
|
151
|
+
includedOperationalLog = true;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
ignored.push({ file: `operational.jsonl:${idx + 1}`, reason: `invalid JSON: ${err.message || err}` });
|
|
155
|
+
}
|
|
59
156
|
}
|
|
60
157
|
}
|
|
61
|
-
} catch {
|
|
158
|
+
} catch (err) {
|
|
159
|
+
jsonlStatus = `unreadable: ${err.message || err}`;
|
|
160
|
+
ignored.push({ file: "operational.jsonl", reason: jsonlStatus });
|
|
161
|
+
}
|
|
62
162
|
|
|
63
163
|
if (!items.length) {
|
|
64
164
|
log("no feedback found");
|
|
165
|
+
console.log(` ${D}scanned: ${fbDir}${R}`);
|
|
166
|
+
console.log(` ${D}accepted inputs: ${acceptedFormats.join(", ")}${R}`);
|
|
167
|
+
console.log(` ${D}operational log: ${jsonlStatus}${R}`);
|
|
168
|
+
const shownIgnored = ignored.slice(0, 8);
|
|
169
|
+
for (const item of shownIgnored) {
|
|
170
|
+
console.log(` ${D}ignored ${item.file}: ${item.reason}${R}`);
|
|
171
|
+
}
|
|
172
|
+
if (ignored.length > shownIgnored.length) {
|
|
173
|
+
console.log(` ${D}ignored ${ignored.length - shownIgnored.length} more item(s)${R}`);
|
|
174
|
+
}
|
|
65
175
|
console.log(` ${D}Log feedback first: 0dai feedback log --type suggestion --detail '...'${R}`);
|
|
176
|
+
console.log(` ${D}Report files must include project or verdict before push.${R}`);
|
|
177
|
+
console.log(` ${D}Use 0dai feedback push --target . to send accepted reports from ai/feedback/.${R}`);
|
|
178
|
+
if (onlyFile) process.exitCode = 1;
|
|
66
179
|
return;
|
|
67
180
|
}
|
|
68
181
|
|
|
@@ -81,14 +194,17 @@ async function cmdFeedbackPush(target) {
|
|
|
81
194
|
if (result.warning) console.log(` ${D}${result.warning}${R}`);
|
|
82
195
|
if (result.issue_error) console.log(` ${D}issue warning: ${result.issue_error}${R}`);
|
|
83
196
|
// Archive pushed entries
|
|
84
|
-
if (fs.existsSync(jsonlPath)) {
|
|
197
|
+
if (includedOperationalLog && fs.existsSync(jsonlPath)) {
|
|
85
198
|
const archivePath = path.join(fbDir, `pushed-${Date.now()}.jsonl`);
|
|
86
199
|
fs.renameSync(jsonlPath, archivePath);
|
|
87
200
|
}
|
|
88
201
|
} else {
|
|
89
202
|
const outboxPath = writeFeedbackOutbox(target, report, result);
|
|
90
203
|
log(`error: ${(result && result.error) || "unknown"}`);
|
|
204
|
+
console.log(` ${D}type: feedback_endpoint_error${R}`);
|
|
205
|
+
console.log(` ${D}endpoint: /v1/feedback${R}`);
|
|
91
206
|
console.log(` ${D}queued for retry: ${outboxPath}${R}`);
|
|
207
|
+
console.log(` ${D}retry: 0dai feedback retry${R}`);
|
|
92
208
|
console.log(` ${D}source feedback was left in place${R}`);
|
|
93
209
|
process.exitCode = 1;
|
|
94
210
|
}
|
|
@@ -144,9 +260,35 @@ async function cmdFeedbackRetry(args = []) {
|
|
|
144
260
|
async function cmdFeedback(target, sub, args) {
|
|
145
261
|
const fbDir = path.join(target, "ai", "feedback");
|
|
146
262
|
|
|
263
|
+
if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
|
|
264
|
+
printFeedbackHelp();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (hasHelp(args)) {
|
|
269
|
+
if (sub === "push") printFeedbackPushHelp();
|
|
270
|
+
else if (sub === "submit") printFeedbackSubmitHelp();
|
|
271
|
+
else if (sub === "retry") printFeedbackRetryHelp();
|
|
272
|
+
else if (sub === "log") printFeedbackLogHelp();
|
|
273
|
+
else if (sub === "list") printFeedbackListHelp();
|
|
274
|
+
else printFeedbackHelp();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
147
278
|
if (sub === "push") {
|
|
148
279
|
return cmdFeedbackPush(target);
|
|
149
280
|
}
|
|
281
|
+
if (sub === "submit") {
|
|
282
|
+
const fileArg = args.find((_, i) => args[i - 1] === "--file") || "";
|
|
283
|
+
const resolved = resolveFeedbackSubmitFile(target, fileArg);
|
|
284
|
+
if (resolved.error) {
|
|
285
|
+
log(`feedback submit error: ${resolved.error}`);
|
|
286
|
+
printFeedbackSubmitHelp();
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
return cmdFeedbackPush(target, { onlyFile: resolved.fullPath });
|
|
291
|
+
}
|
|
150
292
|
if (sub === "retry") {
|
|
151
293
|
return cmdFeedbackRetry(args);
|
|
152
294
|
}
|
|
@@ -173,7 +315,7 @@ async function cmdFeedback(target, sub, args) {
|
|
|
173
315
|
} catch { log("no feedback directory"); }
|
|
174
316
|
return;
|
|
175
317
|
}
|
|
176
|
-
|
|
318
|
+
printFeedbackHelp();
|
|
177
319
|
}
|
|
178
320
|
|
|
179
|
-
module.exports = { cmdFeedbackPush, cmdFeedbackRetry, cmdFeedback, writeFeedbackOutbox, listFeedbackOutbox, feedbackAccepted };
|
|
321
|
+
module.exports = { cmdFeedbackPush, cmdFeedbackRetry, cmdFeedback, writeFeedbackOutbox, listFeedbackOutbox, feedbackAccepted, resolveFeedbackSubmitFile };
|
package/lib/commands/gh.js
CHANGED
|
@@ -330,12 +330,38 @@ function hasGithubRemote(target) {
|
|
|
330
330
|
return /github\.com[:/]/.test(String(result.stdout || ""));
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
// An existing hook setup that pointing core.hooksPath at .githooks would
|
|
334
|
+
// silently bypass (Husky, or non-sample committed .git/hooks).
|
|
335
|
+
function existingHookSetup(target) {
|
|
336
|
+
try {
|
|
337
|
+
if (fs.existsSync(path.join(target, ".husky"))) return ".husky";
|
|
338
|
+
const hooksDir = path.join(target, ".git", "hooks");
|
|
339
|
+
if (fs.existsSync(hooksDir)) {
|
|
340
|
+
const real = fs.readdirSync(hooksDir).filter((f) => !f.endsWith(".sample"));
|
|
341
|
+
if (real.length) return ".git/hooks";
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
/* ignore */
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
333
349
|
function ensureHooksPath(target) {
|
|
334
350
|
if (!isGitRepo(target)) return false;
|
|
351
|
+
const current = getHooksPath(target);
|
|
352
|
+
if (current === ".githooks") return true; // already ours — nothing to do
|
|
353
|
+
// Warn (don't silently clobber) when the user already has hooks we'd bypass.
|
|
354
|
+
const conflict = current && current !== ".githooks" ? `core.hooksPath=${current}` : existingHookSetup(target);
|
|
335
355
|
const result = spawnSync("git", ["-C", target, "config", "core.hooksPath", ".githooks"], {
|
|
336
356
|
encoding: "utf8",
|
|
337
357
|
stdio: ["ignore", "pipe", "pipe"],
|
|
338
358
|
});
|
|
359
|
+
if (conflict && result.status === 0) {
|
|
360
|
+
log(`warning: redirected git hooks to .githooks — your existing hooks (${conflict}) are now bypassed`);
|
|
361
|
+
console.log(
|
|
362
|
+
` ${D}Keep yours: git config core.hooksPath ${current || "<your-path>"} (or chain them from .githooks). Skip 0dai's policy: ODAI_GIT_POLICY_SKIP=1${R}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
339
365
|
return result.status === 0;
|
|
340
366
|
}
|
|
341
367
|
|
package/lib/commands/graph.js
CHANGED
|
@@ -23,8 +23,12 @@ function graphPayloadForPush(localGraph, auth) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function missingGraphHint() {
|
|
27
|
+
return "No local graph found. Create ai/manifest/project_graph.json, or from a repo checkout run: python3 scripts/generate_project_graph.py --target .";
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
function readStructuralMemorySummary(target) {
|
|
27
|
-
const script = shared.
|
|
31
|
+
const script = shared.resolvePythonScript(target, "structural_memory.py");
|
|
28
32
|
if (!script) return null;
|
|
29
33
|
const result = shared.spawnSync("python3", [script, "--target", target, "--json"], {
|
|
30
34
|
encoding: "utf8",
|
|
@@ -72,7 +76,7 @@ async function cmdGraph(target, sub, args) {
|
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
if (!fs.existsSync(graphFile)) {
|
|
75
|
-
log(
|
|
79
|
+
log(missingGraphHint());
|
|
76
80
|
return;
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -264,10 +268,10 @@ async function cmdGraph(target, sub, args) {
|
|
|
264
268
|
|
|
265
269
|
if (sub === "context") {
|
|
266
270
|
if (!fs.existsSync(graphFile)) {
|
|
267
|
-
log(
|
|
271
|
+
log(missingGraphHint());
|
|
268
272
|
return;
|
|
269
273
|
}
|
|
270
|
-
const slicerScript = shared.
|
|
274
|
+
const slicerScript = shared.resolvePythonScript(target, "graph_slicer_cli.py");
|
|
271
275
|
if (!slicerScript) {
|
|
272
276
|
log("graph slicer unavailable in this environment");
|
|
273
277
|
console.log(` ${D}Expected scripts/graph_slicer_cli.py in repo checkout${R}`);
|
|
@@ -333,6 +337,7 @@ module.exports = {
|
|
|
333
337
|
graphAuthToken,
|
|
334
338
|
graphPayloadForPush,
|
|
335
339
|
isPaidGraphPlan,
|
|
340
|
+
missingGraphHint,
|
|
336
341
|
readStructuralMemorySummary,
|
|
337
342
|
formatStructuralMemorySummary,
|
|
338
343
|
};
|
package/lib/commands/heatmap.js
CHANGED
|
@@ -3,7 +3,7 @@ const shared = require("../shared");
|
|
|
3
3
|
const { log, spawnSync, findRepoScript } = shared;
|
|
4
4
|
|
|
5
5
|
function cmdHeatmap(target, args) {
|
|
6
|
-
const script =
|
|
6
|
+
const script = shared.resolvePythonScript(target, "heatmap.py");
|
|
7
7
|
if (!script) {
|
|
8
8
|
log("heatmap script unavailable in this environment");
|
|
9
9
|
return;
|