@0dai-dev/cli 4.2.0 → 4.3.5
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 +98 -10
- package/bin/0dai.js +298 -60
- package/lib/commands/audit.js +13 -0
- package/lib/commands/auth.js +344 -98
- 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 +39 -1
- 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 +504 -28
- package/lib/commands/loop.js +108 -0
- package/lib/commands/mcp.js +410 -0
- package/lib/commands/models.js +27 -3
- package/lib/commands/paste.js +114 -0
- package/lib/commands/play.js +173 -0
- package/lib/commands/provider.js +69 -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 +104 -7
- package/lib/commands/runner.js +527 -0
- package/lib/commands/session.js +1 -7
- package/lib/commands/standup.js +40 -0
- package/lib/commands/status.js +30 -1
- package/lib/commands/swarm.js +97 -4
- package/lib/commands/tui.js +81 -13
- package/lib/commands/upgrade.js +58 -0
- package/lib/commands/usage.js +87 -0
- package/lib/commands/vault.js +246 -0
- package/lib/onboarding.js +9 -3
- package/lib/shared.js +29 -14
- package/lib/utils/activation_telemetry.js +156 -0
- package/lib/utils/auth.js +1 -0
- package/lib/utils/canonical-counts.js +54 -0
- package/lib/utils/constants.js +7 -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/plan.js +47 -2
- package/lib/utils/run_cost.js +91 -0
- 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 +8 -4
- package/lib/tui/index.mjs +0 -34610
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0dai
|
|
2
2
|
|
|
3
|
-
One config layer for
|
|
3
|
+
One config layer for <!-- canonical-count:agent_clis_total -->7<!-- /canonical-count --> AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider, Qoder, Cursor.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,22 +8,88 @@ One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, A
|
|
|
8
8
|
npm install -g @0dai-dev/cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Five commands that matter
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
cd your-project
|
|
15
|
-
0dai auth
|
|
16
|
-
0dai
|
|
17
|
-
0dai
|
|
18
|
-
0dai
|
|
19
|
-
0dai
|
|
20
|
-
0dai doctor # check health
|
|
21
|
-
0dai status # maturity, swarm tasks, session
|
|
15
|
+
0dai init # create ai/ layer, auth, and MCP bootstrap
|
|
16
|
+
0dai status # maturity, swarm tasks, and session state
|
|
17
|
+
0dai sync # refresh ai/ after repo or server changes
|
|
18
|
+
0dai run "…" # split a backlog item into agent tasks
|
|
19
|
+
0dai doctor # health check for credentials, drift, and env
|
|
22
20
|
```
|
|
23
21
|
|
|
22
|
+
## All commands
|
|
23
|
+
|
|
24
|
+
Start (first 5 minutes):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
0dai init # create ai/ layer + MCP [--local] [--dry-run] [--minimal]
|
|
28
|
+
0dai init --auth-code <code> --code <TEAM-CODE> # non-interactive auth + activation
|
|
29
|
+
0dai doctor # check health, credentials, and drift [--drift]
|
|
30
|
+
0dai status # maturity, swarm, and session state [--json]
|
|
31
|
+
0dai quickstart # auth, init, doctor, and status in one pass
|
|
32
|
+
0dai detect # show detected stack
|
|
33
|
+
0dai auth login # OAuth/device flow [--device] [--no-browser] [--code CODE] [--mcp]
|
|
34
|
+
0dai auth mcp # store MCP token from current auth or device code
|
|
35
|
+
0dai auth status # account and usage
|
|
36
|
+
0dai activate free # claim free activation license
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Daily (regular work):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
0dai sync # update ai/ after repo or server changes [--dry-run] [--yes] [--quiet] [--force]
|
|
43
|
+
0dai run <goal> # split a backlog item into agent tasks [--dry-run] [--dry-cost] [--agent claude|codex|gemini]
|
|
44
|
+
0dai swarm status # show queued, active, and done tasks
|
|
45
|
+
0dai swarm add # queue one task [--task '...' --to agent]
|
|
46
|
+
0dai swarm-run # add, dispatch, and wait for one swarm task as JSON
|
|
47
|
+
0dai harvest # convert experience events into candidate lessons
|
|
48
|
+
0dai watch # live task monitor [--interval N]
|
|
49
|
+
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
|
|
52
|
+
0dai feedback retry # retry queued feedback after a failed push
|
|
53
|
+
0dai persona-simulate "topic" # focus-group report and optional issue drafts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Pro / advanced:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
0dai init-existing # existing-repo setup alias for init
|
|
60
|
+
0dai project bind # bind repository to your 0dai account [--json]
|
|
61
|
+
0dai project status # local project binding and health [--json]
|
|
62
|
+
0dai graph push # upload local graph (Pro: edges, Free: nodes)
|
|
63
|
+
0dai graph pull # download server graph and merge locally
|
|
64
|
+
0dai graph status # local graph stats and sync state
|
|
65
|
+
0dai ci # portable CI pipelines and AI-MQ [list|plan|mq-status]
|
|
66
|
+
0dai heatmap # repo treemap: LOC × agent-edit intensity
|
|
67
|
+
0dai session save # save session for roaming
|
|
68
|
+
0dai provider # local provider profiles and direct invoke
|
|
69
|
+
0dai models # model ratings (--fast/--balanced/--deep/--available)
|
|
70
|
+
0dai quota # agent subscription usage [--refresh] [--json]
|
|
71
|
+
0dai usage # local token, task, and USD ledger [status|daily|monthly]
|
|
72
|
+
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)
|
|
75
|
+
0dai compliance # SOC2/ISO evidence and ADR audit-trail export
|
|
76
|
+
0dai experience # structured experience events (list|stats|sync|warnings|dismiss)
|
|
77
|
+
0dai receipt # session receipt PNG [--last|--active|--session ID]
|
|
78
|
+
0dai boneyard # weekly digest of worst agent moves [--week YYYY-WW|current]
|
|
79
|
+
0dai gh branch-protection # GitHub branch protection [print|apply|install]
|
|
80
|
+
0dai import claude-code-agents # import .claude/agents/*.md as personas [--dry-run]
|
|
81
|
+
0dai auth logout # remove credentials
|
|
82
|
+
0dai activate code <TEAM-CODE> # redeem Pro/Team activation code
|
|
83
|
+
0dai activate status # activation and bound-project status
|
|
84
|
+
0dai redeem <CODE> # redeem a plan upgrade code
|
|
85
|
+
0dai upgrade # open pricing page (browser or printed URL)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Global flags: `--target PATH`, `--version`, `--help`, `--json`, `--quiet`. See `0dai --help` for the full surface.
|
|
89
|
+
|
|
24
90
|
## What it does
|
|
25
91
|
|
|
26
|
-
`0dai init` and `0dai sync` are activation-first.
|
|
92
|
+
`0dai init` and `0dai sync` are activation-first. `init` can complete auth and plan activation in one run: browser/OAuth exchange code via `--auth-code`, Team/Pro activation code via `--code` or `--activation-code`, or the interactive OAuth/device-code prompt. After that it binds the project and sends only allowlisted project metadata (file names + package/build manifests) to the API and generates:
|
|
27
93
|
|
|
28
94
|
- `ai/` — manifests, personas, skills, playbooks, delegation policy
|
|
29
95
|
- `.claude/` — settings, agents, hooks, rules
|
|
@@ -34,6 +100,28 @@ cd your-project
|
|
|
34
100
|
|
|
35
101
|
Your source code is never sent. Only file names and package/build manifests.
|
|
36
102
|
|
|
103
|
+
## Why 0dai, not just Cursor or Copilot?
|
|
104
|
+
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
A fresh `0dai init` turns a repo into an AI-ready workspace. It records stack detection, safe project commands, agent preferences, MCP config, and delegation policy under `ai/`, then writes native files such as `AGENTS.md` and `.mcp.json`. Your source stays local; only file names and package/build manifests go to the API.
|
|
108
|
+
|
|
109
|
+
0dai is useful when work needs more than one model or more than one sitting. You can ask for a backlog item with `0dai run "add password reset tests"`, check the queue with `0dai swarm status`, and continue from the same project facts in another agent CLI. Cursor or Copilot can still be your editor; 0dai keeps the repo-level context and task state outside the editor.
|
|
110
|
+
|
|
111
|
+
The first five minutes are direct: run `0dai init`, run `0dai doctor`, then try one backlog task with `0dai run "..."` or inspect state with `0dai status`. If you only want a local setup, use `0dai init --local`; cloud mode adds sync, graph history, and shared task records.
|
|
112
|
+
|
|
113
|
+
## Free-tier examples
|
|
114
|
+
|
|
115
|
+
On the free tier you get ≈5 backlog tasks/day. Example: in one day you might ask 0dai to split and queue these tasks:
|
|
116
|
+
|
|
117
|
+
- Write missing tests for the login form.
|
|
118
|
+
- Add empty-state copy to the dashboard.
|
|
119
|
+
- Check a failing CI log and suggest the smallest code fix.
|
|
120
|
+
- Draft a PR checklist from the files changed.
|
|
121
|
+
- Summarize today's `ai/swarm/done` tasks for standup.
|
|
122
|
+
|
|
123
|
+
Each task can become one or more agent prompts, so the exact count depends on task size. Treat the free tier as enough for a daily cleanup pass; Pro is for larger task queues, graph sync with edges, and session roaming across projects.
|
|
124
|
+
|
|
37
125
|
## Links
|
|
38
126
|
|
|
39
127
|
- Website: https://0dai.dev
|
package/bin/0dai.js
CHANGED
|
@@ -8,45 +8,261 @@
|
|
|
8
8
|
* Shared config and utilities live in lib/shared.js.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const nodePath = require("path");
|
|
12
|
+
|
|
13
|
+
function parseTargetAndArgs(rawArgs, cwd) {
|
|
14
|
+
const args = rawArgs.slice();
|
|
15
|
+
let target = cwd;
|
|
16
|
+
const ti = args.indexOf("--target");
|
|
17
|
+
if (ti >= 0 && args[ti + 1]) { target = nodePath.resolve(args[ti + 1]); args.splice(ti, 2); }
|
|
18
|
+
return { args, target };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const earlyParsed = parseTargetAndArgs(process.argv.slice(2), process.cwd());
|
|
22
|
+
if ((earlyParsed.args[0] || "help") === "swarm-run") {
|
|
23
|
+
const { cmdSwarmRun } = require("../lib/commands/swarm");
|
|
24
|
+
cmdSwarmRun(earlyParsed.target, earlyParsed.args.slice(1));
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
const shared = require("../lib/shared");
|
|
12
29
|
const { T, R, D, log, VERSION, fs, path, spawnSync, findRepoScript, checkVersion } = shared;
|
|
13
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Hot-path Go binary fallback (issue #2424).
|
|
33
|
+
*
|
|
34
|
+
* For read-only hot-path commands (status/doctor/version) we attempt to delegate
|
|
35
|
+
* to a Go binary when:
|
|
36
|
+
* 1. ODAI_GO_BIN is set and points at an executable file, OR a binary called
|
|
37
|
+
* `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).
|
|
40
|
+
* 3. ODAI_GO_DISABLE is NOT set to a truthy value.
|
|
41
|
+
*
|
|
42
|
+
* If any of these checks fail we silently fall through to the existing
|
|
43
|
+
* Python/Node implementations. The goal is zero behaviour change when the Go
|
|
44
|
+
* binary is missing, broken, or version-skewed.
|
|
45
|
+
*/
|
|
46
|
+
function locateGoBinary() {
|
|
47
|
+
if (process.env.ODAI_GO_DISABLE && process.env.ODAI_GO_DISABLE !== "0") return null;
|
|
48
|
+
const explicit = process.env.ODAI_GO_BIN;
|
|
49
|
+
if (explicit && fs.existsSync(explicit)) {
|
|
50
|
+
try { if ((fs.statSync(explicit).mode & 0o111) !== 0) return explicit; } catch {}
|
|
51
|
+
}
|
|
52
|
+
// Search PATH for `0dai-go`.
|
|
53
|
+
const pathEnv = process.env.PATH || "";
|
|
54
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
55
|
+
for (const dir of pathEnv.split(sep)) {
|
|
56
|
+
if (!dir) continue;
|
|
57
|
+
const candidate = path.join(dir, "0dai-go");
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(candidate) && (fs.statSync(candidate).mode & 0o111) !== 0) return candidate;
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function goBinaryCompatible(binPath) {
|
|
66
|
+
try {
|
|
67
|
+
const res = spawnSync(binPath, ["version", "--json"], { timeout: 3000 });
|
|
68
|
+
if (res.status !== 0 || !res.stdout) return false;
|
|
69
|
+
const info = JSON.parse(res.stdout.toString());
|
|
70
|
+
if (!info || typeof info.binary_version !== "string") return false;
|
|
71
|
+
return info.binary_version === VERSION;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Attempt to dispatch a hot-path command to the Go binary.
|
|
79
|
+
* Returns true if Go handled the command (process should exit), false if the
|
|
80
|
+
* caller must fall back to the existing Python/Node path.
|
|
81
|
+
*/
|
|
82
|
+
function tryGoHotPath(cmdName, target, argv) {
|
|
83
|
+
const bin = locateGoBinary();
|
|
84
|
+
if (!bin) return false;
|
|
85
|
+
if (!goBinaryCompatible(bin)) return false;
|
|
86
|
+
const forwarded = [cmdName, "--target", target, ...argv];
|
|
87
|
+
const res = spawnSync(bin, forwarded, { stdio: "inherit" });
|
|
88
|
+
if (res.error) return false;
|
|
89
|
+
if (typeof res.status === "number") process.exit(res.status);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Export for tests; harmless at runtime.
|
|
94
|
+
module.exports = { locateGoBinary, goBinaryCompatible, tryGoHotPath };
|
|
95
|
+
const { loadCanonicalCounts, mcpToolsLabel } = require("../lib/utils/canonical-counts");
|
|
96
|
+
|
|
14
97
|
// --- Command imports ---
|
|
15
|
-
const { cmdAuthLogin, cmdAuthLogout, cmdRedeem, cmdAuthStatus, cmdActivateFree, cmdActivateStatus } = require("../lib/commands/auth");
|
|
16
|
-
const { cmdInit, cmdSync } = require("../lib/commands/init");
|
|
98
|
+
const { cmdAuthLogin, cmdAuthLogout, cmdRedeem, cmdAuthStatus, cmdAuthMcp, cmdActivateFree, cmdActivateStatus } = require("../lib/commands/auth");
|
|
99
|
+
const { cmdInit, cmdSync, cmdProjectBind } = require("../lib/commands/init");
|
|
17
100
|
const { cmdDetect } = require("../lib/commands/detect");
|
|
18
101
|
const { cmdAudit } = require("../lib/commands/audit");
|
|
19
102
|
const { cmdDoctor } = require("../lib/commands/doctor");
|
|
20
103
|
const { cmdValidate } = require("../lib/commands/validate");
|
|
21
104
|
const { cmdUpdate } = require("../lib/commands/update");
|
|
105
|
+
const { cmdUpgrade } = require("../lib/commands/upgrade");
|
|
22
106
|
const { cmdReflect } = require("../lib/commands/reflect");
|
|
23
107
|
const { cmdMetrics } = require("../lib/commands/metrics");
|
|
108
|
+
const { cmdHeatmap } = require("../lib/commands/heatmap");
|
|
24
109
|
const { cmdStatus } = require("../lib/commands/status");
|
|
25
110
|
const { cmdPortfolio } = require("../lib/commands/portfolio");
|
|
26
111
|
const { cmdRun } = require("../lib/commands/run");
|
|
27
112
|
const { cmdWatch } = require("../lib/commands/watch");
|
|
28
113
|
const { cmdModels } = require("../lib/commands/models");
|
|
29
114
|
const { cmdSession } = require("../lib/commands/session");
|
|
30
|
-
const { cmdSwarm } = require("../lib/commands/swarm");
|
|
115
|
+
const { cmdSwarm, cmdSwarmRun } = require("../lib/commands/swarm");
|
|
116
|
+
const { cmdStandup } = require("../lib/commands/standup");
|
|
31
117
|
const { cmdFeedback, cmdFeedbackPush } = require("../lib/commands/feedback");
|
|
118
|
+
const { cmdPlay } = require("../lib/commands/play");
|
|
32
119
|
const { cmdGraph } = require("../lib/commands/graph");
|
|
33
120
|
const { cmdReport } = require("../lib/commands/report");
|
|
121
|
+
const { cmdCompliance } = require("../lib/commands/compliance");
|
|
34
122
|
const { cmdExperience } = require("../lib/commands/experience");
|
|
35
123
|
const { cmdWorkspace } = require("../lib/commands/workspace");
|
|
36
124
|
const { cmdSsh } = require("../lib/commands/ssh");
|
|
37
125
|
const { cmdTui } = require("../lib/commands/tui");
|
|
38
126
|
const { cmdPersonaSimulate } = require("../lib/commands/persona-simulate");
|
|
39
127
|
const { cmdProvider } = require("../lib/commands/provider");
|
|
128
|
+
const { cmdPaste } = require("../lib/commands/paste");
|
|
129
|
+
const { cmdReceipt } = require("../lib/commands/receipt");
|
|
130
|
+
const { cmdBoneyard } = require("../lib/commands/boneyard");
|
|
131
|
+
const { cmdQuota } = require("../lib/commands/quota");
|
|
132
|
+
const { cmdUsage } = require("../lib/commands/usage");
|
|
133
|
+
const { cmdGh } = require("../lib/commands/gh");
|
|
134
|
+
const { cmdLoop } = require("../lib/commands/loop");
|
|
135
|
+
const { cmdImportClaudeCodeAgents } = require("../lib/commands/import_claude_code_agents");
|
|
136
|
+
const { cmdRunner } = require("../lib/commands/runner");
|
|
137
|
+
const { cmdCi } = require("../lib/commands/ci");
|
|
138
|
+
|
|
139
|
+
function printHelp() {
|
|
140
|
+
const counts = loadCanonicalCounts();
|
|
141
|
+
console.log(`\n ${T}0dai${R} v${VERSION} — One config for ${counts.agent_clis_total} AI agent CLIs · ${mcpToolsLabel(counts)}\n`);
|
|
142
|
+
console.log("Start (first 5 minutes):");
|
|
143
|
+
console.log(" init Create ai/ layer + MCP [--local] [--dry-run] [--minimal]");
|
|
144
|
+
console.log(" doctor Check health, credentials, and drift [--drift]");
|
|
145
|
+
console.log(" status Show maturity, swarm, and session state [--json]");
|
|
146
|
+
console.log(" quickstart Run auth, init, doctor, and status checks in order");
|
|
147
|
+
console.log(" detect Show detected stack");
|
|
148
|
+
console.log(" auth login Authenticate (OAuth/device code flow) [--device] [--no-browser] [--code CODE] [--mcp]");
|
|
149
|
+
console.log(" auth mcp Store an MCP token from current auth or device code [--device] [--no-browser]");
|
|
150
|
+
console.log(" auth status Show account and usage");
|
|
151
|
+
console.log(" activate free Claim free activation license");
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log("Daily (regular work):");
|
|
154
|
+
console.log(" sync Update ai/ layer after repo or server changes [--dry-run] [--yes] [--quiet] [--force]");
|
|
155
|
+
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
|
+
console.log(" swarm status Show queued, active, and done tasks");
|
|
157
|
+
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");
|
|
159
|
+
console.log(" harvest Convert experience events into candidate lessons");
|
|
160
|
+
console.log(" watch Live task monitor: queue, active, recently done [--interval N]");
|
|
161
|
+
console.log(" reflect Session reflection: delivered, delegation rate, blockers");
|
|
162
|
+
console.log(" standup Morning voice briefing about overnight agent work");
|
|
163
|
+
console.log(" feedback push Send feedback to 0dai");
|
|
164
|
+
console.log(" feedback retry Retry queued feedback after a failed push");
|
|
165
|
+
console.log("");
|
|
166
|
+
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]");
|
|
169
|
+
console.log(" project status Show local project binding and health state [--json]");
|
|
170
|
+
console.log(" graph push Upload local graph to server (Pro: edges, Free: nodes)");
|
|
171
|
+
console.log(" graph pull Download server graph and merge locally");
|
|
172
|
+
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]");
|
|
174
|
+
console.log(" heatmap Repo treemap: LOC x agent-edit intensity");
|
|
175
|
+
console.log(" session save Save session for roaming");
|
|
176
|
+
console.log(" provider Local provider profiles, bindings, and direct invoke");
|
|
177
|
+
console.log(" models Show model ratings (--fast/--balanced/--deep/--available)");
|
|
178
|
+
console.log(" quota Agent subscription usage table [--refresh] [--json]");
|
|
179
|
+
console.log(" usage Local token, task, and USD usage ledger [status|daily|monthly]");
|
|
180
|
+
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)");
|
|
183
|
+
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");
|
|
186
|
+
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
|
+
console.log(" gh branch-protection [print|apply|install] Manage generated GitHub branch protection");
|
|
189
|
+
console.log(" import claude-code-agents Import .claude/agents/*.md as 0dai personas [--source DIR] [--target DIR] [--dry-run]");
|
|
190
|
+
console.log(" auth logout Remove credentials");
|
|
191
|
+
console.log(" activate code Redeem a Pro/Team activation code");
|
|
192
|
+
console.log(" activate status Show activation and bound-project status");
|
|
193
|
+
console.log(" redeem <CODE> Redeem a plan upgrade code");
|
|
194
|
+
console.log(" advanced flags:");
|
|
195
|
+
console.log(" --target PATH Run any command against another project path");
|
|
196
|
+
console.log(" init --auth-code CODE --code TEAM-CODE Sign in and activate during init");
|
|
197
|
+
console.log(" init --no-mcp-auth --mcp-host HOST --reset Control MCP bootstrap");
|
|
198
|
+
console.log(" sync --no-diff --force Control managed config updates");
|
|
199
|
+
console.log(" --version\n");
|
|
200
|
+
console.log("https://0dai.dev");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const SYNC_ALLOWED_FLAGS = new Set([
|
|
204
|
+
"--dry-run",
|
|
205
|
+
"--yes",
|
|
206
|
+
"-y",
|
|
207
|
+
"--quiet",
|
|
208
|
+
"-q",
|
|
209
|
+
"--force",
|
|
210
|
+
"--no-diff",
|
|
211
|
+
"--no-mcp-auth",
|
|
212
|
+
"--skip-link-check",
|
|
213
|
+
"--strict-links",
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
function printSyncHelp() {
|
|
217
|
+
console.log(`\n ${T}0dai sync${R} — Update the managed ai/ layer\n`);
|
|
218
|
+
console.log("Usage:");
|
|
219
|
+
console.log(" 0dai sync [--dry-run] [--yes|-y] [--quiet|-q] [--force] [--no-diff] [--no-mcp-auth] [--target PATH]");
|
|
220
|
+
console.log("");
|
|
221
|
+
console.log("Options:");
|
|
222
|
+
console.log(" --dry-run Preview managed file changes without writing them");
|
|
223
|
+
console.log(" --yes, -y Apply changes without an interactive confirmation prompt");
|
|
224
|
+
console.log(" --quiet, -q Reduce non-essential output");
|
|
225
|
+
console.log(" --force Also overwrite native config files from managed ai/ sources");
|
|
226
|
+
console.log(" --no-diff Hide unified diff output in previews/prompts");
|
|
227
|
+
console.log(" --no-mcp-auth Skip MCP cloud-token bootstrap during sync");
|
|
228
|
+
console.log(" --skip-link-check Skip the SPEC-028 doc cross-link scan after sync");
|
|
229
|
+
console.log(" --strict-links Fail sync if doc_link_check reports broken or closed refs");
|
|
230
|
+
console.log(" --target PATH Run sync against another project path");
|
|
231
|
+
console.log("");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function handleSyncHelpOrInvalidArgs(args) {
|
|
235
|
+
const syncArgs = args.slice(1);
|
|
236
|
+
if (syncArgs.includes("--help") || syncArgs.includes("-h")) {
|
|
237
|
+
printSyncHelp();
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const arg of syncArgs) {
|
|
242
|
+
if (SYNC_ALLOWED_FLAGS.has(arg)) continue;
|
|
243
|
+
const label = arg.startsWith("-") ? "unknown sync option" : "unexpected sync argument";
|
|
244
|
+
log(`${label}: ${arg}`);
|
|
245
|
+
printSyncHelp();
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
40
250
|
|
|
41
251
|
async function main() {
|
|
42
|
-
const args = process.argv.slice(2);
|
|
43
|
-
let target = process.cwd();
|
|
44
|
-
const ti = args.indexOf("--target");
|
|
45
|
-
if (ti >= 0 && args[ti + 1]) { target = path.resolve(args[ti + 1]); args.splice(ti, 2); }
|
|
252
|
+
const { args, target } = parseTargetAndArgs(process.argv.slice(2), process.cwd());
|
|
46
253
|
|
|
47
254
|
const cmd = args[0] || "help";
|
|
48
255
|
const sub = args[1] || "";
|
|
49
256
|
|
|
257
|
+
if (cmd === "swarm-run") {
|
|
258
|
+
cmdSwarmRun(target, args.slice(1));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (cmd === "sync" && handleSyncHelpOrInvalidArgs(args)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
50
266
|
// Non-blocking version check (runs in background, once per day)
|
|
51
267
|
checkVersion();
|
|
52
268
|
|
|
@@ -54,7 +270,7 @@ async function main() {
|
|
|
54
270
|
try { require("../lib/onboarding").trackFirstRun(target); } catch {}
|
|
55
271
|
|
|
56
272
|
// First-run wizard prompt for commands that need ai/
|
|
57
|
-
if (["status", "doctor", "sync", "swarm", "detect", "validate", "reflect", "metrics", "experience", "graph", "session", "report"].includes(cmd)) {
|
|
273
|
+
if (["status", "doctor", "sync", "swarm", "swarm-run", "detect", "validate", "reflect", "metrics", "experience", "graph", "session", "report", "live", "feed", "live-feed"].includes(cmd)) {
|
|
58
274
|
try {
|
|
59
275
|
const { maybeWizard } = require("../lib/wizard");
|
|
60
276
|
const handled = await maybeWizard(target, cmd);
|
|
@@ -68,6 +284,7 @@ async function main() {
|
|
|
68
284
|
await ob.cmdQuickstart(target, { cmdDoctor, cmdStatus, cmdInit: (t, a) => cmdInit(t, a || []), log, ensureAuthenticated: shared.makeEnsureAuthenticated(cmdAuthLogin) });
|
|
69
285
|
break;
|
|
70
286
|
}
|
|
287
|
+
case "init-existing": await cmdInit(target, args); break;
|
|
71
288
|
case "run": await cmdRun(args[1] || "", target, args.slice(2)); break;
|
|
72
289
|
case "watch": cmdWatch(target, args.slice(1)); break;
|
|
73
290
|
case "audit": cmdAudit(target); break;
|
|
@@ -109,6 +326,11 @@ async function main() {
|
|
|
109
326
|
case "detect": await cmdDetect(target); break;
|
|
110
327
|
case "doctor": {
|
|
111
328
|
const driftMode = args.includes("--drift");
|
|
329
|
+
// Go fast-path covers only the base read-only doctor (no --drift, no
|
|
330
|
+
// network). Anything else falls through to the full Node implementation.
|
|
331
|
+
if (!driftMode) {
|
|
332
|
+
tryGoHotPath("doctor", target, args.slice(1));
|
|
333
|
+
}
|
|
112
334
|
cmdDoctor(target, { drift: driftMode });
|
|
113
335
|
if (args.includes("--drift")) {
|
|
114
336
|
const ds = findRepoScript(target, "drift_detector.py");
|
|
@@ -137,28 +359,86 @@ async function main() {
|
|
|
137
359
|
case "validate": cmdValidate(target); break;
|
|
138
360
|
case "reflect": cmdReflect(target, args); break;
|
|
139
361
|
case "update": cmdUpdate(args); break;
|
|
362
|
+
case "upgrade": cmdUpgrade(); break;
|
|
140
363
|
case "metrics": cmdMetrics(target); break;
|
|
364
|
+
case "heatmap": cmdHeatmap(target, args.slice(1)); break;
|
|
141
365
|
case "portfolio": cmdPortfolio(); break;
|
|
142
|
-
case "status":
|
|
366
|
+
case "status":
|
|
367
|
+
tryGoHotPath("status", target, args.slice(1));
|
|
368
|
+
cmdStatus(target, { json: args.includes("--json") });
|
|
369
|
+
break;
|
|
370
|
+
case "project":
|
|
371
|
+
if (sub === "status") cmdStatus(target, { json: args.includes("--json") });
|
|
372
|
+
else if (sub === "bind") await cmdProjectBind(target, args.slice(2));
|
|
373
|
+
else console.log("Usage: 0dai project [bind [--json]|status [--json]] [--target PATH]");
|
|
374
|
+
break;
|
|
143
375
|
case "auth":
|
|
144
|
-
if (sub === "login") await cmdAuthLogin();
|
|
376
|
+
if (sub === "login") await cmdAuthLogin(args.slice(2));
|
|
145
377
|
else if (sub === "logout") cmdAuthLogout();
|
|
146
378
|
else if (sub === "status") await cmdAuthStatus();
|
|
147
|
-
else
|
|
379
|
+
else if (sub === "mcp") await cmdAuthMcp(args.slice(2));
|
|
380
|
+
else console.log("Usage: 0dai auth [login [--device] [--no-browser] [--code <success-code>] [--mcp]|mcp [--device] [--no-browser]|logout|status]");
|
|
148
381
|
break;
|
|
149
382
|
case "activate":
|
|
150
383
|
if (sub === "free" || !sub) await cmdActivateFree();
|
|
151
384
|
else if (sub === "status") await cmdActivateStatus();
|
|
152
|
-
else
|
|
385
|
+
else if (sub === "code" || sub === "redeem") await cmdRedeem(args[2]);
|
|
386
|
+
else console.log("Usage: 0dai activate [free|status|code <CODE>]");
|
|
153
387
|
break;
|
|
154
388
|
case "session": cmdSession(target, sub, args); break;
|
|
155
389
|
case "swarm": cmdSwarm(target, sub, args); break;
|
|
156
390
|
case "workspace": cmdWorkspace(target, sub, args.slice(2)); break;
|
|
391
|
+
case "runner": cmdRunner(target, sub, args); break;
|
|
392
|
+
case "ci": cmdCi(target, sub, args); break;
|
|
157
393
|
case "ssh": await cmdSsh(target, sub, args); break;
|
|
158
394
|
case "provider": cmdProvider(target, args.slice(1)); break;
|
|
395
|
+
case "standup": await cmdStandup(target, args.slice(1)); break;
|
|
159
396
|
case "tui": case "dashboard": await cmdTui(target, args.slice(1)); break;
|
|
397
|
+
case "bundle":
|
|
398
|
+
case "replay": {
|
|
399
|
+
const glassBoxScript = findRepoScript(target, "glass_box.py");
|
|
400
|
+
if (!glassBoxScript) { log("glass box bundle helper unavailable"); break; }
|
|
401
|
+
const fwd = [glassBoxScript, cmd, "--target", target, ...args.slice(1)];
|
|
402
|
+
const result = spawnSync("python3", fwd, { stdio: "inherit", timeout: 30000 });
|
|
403
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
160
406
|
case "feedback": await cmdFeedback(target, sub, args); break;
|
|
407
|
+
case "harvest": {
|
|
408
|
+
const harvestScript = findRepoScript(target, "harvest_experience.py");
|
|
409
|
+
if (!harvestScript) { log("harvest helper unavailable"); break; }
|
|
410
|
+
const result = spawnSync("python3", [harvestScript, "--target", target, ...args.slice(1)], { stdio: "inherit", timeout: 15000 });
|
|
411
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
case "play": await cmdPlay(target, sub, args); break;
|
|
415
|
+
case "paste": await cmdPaste(args.slice(1)); break;
|
|
416
|
+
case "receipt": cmdReceipt(target, args.slice(1)); break;
|
|
417
|
+
case "boneyard": cmdBoneyard(target, args.slice(1)); break;
|
|
418
|
+
case "quota": case "quotas": cmdQuota(target, args.slice(1)); break;
|
|
419
|
+
case "usage": cmdUsage(target, args.slice(1)); break;
|
|
420
|
+
case "gh": await cmdGh(target, sub, args); break;
|
|
421
|
+
case "loop": cmdLoop(target, sub, args); break;
|
|
422
|
+
case "import": {
|
|
423
|
+
const what = sub || args[1];
|
|
424
|
+
if (what === "claude-code-agents" || what === "claude-agents") {
|
|
425
|
+
await cmdImportClaudeCodeAgents(target, args.slice(2));
|
|
426
|
+
} else {
|
|
427
|
+
log(`unknown import source: ${what || "(none)"}`);
|
|
428
|
+
console.log(` ${D}supported: claude-code-agents${R}`);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case "live": case "feed": case "live-feed": {
|
|
434
|
+
const liveScript = findRepoScript(target, "live_feed.py");
|
|
435
|
+
if (!liveScript) { log("live feed helper unavailable"); break; }
|
|
436
|
+
const result = spawnSync("python3", [liveScript, "--target", target, ...args.slice(1)], { stdio: "inherit", timeout: 15000 });
|
|
437
|
+
if (typeof result.status === "number" && result.status !== 0) process.exit(result.status);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
161
440
|
case "report": cmdReport(target, sub, args); break;
|
|
441
|
+
case "compliance": cmdCompliance(target, args.slice(1)); break;
|
|
162
442
|
case "experience": cmdExperience(target, sub, args); break;
|
|
163
443
|
case "persona-simulate": cmdPersonaSimulate(target, args.slice(1)); break;
|
|
164
444
|
case "graph": await cmdGraph(target, sub, args); break;
|
|
@@ -251,55 +531,13 @@ async function main() {
|
|
|
251
531
|
else log(`error: ${e.message}`);
|
|
252
532
|
}
|
|
253
533
|
break;
|
|
254
|
-
case "--version":
|
|
534
|
+
case "--version":
|
|
535
|
+
case "version":
|
|
536
|
+
tryGoHotPath("version", target, args.slice(1));
|
|
537
|
+
console.log(`${T}0dai${R} ${VERSION}`);
|
|
538
|
+
break;
|
|
255
539
|
case "help": case "--help": case "-h":
|
|
256
|
-
|
|
257
|
-
console.log("Commands:");
|
|
258
|
-
console.log(" run <goal> AI-decompose goal → swarm tasks (auto-routed) [--dry-run]");
|
|
259
|
-
console.log(" watch Live task monitor: queue, active, recently done [--interval N]");
|
|
260
|
-
console.log(" audit Scan ai/ and agent configs for leaked secrets");
|
|
261
|
-
console.log(" init Initialize ai/ layer (via API) [--dry-run] [--minimal]");
|
|
262
|
-
console.log(" sync Update ai/ layer (via API) [--dry-run] [--quiet] [--force]");
|
|
263
|
-
console.log(" detect Show detected stack");
|
|
264
|
-
console.log(" doctor Check health + credentials checklist [--drift]");
|
|
265
|
-
console.log(" update Update all installed agent CLIs to latest [--dry-run]");
|
|
266
|
-
console.log(" validate Validate ai/ layer completeness");
|
|
267
|
-
console.log(" reflect Session reflection: delivered, delegation rate, blockers");
|
|
268
|
-
console.log(" metrics Effectiveness score: adoption funnel, sessions, delegation");
|
|
269
|
-
console.log(" portfolio All tracked projects: score, sessions, agents, last activity");
|
|
270
|
-
console.log(" status Show maturity, swarm, session [--json]");
|
|
271
|
-
console.log(" session save Save session for roaming");
|
|
272
|
-
console.log(" swarm status Task queue & delegation");
|
|
273
|
-
console.log(" swarm webhook add Register webhook (fires on task done/failed)");
|
|
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");
|
|
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");
|
|
281
|
-
console.log(" feedback push Send feedback to 0dai");
|
|
282
|
-
console.log(" report preview Preview privacy-safe project report");
|
|
283
|
-
console.log(" report push Send report to 0dai (with offline queue)");
|
|
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");
|
|
286
|
-
console.log(" experience list Show recent structured experience events");
|
|
287
|
-
console.log(" experience stats Show success and cost stats by agent/model/type");
|
|
288
|
-
console.log(" graph push Upload local graph to server (Pro: edges, Free: nodes)");
|
|
289
|
-
console.log(" graph pull Download server graph and merge locally");
|
|
290
|
-
console.log(" graph status Show local graph stats and sync state");
|
|
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");
|
|
294
|
-
console.log(" terminal Launch interactive agent session");
|
|
295
|
-
console.log(" auth login Authenticate (device code flow)");
|
|
296
|
-
console.log(" auth logout Remove credentials");
|
|
297
|
-
console.log(" auth status Show account and usage");
|
|
298
|
-
console.log(" activate free Claim free activation license");
|
|
299
|
-
console.log(" activate status Show activation and bound-project status");
|
|
300
|
-
console.log(" redeem <CODE> Redeem a plan upgrade code");
|
|
301
|
-
console.log(" --version\n");
|
|
302
|
-
console.log("https://0dai.dev");
|
|
540
|
+
printHelp();
|
|
303
541
|
break;
|
|
304
542
|
default:
|
|
305
543
|
log(`unknown command: ${cmd}. Run '0dai --help'`);
|
package/lib/commands/audit.js
CHANGED
|
@@ -29,6 +29,18 @@ function cmdAudit(target) {
|
|
|
29
29
|
"opencode.json", ".mcp.json",
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
// Paths whose contents are synthetic test fixtures by design — they contain
|
|
33
|
+
// realistic-looking secret patterns to exercise detection logic. Skipping
|
|
34
|
+
// them avoids false-positive blocking of release pipelines.
|
|
35
|
+
const SKIP_PATH_SEGMENTS = [
|
|
36
|
+
"synthetic-tests", // ai/meta/synthetic-tests/scenarios/*.json — PII/secret detection fixtures
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function shouldSkip(filePath) {
|
|
40
|
+
const rel = path.relative(target, filePath);
|
|
41
|
+
return SKIP_PATH_SEGMENTS.some(seg => rel.split(path.sep).includes(seg));
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
// Walk a directory recursively, return all file paths
|
|
33
45
|
function walk(dir, maxDepth = 6, _depth = 0) {
|
|
34
46
|
if (_depth > maxDepth) return [];
|
|
@@ -36,6 +48,7 @@ function cmdAudit(target) {
|
|
|
36
48
|
try {
|
|
37
49
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
38
50
|
if (entry.name.startsWith(".git") || entry.name === "node_modules") continue;
|
|
51
|
+
if (SKIP_PATH_SEGMENTS.includes(entry.name)) continue;
|
|
39
52
|
const full = path.join(dir, entry.name);
|
|
40
53
|
if (entry.isDirectory()) results = results.concat(walk(full, maxDepth, _depth + 1));
|
|
41
54
|
else results.push(full);
|