@0dai-dev/cli 4.3.5 → 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.
Files changed (79) hide show
  1. package/README.md +12 -11
  2. package/bin/0dai.js +214 -40
  3. package/lib/ai/manifest/mcp-exposure-contract.json +121 -0
  4. package/lib/ai/meta/manifest/mcp-tool-tiers.json +435 -0
  5. package/lib/ai/registry/mcp-catalog.json +98 -0
  6. package/lib/commands/auth.js +55 -1
  7. package/lib/commands/compliance.js +1 -1
  8. package/lib/commands/detect.js +10 -4
  9. package/lib/commands/doctor.js +545 -26
  10. package/lib/commands/experience.js +40 -5
  11. package/lib/commands/export.js +73 -0
  12. package/lib/commands/feedback.js +157 -15
  13. package/lib/commands/gh.js +26 -0
  14. package/lib/commands/graph.js +9 -4
  15. package/lib/commands/heatmap.js +1 -1
  16. package/lib/commands/init.js +222 -30
  17. package/lib/commands/mcp.js +129 -21
  18. package/lib/commands/models.js +138 -41
  19. package/lib/commands/provider.js +30 -59
  20. package/lib/commands/quota.js +1 -1
  21. package/lib/commands/receipt.js +1 -1
  22. package/lib/commands/run.js +18 -7
  23. package/lib/commands/runner.js +31 -1
  24. package/lib/commands/status.js +44 -11
  25. package/lib/commands/swarm.js +130 -12
  26. package/lib/commands/trust.js +286 -0
  27. package/lib/commands/update.js +184 -38
  28. package/lib/commands/usage.js +1 -1
  29. package/lib/commands/validate.js +32 -3
  30. package/lib/commands/vault.js +46 -9
  31. package/lib/python/__init__.py +0 -0
  32. package/lib/python/agent_quotas.py +525 -0
  33. package/lib/python/anomaly_alert.py +397 -0
  34. package/lib/python/anti_pattern_detector.py +799 -0
  35. package/lib/python/auth.py +443 -0
  36. package/lib/python/capi_profile_guard.py +477 -0
  37. package/lib/python/compliance_report.py +581 -0
  38. package/lib/python/drift_detector.py +388 -0
  39. package/lib/python/experience_pipeline.py +1130 -0
  40. package/lib/python/graph.py +19 -0
  41. package/lib/python/graph_core.py +293 -0
  42. package/lib/python/graph_io.py +179 -0
  43. package/lib/python/graph_legacy.py +2052 -0
  44. package/lib/python/graph_legacy_helpers.py +221 -0
  45. package/lib/python/graph_outcomes_core.py +85 -0
  46. package/lib/python/graph_queries.py +171 -0
  47. package/lib/python/graph_slice.py +198 -0
  48. package/lib/python/graph_slicer.py +576 -0
  49. package/lib/python/graph_slicer_cli.py +60 -0
  50. package/lib/python/graph_validation.py +64 -0
  51. package/lib/python/heatmap.py +934 -0
  52. package/lib/python/json_utils.py +193 -0
  53. package/lib/python/mcp_exposure_check.py +247 -0
  54. package/lib/python/model_router.py +1434 -0
  55. package/lib/python/project_manager.py +621 -0
  56. package/lib/python/provider_profiles.py +1618 -0
  57. package/lib/python/provider_registry.py +1211 -0
  58. package/lib/python/provider_registry_cli.py +125 -0
  59. package/lib/python/receipt_png.py +727 -0
  60. package/lib/python/structural_memory.py +325 -0
  61. package/lib/python/swarm_cost.py +177 -0
  62. package/lib/python/usage_ledger.py +569 -0
  63. package/lib/scripts/mcp_tier_config.py +240 -0
  64. package/lib/shared.js +97 -14
  65. package/lib/tui/index.mjs +35174 -0
  66. package/lib/utils/activation_telemetry.js +230 -11
  67. package/lib/utils/constants.js +7 -1
  68. package/lib/utils/export-bundler.js +285 -0
  69. package/lib/utils/identity.js +198 -1
  70. package/lib/utils/mcp-auth.js +81 -15
  71. package/lib/utils/plan.js +1 -1
  72. package/lib/vault/index.js +19 -3
  73. package/lib/vault/storage.js +21 -2
  74. package/lib/wizard.js +5 -2
  75. package/package.json +9 -3
  76. package/scripts/build-python-bundle.js +106 -0
  77. package/scripts/build-tui.js +14 -1
  78. package/scripts/harvest_experience.py +523 -0
  79. package/scripts/postinstall.js +15 -9
package/README.md CHANGED
@@ -43,39 +43,36 @@ Daily (regular work):
43
43
  0dai run <goal> # split a backlog item into agent tasks [--dry-run] [--dry-cost] [--agent claude|codex|gemini]
44
44
  0dai swarm status # show queued, active, and done tasks
45
45
  0dai swarm add # queue one task [--task '...' --to agent]
46
- 0dai swarm-run # add, dispatch, and wait for one swarm task as JSON
46
+ 0dai swarm pick # pick one queued task [--agent codex]
47
+ 0dai swarm-run # repo-checkout helper: add, dispatch, and wait for one swarm task as JSON
47
48
  0dai harvest # convert experience events into candidate lessons
48
49
  0dai watch # live task monitor [--interval N]
49
50
  0dai reflect # session reflection: delivered, delegation, blockers
50
- 0dai standup # morning voice briefing about overnight agent work
51
- 0dai feedback push # send feedback to 0dai
51
+ 0dai standup # repo-checkout helper: morning voice briefing about overnight agent work
52
+ 0dai feedback push --target . # send feedback to 0dai
52
53
  0dai feedback retry # retry queued feedback after a failed push
53
- 0dai persona-simulate "topic" # focus-group report and optional issue drafts
54
54
  ```
55
55
 
56
56
  Pro / advanced:
57
57
 
58
58
  ```bash
59
59
  0dai init-existing # existing-repo setup alias for init
60
- 0dai project bind # bind repository to your 0dai account [--json]
60
+ 0dai project bind # bind repository to your 0dai account [--name NAME] [--json]
61
61
  0dai project status # local project binding and health [--json]
62
62
  0dai graph push # upload local graph (Pro: edges, Free: nodes)
63
63
  0dai graph pull # download server graph and merge locally
64
64
  0dai graph status # local graph stats and sync state
65
- 0dai ci # portable CI pipelines and AI-MQ [list|plan|mq-status]
66
65
  0dai heatmap # repo treemap: LOC × agent-edit intensity
67
66
  0dai session save # save session for roaming
68
- 0dai provider # local provider profiles and direct invoke
67
+ 0dai provider # provider profiles and local BYOK registry [status|list|switch|clear]
69
68
  0dai models # model ratings (--fast/--balanced/--deep/--available)
70
69
  0dai quota # agent subscription usage [--refresh] [--json]
71
70
  0dai usage # local token, task, and USD ledger [status|daily|monthly]
72
71
  0dai workspace # tmux workspace sessions (init|up|status)
73
- 0dai runner # runner/host architecture and queues [--json]
74
- 0dai report # privacy-safe project reports (preview|push|status)
72
+ 0dai report # repo-checkout helper: privacy-safe project reports (preview|push|status)
75
73
  0dai compliance # SOC2/ISO evidence and ADR audit-trail export
76
- 0dai experience # structured experience events (list|stats|sync|warnings|dismiss)
74
+ 0dai experience # structured experience events (list|stats|record-json|sync|warnings|dismiss)
77
75
  0dai receipt # session receipt PNG [--last|--active|--session ID]
78
- 0dai boneyard # weekly digest of worst agent moves [--week YYYY-WW|current]
79
76
  0dai gh branch-protection # GitHub branch protection [print|apply|install]
80
77
  0dai import claude-code-agents # import .claude/agents/*.md as personas [--dry-run]
81
78
  0dai auth logout # remove credentials
@@ -100,6 +97,10 @@ Global flags: `--target PATH`, `--version`, `--help`, `--json`, `--quiet`. See `
100
97
 
101
98
  Your source code is never sent. Only file names and package/build manifests.
102
99
 
100
+ ## Privacy
101
+
102
+ Local activation and experience records stay on your machine: `ai/meta/telemetry/activation.jsonl`, `ai/experience/`, and the generated `ai/` layer/project agent configs are local files. Server-side activation sends one `free_tier_activated` event when you run `0dai activate free` unless telemetry is opted out. See https://0dai.dev/legal/privacy.
103
+
103
104
  ## Why 0dai, not just Cursor or Copilot?
104
105
 
105
106
  Cursor and Copilot are editors. They help inside one coding session. 0dai writes a project layer that Claude Code, Codex, OpenCode, Gemini, and Aider can all read. The point is not another autocomplete box; it is one manifest, one set of agent roles, one task queue, and one health check for the repo.
package/bin/0dai.js CHANGED
@@ -26,23 +26,63 @@ if ((earlyParsed.args[0] || "help") === "swarm-run") {
26
26
  }
27
27
 
28
28
  const shared = require("../lib/shared");
29
- const { T, R, D, log, VERSION, fs, path, spawnSync, findRepoScript, checkVersion } = shared;
29
+ const { T, R, D, log, VERSION, fs, path, spawnSync, findRepoScript, repoScriptCandidates, checkVersion } = shared;
30
30
 
31
31
  /**
32
32
  * Hot-path Go binary fallback (issue #2424).
33
33
  *
34
- * For read-only hot-path commands (status/doctor/version) we attempt to delegate
35
- * to a Go binary when:
34
+ * For read-only hot-path commands we attempt to delegate to a Go binary when:
36
35
  * 1. ODAI_GO_BIN is set and points at an executable file, OR a binary called
37
36
  * `0dai-go` is found on PATH.
38
- * 2. The binary reports `binary_version` matching the dispatcher VERSION
39
- * (we accept exact match only drift means fall back to Python/Node).
37
+ * 2. The binary reports `dispatcher_compat_version` matching the dispatcher
38
+ * VERSION (legacy binaries may still use `binary_version` for this).
40
39
  * 3. ODAI_GO_DISABLE is NOT set to a truthy value.
40
+ * 4. The command's batch flag is not disabled. SPEC-035 rollback Level 1
41
+ * is `ODAI_GO_BATCH_<N>=0`, which transparently routes the whole batch
42
+ * back to the Node/Python implementation without reinstalling.
43
+ *
44
+ * Only commands listed in GO_HOT_PATH_COMMANDS are eligible for automatic
45
+ * delegation. `status` moved into batch 2 only after the #4098 Go↔Node
46
+ * payload parity proof landed. Base `doctor` moved into the same batch only
47
+ * after #4111 made the local `doctor_checks` shadow contract explicit;
48
+ * `doctor --drift` stays on the full Node implementation.
41
49
  *
42
50
  * If any of these checks fail we silently fall through to the existing
43
51
  * Python/Node implementations. The goal is zero behaviour change when the Go
44
52
  * binary is missing, broken, or version-skewed.
45
53
  */
54
+ const GO_HOT_PATH_COMMANDS = new Set(["version", "status", "doctor"]);
55
+ const GO_HOT_PATH_BATCHES = Object.freeze({
56
+ version: 1,
57
+ status: 2,
58
+ doctor: 2,
59
+ });
60
+
61
+ function goFlagDisabled(raw) {
62
+ if (raw === undefined || raw === null || raw === "") return false;
63
+ const value = String(raw).trim().toLowerCase();
64
+ return ["0", "false", "no", "off"].includes(value);
65
+ }
66
+
67
+ function goCommandEnvName(cmdName) {
68
+ return `ODAI_GO_COMMAND_${String(cmdName).toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
69
+ }
70
+
71
+ function goBatchEnvName(batch) {
72
+ return `ODAI_GO_BATCH_${batch}`;
73
+ }
74
+
75
+ function goBatchEnabled(cmdName) {
76
+ const batch = GO_HOT_PATH_BATCHES[cmdName];
77
+ if (!batch) return false;
78
+ if (goFlagDisabled(process.env[goBatchEnvName(batch)])) return false;
79
+
80
+ // Drill-only per-command rollback override. Batch flags remain the release
81
+ // contract; this override is intentionally narrower for staging drills.
82
+ if (goFlagDisabled(process.env[goCommandEnvName(cmdName)])) return false;
83
+ return true;
84
+ }
85
+
46
86
  function locateGoBinary() {
47
87
  if (process.env.ODAI_GO_DISABLE && process.env.ODAI_GO_DISABLE !== "0") return null;
48
88
  const explicit = process.env.ODAI_GO_BIN;
@@ -68,7 +108,11 @@ function goBinaryCompatible(binPath) {
68
108
  if (res.status !== 0 || !res.stdout) return false;
69
109
  const info = JSON.parse(res.stdout.toString());
70
110
  if (!info || typeof info.binary_version !== "string") return false;
71
- return info.binary_version === VERSION;
111
+ const compatVersion =
112
+ typeof info.dispatcher_compat_version === "string"
113
+ ? info.dispatcher_compat_version
114
+ : info.binary_version;
115
+ return compatVersion === VERSION;
72
116
  } catch {
73
117
  return false;
74
118
  }
@@ -80,18 +124,32 @@ function goBinaryCompatible(binPath) {
80
124
  * caller must fall back to the existing Python/Node path.
81
125
  */
82
126
  function tryGoHotPath(cmdName, target, argv) {
127
+ if (!GO_HOT_PATH_COMMANDS.has(cmdName)) return false;
128
+ if (!goBatchEnabled(cmdName)) return false;
83
129
  const bin = locateGoBinary();
84
130
  if (!bin) return false;
85
131
  if (!goBinaryCompatible(bin)) return false;
86
132
  const forwarded = [cmdName, "--target", target, ...argv];
87
- const res = spawnSync(bin, forwarded, { stdio: "inherit" });
133
+ const res = spawnSync(bin, forwarded, {
134
+ stdio: "inherit",
135
+ env: { ...process.env, ODAI_NODE_PTY_PROBE_DIR: path.join(__dirname, "..") },
136
+ });
88
137
  if (res.error) return false;
89
138
  if (typeof res.status === "number") process.exit(res.status);
90
139
  process.exit(0);
91
140
  }
92
141
 
93
142
  // Export for tests; harmless at runtime.
94
- module.exports = { locateGoBinary, goBinaryCompatible, tryGoHotPath };
143
+ module.exports = {
144
+ locateGoBinary,
145
+ goBinaryCompatible,
146
+ goBatchEnabled,
147
+ goBatchEnvName,
148
+ goCommandEnvName,
149
+ tryGoHotPath,
150
+ GO_HOT_PATH_BATCHES,
151
+ GO_HOT_PATH_COMMANDS,
152
+ };
95
153
  const { loadCanonicalCounts, mcpToolsLabel } = require("../lib/utils/canonical-counts");
96
154
 
97
155
  // --- Command imports ---
@@ -99,7 +157,10 @@ const { cmdAuthLogin, cmdAuthLogout, cmdRedeem, cmdAuthStatus, cmdAuthMcp, cmdAc
99
157
  const { cmdInit, cmdSync, cmdProjectBind } = require("../lib/commands/init");
100
158
  const { cmdDetect } = require("../lib/commands/detect");
101
159
  const { cmdAudit } = require("../lib/commands/audit");
102
- const { cmdDoctor } = require("../lib/commands/doctor");
160
+ const { cmdExport } = require("../lib/commands/export");
161
+ const { cmdMcp } = require("../lib/commands/mcp");
162
+ const { cmdVault } = require("../lib/commands/vault");
163
+ const { cmdDoctor, collectLayerVersionFreshness } = require("../lib/commands/doctor");
103
164
  const { cmdValidate } = require("../lib/commands/validate");
104
165
  const { cmdUpdate } = require("../lib/commands/update");
105
166
  const { cmdUpgrade } = require("../lib/commands/upgrade");
@@ -110,7 +171,7 @@ const { cmdStatus } = require("../lib/commands/status");
110
171
  const { cmdPortfolio } = require("../lib/commands/portfolio");
111
172
  const { cmdRun } = require("../lib/commands/run");
112
173
  const { cmdWatch } = require("../lib/commands/watch");
113
- const { cmdModels } = require("../lib/commands/models");
174
+ const { cmdModels, cmdModelsRecommend } = require("../lib/commands/models");
114
175
  const { cmdSession } = require("../lib/commands/session");
115
176
  const { cmdSwarm, cmdSwarmRun } = require("../lib/commands/swarm");
116
177
  const { cmdStandup } = require("../lib/commands/standup");
@@ -135,10 +196,18 @@ const { cmdLoop } = require("../lib/commands/loop");
135
196
  const { cmdImportClaudeCodeAgents } = require("../lib/commands/import_claude_code_agents");
136
197
  const { cmdRunner } = require("../lib/commands/runner");
137
198
  const { cmdCi } = require("../lib/commands/ci");
199
+ const { cmdTrust } = require("../lib/commands/trust");
138
200
 
139
201
  function printHelp() {
140
202
  const counts = loadCanonicalCounts();
141
203
  console.log(`\n ${T}0dai${R} v${VERSION} — One config for ${counts.agent_clis_total} AI agent CLIs · ${mcpToolsLabel(counts)}\n`);
204
+ console.log("First-run sequence (canonical):");
205
+ console.log(" npm install -g @0dai-dev/cli # install once, globally");
206
+ console.log(" 0dai auth login # sign in (OAuth / device code)");
207
+ console.log(" 0dai activate free # claim free-tier license");
208
+ console.log(" 0dai init # generate ai/ layer in cwd");
209
+ console.log(" 0dai doctor # verify health and drift");
210
+ console.log("");
142
211
  console.log("Start (first 5 minutes):");
143
212
  console.log(" init Create ai/ layer + MCP [--local] [--dry-run] [--minimal]");
144
213
  console.log(" doctor Check health, credentials, and drift [--drift]");
@@ -155,36 +224,38 @@ function printHelp() {
155
224
  console.log(" run <goal> Split a backlog item into agent tasks [--dry-run] [--dry-cost] [--max-cost N] [--agent claude|codex|gemini] [--provider X]");
156
225
  console.log(" swarm status Show queued, active, and done tasks");
157
226
  console.log(" swarm add Queue one task for an agent [--task '...' --to agent]");
158
- console.log(" swarm-run Add, dispatch, and wait for one swarm task as JSON");
227
+ console.log(" swarm pick Pick one queued task for this agent [--agent codex]");
228
+ console.log(" swarm-run Repo-checkout helper: add, dispatch, and wait for one swarm task as JSON");
159
229
  console.log(" harvest Convert experience events into candidate lessons");
160
230
  console.log(" watch Live task monitor: queue, active, recently done [--interval N]");
161
231
  console.log(" reflect Session reflection: delivered, delegation rate, blockers");
162
- console.log(" standup Morning voice briefing about overnight agent work");
232
+ console.log(" standup Repo-checkout helper: morning voice briefing about overnight agent work");
163
233
  console.log(" feedback push Send feedback to 0dai");
234
+ console.log(" feedback submit Send one feedback report file [--file ai/feedback/codex-report.json]");
164
235
  console.log(" feedback retry Retry queued feedback after a failed push");
165
236
  console.log("");
166
237
  console.log("Pro / advanced:");
167
- console.log(" init-existing Existing-repo setup alias for init [--minimal] [--dry-run]");
168
- console.log(" project bind Bind current repository to your 0dai account [--json]");
238
+ console.log(" init-existing Legacy alias for init (older docs / scripted bootstraps); use 'init' [--minimal] [--dry-run]");
239
+ console.log(" project bind Bind current repository to your 0dai account [--name NAME] [--json]");
169
240
  console.log(" project status Show local project binding and health state [--json]");
170
241
  console.log(" graph push Upload local graph to server (Pro: edges, Free: nodes)");
171
242
  console.log(" graph pull Download server graph and merge locally");
172
243
  console.log(" graph status Show local graph stats and sync state");
173
- console.log(" ci Plan portable 0dai CI pipelines and inspect AI-MQ [list|plan|mq-status] [--json]");
244
+ console.log(" mcp MCP server, tools, and health [list|catalog|doctor|call] [--json]");
245
+ console.log(" vault Local age-encrypted secrets vault [init|add|get] [--json]");
174
246
  console.log(" heatmap Repo treemap: LOC x agent-edit intensity");
175
247
  console.log(" session save Save session for roaming");
176
- console.log(" provider Local provider profiles, bindings, and direct invoke");
248
+ console.log(" provider Local provider profiles and BYOK registry [status|list|switch|clear]");
177
249
  console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
250
+ console.log(" models recommend Ledger-ranked model pick for a task type [--task TYPE] [--goal '...'] [--json]");
178
251
  console.log(" quota Agent subscription usage table [--refresh] [--json]");
179
252
  console.log(" usage Local token, task, and USD usage ledger [status|daily|monthly]");
180
253
  console.log(" workspace Manage tmux workspace sessions (init|up|status)");
181
- console.log(" runner Show runner/project-host architecture, queues, labels, and burst routing [status|plan|queue-status|label-audit|route-dry-run] [--json]");
182
- console.log(" report Privacy-safe project reports (preview|push|status)");
254
+ console.log(" report Repo-checkout helper: privacy-safe project reports (preview|push|status)");
255
+ console.log(" trust Pre-run blast-radius: protected paths, authority matrix, egress [--json]");
183
256
  console.log(" compliance SOC2/ISO evidence and ADR audit-trail export");
184
- console.log(" experience Structured experience events (list|stats|sync|warnings|dismiss)");
185
- console.log(" persona-simulate Produce a focus-group report and optional issue drafts");
257
+ console.log(" experience Structured experience events (list|stats|record-json|sync|warnings|dismiss)");
186
258
  console.log(" receipt Render a 1200×630 session receipt PNG [--last|--active|--session ID]");
187
- console.log(" boneyard Weekly digest of worst agent moves [--week YYYY-WW|current]");
188
259
  console.log(" gh branch-protection [print|apply|install] Manage generated GitHub branch protection");
189
260
  console.log(" import claude-code-agents Import .claude/agents/*.md as 0dai personas [--source DIR] [--target DIR] [--dry-run]");
190
261
  console.log(" auth logout Remove credentials");
@@ -200,29 +271,105 @@ function printHelp() {
200
271
  console.log("https://0dai.dev");
201
272
  }
202
273
 
274
+ function explainMissingHarvestHelper(target, args) {
275
+ const candidates = repoScriptCandidates(target, "harvest_experience.py");
276
+ if (args.includes("--json")) {
277
+ console.log(JSON.stringify({
278
+ error: "harvest_helper_unavailable",
279
+ helper: "harvest_experience.py",
280
+ target,
281
+ looked_in: candidates,
282
+ fallback_inputs: [
283
+ "ai/experience/events/*.json",
284
+ "ai/experience/events/*.jsonl",
285
+ "ai/experience/outbox/*.json",
286
+ "ai/experience/outbox/*.jsonl",
287
+ ],
288
+ required_fallback_fields: [
289
+ "schema",
290
+ "event_id",
291
+ "timestamp",
292
+ "event_type",
293
+ "tool",
294
+ "task_type",
295
+ "summary",
296
+ "paths",
297
+ "ci_passed",
298
+ "source",
299
+ ],
300
+ }, null, 2));
301
+ return;
302
+ }
303
+
304
+ log("harvest helper unavailable");
305
+ if (!args.includes("--explain")) return;
306
+
307
+ console.log(` ${D}looked for scripts/harvest_experience.py at:${R}`);
308
+ for (const candidate of candidates) {
309
+ console.log(` - ${candidate}`);
310
+ }
311
+ console.log(` ${D}fallback event inputs:${R}`);
312
+ console.log(" - ai/experience/events/*.json");
313
+ console.log(" - ai/experience/events/*.jsonl");
314
+ console.log(" - ai/experience/outbox/*.json");
315
+ console.log(" - ai/experience/outbox/*.jsonl");
316
+ console.log(` ${D}fallback schema fields:${R}`);
317
+ console.log(" schema, event_id, timestamp, event_type, tool, task_type, summary, paths, ci_passed, source");
318
+ }
319
+
203
320
  const SYNC_ALLOWED_FLAGS = new Set([
321
+ "--check",
204
322
  "--dry-run",
205
323
  "--yes",
206
324
  "-y",
207
325
  "--quiet",
208
326
  "-q",
209
327
  "--force",
328
+ "--force-template-reset",
210
329
  "--no-diff",
211
330
  "--no-mcp-auth",
212
331
  "--skip-link-check",
213
332
  "--strict-links",
214
333
  ]);
215
334
 
335
+ function printInitHelp(commandName = "init") {
336
+ console.log(`\n ${T}0dai ${commandName}${R} — Create or refresh the managed ai/ layer\n`);
337
+ console.log("Usage:");
338
+ console.log(` 0dai ${commandName} [--local] [--minimal] [--dry-run] [--no-wizard] [--auth-code CODE] [--code LICENSE] [--no-mcp-auth] [--mcp-host HOST] [--reset] [--target PATH]`);
339
+ console.log("");
340
+ console.log("Options:");
341
+ console.log(" --local Generate the ai/ layer offline without signing in");
342
+ console.log(" --minimal Generate the smallest starter layer");
343
+ console.log(" --dry-run Preview init changes without writing them");
344
+ console.log(" --no-wizard Skip the interactive first-run wizard");
345
+ console.log(" --auth-code Exchange a browser/device auth code before init");
346
+ console.log(" --code Redeem an activation/license code before init");
347
+ console.log(" --no-mcp-auth Skip MCP cloud-token bootstrap during init");
348
+ console.log(" --mcp-host Use a custom MCP cloud host");
349
+ console.log(" --reset Overwrite managed MCP entries during bootstrap");
350
+ console.log(" --target PATH Run init against another project path");
351
+ console.log("");
352
+ }
353
+
354
+ function handleInitHelp(args) {
355
+ const initArgs = args.slice(1);
356
+ if (!initArgs.includes("--help") && !initArgs.includes("-h")) return false;
357
+ printInitHelp(args[0] || "init");
358
+ return true;
359
+ }
360
+
216
361
  function printSyncHelp() {
217
362
  console.log(`\n ${T}0dai sync${R} — Update the managed ai/ layer\n`);
218
363
  console.log("Usage:");
219
- console.log(" 0dai sync [--dry-run] [--yes|-y] [--quiet|-q] [--force] [--no-diff] [--no-mcp-auth] [--target PATH]");
364
+ console.log(" 0dai sync [--check] [--dry-run] [--yes|-y] [--quiet|-q] [--force] [--force-template-reset] [--no-diff] [--no-mcp-auth] [--target PATH]");
220
365
  console.log("");
221
366
  console.log("Options:");
367
+ console.log(" --check CI-friendly alias for --dry-run --quiet");
222
368
  console.log(" --dry-run Preview managed file changes without writing them");
223
369
  console.log(" --yes, -y Apply changes without an interactive confirmation prompt");
224
370
  console.log(" --quiet, -q Reduce non-essential output");
225
371
  console.log(" --force Also overwrite native config files from managed ai/ sources");
372
+ console.log(" --force-template-reset Allow replacing project-owned roadmap/manifest state");
226
373
  console.log(" --no-diff Hide unified diff output in previews/prompts");
227
374
  console.log(" --no-mcp-auth Skip MCP cloud-token bootstrap during sync");
228
375
  console.log(" --skip-link-check Skip the SPEC-028 doc cross-link scan after sync");
@@ -248,6 +395,24 @@ function handleSyncHelpOrInvalidArgs(args) {
248
395
  return false;
249
396
  }
250
397
 
398
+ function printActivateHelp() {
399
+ console.log(`\n ${T}0dai activate${R} — Claim or inspect a 0dai activation\n`);
400
+ console.log("Usage:");
401
+ console.log(" 0dai activate [free|status|code <CODE>] [--target PATH]");
402
+ console.log("");
403
+ console.log("Privacy:");
404
+ console.log(" Local files stay local; 'activate free' sends one free_tier_activated event unless telemetry is opted out.");
405
+ console.log(" https://0dai.dev/legal/privacy");
406
+ console.log("");
407
+ }
408
+
409
+ function handleActivateHelp(args) {
410
+ const activateArgs = args.slice(1);
411
+ if (!activateArgs.includes("--help") && !activateArgs.includes("-h")) return false;
412
+ printActivateHelp();
413
+ return true;
414
+ }
415
+
251
416
  async function main() {
252
417
  const { args, target } = parseTargetAndArgs(process.argv.slice(2), process.cwd());
253
418
 
@@ -263,8 +428,18 @@ async function main() {
263
428
  return;
264
429
  }
265
430
 
266
- // Non-blocking version check (runs in background, once per day)
267
- checkVersion();
431
+ if ((cmd === "init" || cmd === "init-existing") && handleInitHelp(args)) {
432
+ return;
433
+ }
434
+
435
+ if (cmd === "activate" && handleActivateHelp(args)) {
436
+ return;
437
+ }
438
+
439
+ // Non-blocking version check (runs in background, once per day). Skip
440
+ // machine-readable commands so update network I/O never holds JSON output
441
+ // open or pollutes stdout.
442
+ if (!args.includes("--json") && cmd !== "init" && cmd !== "init-existing") checkVersion();
268
443
 
269
444
  // Track first run for time-to-init telemetry
270
445
  try { require("../lib/onboarding").trackFirstRun(target); } catch {}
@@ -288,6 +463,8 @@ async function main() {
288
463
  case "run": await cmdRun(args[1] || "", target, args.slice(2)); break;
289
464
  case "watch": cmdWatch(target, args.slice(1)); break;
290
465
  case "audit": cmdAudit(target); break;
466
+ case "export": await cmdExport(target, args); break;
467
+ case "vault": cmdVault(target, args[1], args.slice(2)); break;
291
468
  case "security": {
292
469
  const subSec = args[1] || "";
293
470
  if (subSec === "install-hook") {
@@ -328,24 +505,15 @@ async function main() {
328
505
  const driftMode = args.includes("--drift");
329
506
  // Go fast-path covers only the base read-only doctor (no --drift, no
330
507
  // network). Anything else falls through to the full Node implementation.
331
- if (!driftMode) {
508
+ const layerFreshness = collectLayerVersionFreshness(target);
509
+ if (!driftMode && layerFreshness.status !== "stale") {
332
510
  tryGoHotPath("doctor", target, args.slice(1));
333
511
  }
334
- cmdDoctor(target, { drift: driftMode });
335
- if (args.includes("--drift")) {
336
- const ds = findRepoScript(target, "drift_detector.py");
337
- console.log("\n drift report:");
338
- if (ds) {
339
- const result = spawnSync("python3", [ds, "report", "--target", target], { stdio: "inherit" });
340
- if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
341
- } else {
342
- console.log(` ${D}drift detector unavailable in this environment${R}`);
343
- }
344
- }
512
+ cmdDoctor(target, { drift: driftMode, json: args.includes("--json") });
345
513
  break;
346
514
  }
347
515
  case "drift": {
348
- const ds = findRepoScript(target, "drift_detector.py");
516
+ const ds = shared.resolvePythonScript(target, "drift_detector.py");
349
517
  if (!ds) { log("drift detector unavailable"); break; }
350
518
  if (sub === "accept" && args[2]) {
351
519
  spawnSync("python3", [ds, "accept", args[2], "--target", target], { stdio: "inherit" });
@@ -370,7 +538,7 @@ async function main() {
370
538
  case "project":
371
539
  if (sub === "status") cmdStatus(target, { json: args.includes("--json") });
372
540
  else if (sub === "bind") await cmdProjectBind(target, args.slice(2));
373
- else console.log("Usage: 0dai project [bind [--json]|status [--json]] [--target PATH]");
541
+ else console.log("Usage: 0dai project [bind [--name NAME] [--json]|status [--json]] [--target PATH]");
374
542
  break;
375
543
  case "auth":
376
544
  if (sub === "login") await cmdAuthLogin(args.slice(2));
@@ -385,6 +553,8 @@ async function main() {
385
553
  else if (sub === "code" || sub === "redeem") await cmdRedeem(args[2]);
386
554
  else console.log("Usage: 0dai activate [free|status|code <CODE>]");
387
555
  break;
556
+ case "mcp": cmdMcp(target, sub, args); break;
557
+ case "vault": cmdVault(target, args[1], args.slice(2)); break;
388
558
  case "session": cmdSession(target, sub, args); break;
389
559
  case "swarm": cmdSwarm(target, sub, args); break;
390
560
  case "workspace": cmdWorkspace(target, sub, args.slice(2)); break;
@@ -406,7 +576,7 @@ async function main() {
406
576
  case "feedback": await cmdFeedback(target, sub, args); break;
407
577
  case "harvest": {
408
578
  const harvestScript = findRepoScript(target, "harvest_experience.py");
409
- if (!harvestScript) { log("harvest helper unavailable"); break; }
579
+ if (!harvestScript) { explainMissingHarvestHelper(target, args); break; }
410
580
  const result = spawnSync("python3", [harvestScript, "--target", target, ...args.slice(1)], { stdio: "inherit", timeout: 15000 });
411
581
  if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
412
582
  break;
@@ -438,11 +608,15 @@ async function main() {
438
608
  break;
439
609
  }
440
610
  case "report": cmdReport(target, sub, args); break;
611
+ case "trust": cmdTrust(target, args.slice(1)); break;
441
612
  case "compliance": cmdCompliance(target, args.slice(1)); break;
442
613
  case "experience": cmdExperience(target, sub, args); break;
443
614
  case "persona-simulate": cmdPersonaSimulate(target, args.slice(1)); break;
444
615
  case "graph": await cmdGraph(target, sub, args); break;
445
- case "models": cmdModels(sub || args[1]); break;
616
+ case "models":
617
+ if (sub === "recommend") await cmdModelsRecommend(target, args.slice(2));
618
+ else cmdModels(sub || args[1]);
619
+ break;
446
620
  case "delegate": case "delegation": {
447
621
  const deScript = findRepoScript(target, "delegation_engine.py");
448
622
  if (!deScript) { log("delegation engine unavailable"); break; }
@@ -0,0 +1,121 @@
1
+ {
2
+ "schema_version": 1,
3
+ "updated_at": "2026-05-28",
4
+ "purpose": "Expected MCP exposure contract for agent runtimes. Health checks compare observed tool names against this manifest and scripts/mcp_tier_config.py.",
5
+ "project_server": {
6
+ "name": "0dai",
7
+ "config_path": ".mcp.json",
8
+ "tool_count_source": "scripts/mcp_tier_config.py",
9
+ "expected_tool_count": 113
10
+ },
11
+ "normalization": {
12
+ "accepted_examples": [
13
+ "memory_search",
14
+ "mcp__0dai__memory_search",
15
+ "mcp__0dai_internal__.memory_search",
16
+ "mcp__0dai_internal__memory_search"
17
+ ],
18
+ "rule": "Compare by canonical tool basename after MCP namespace prefixes."
19
+ },
20
+ "agents": {
21
+ "default": {
22
+ "required": [
23
+ "memory_search",
24
+ "get_project_health",
25
+ "get_codebase_map",
26
+ "record_experience"
27
+ ],
28
+ "recommended": [
29
+ "get_anomaly_taxonomy",
30
+ "get_anomaly_events",
31
+ "get_anomaly_summary",
32
+ "memory_add",
33
+ "memory_inject",
34
+ "search_experience",
35
+ "get_project_graph",
36
+ "get_specs",
37
+ "get_session",
38
+ "save_session",
39
+ "get_wal"
40
+ ]
41
+ },
42
+ "codex": {
43
+ "inherits": "default",
44
+ "required": [
45
+ "memory_search",
46
+ "get_project_health",
47
+ "get_codebase_map",
48
+ "record_experience"
49
+ ],
50
+ "recommended": [
51
+ "get_anomaly_taxonomy",
52
+ "get_anomaly_events",
53
+ "get_anomaly_summary",
54
+ "record_anomaly",
55
+ "memory_add",
56
+ "memory_inject",
57
+ "search_experience",
58
+ "get_project_graph",
59
+ "get_specs",
60
+ "get_session",
61
+ "save_session",
62
+ "swarm_delegate",
63
+ "watch_tasks",
64
+ "get_portfolio",
65
+ "get_wal"
66
+ ]
67
+ },
68
+ "claude": {
69
+ "inherits": "default",
70
+ "required": [
71
+ "memory_search",
72
+ "get_project_health",
73
+ "get_codebase_map",
74
+ "record_experience",
75
+ "get_session",
76
+ "save_session"
77
+ ],
78
+ "recommended": [
79
+ "get_anomaly_taxonomy",
80
+ "get_anomaly_events",
81
+ "get_anomaly_summary",
82
+ "record_anomaly",
83
+ "memory_add",
84
+ "memory_inject",
85
+ "search_experience",
86
+ "swarm_delegate",
87
+ "watch_tasks",
88
+ "get_portfolio",
89
+ "gh_label_create",
90
+ "gh_issue_edit_body"
91
+ ]
92
+ },
93
+ "pool": {
94
+ "inherits": "default",
95
+ "required": [
96
+ "memory_search",
97
+ "get_project_health",
98
+ "get_codebase_map",
99
+ "record_experience",
100
+ "watch_tasks"
101
+ ],
102
+ "recommended": [
103
+ "get_anomaly_taxonomy",
104
+ "get_anomaly_events",
105
+ "get_anomaly_summary",
106
+ "memory_add",
107
+ "search_experience",
108
+ "swarm_delegate",
109
+ "run_task",
110
+ "get_swarm_status",
111
+ "save_session"
112
+ ]
113
+ }
114
+ },
115
+ "statuses": {
116
+ "green": "All required and recommended tools for the agent were observed.",
117
+ "yellow": "Required tools were observed, but recommended tools or full server count evidence is missing.",
118
+ "red": "One or more required tools are missing from the observed runtime.",
119
+ "unknown": "No observed tool evidence was supplied by the runtime."
120
+ }
121
+ }