cadre-ai 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,124 @@
1
- # cadre-ai
1
+ # Cadre
2
2
 
3
- Bootstrap package for configuring npm trusted publishing.
3
+ ![Cadre logo](../docs/public/cadre-logo.png)
4
+
5
+ **Measure twice, code once.**
6
+
7
+ Cadre is a context-driven development harness for AI coding agents. It combines
8
+ spec-first tracks, Beads-backed durable task memory, review gates, team boards,
9
+ parallel worker orchestration, and mono/polyrepo delivery into one packet-owned
10
+ workflow.
11
+
12
+ Public docs: [https://cadre-docs.pages.dev/](https://cadre-docs.pages.dev/)
13
+
14
+ ## What Cadre Provides
15
+
16
+ - **Structured work:** setup, new track, implementation, review, ship/land,
17
+ archive, release, handoff, refresh, revise, validate, flag, and formula flows.
18
+ - **Persistent memory:** Beads stores task graph, dependencies, notes, handoffs,
19
+ and resume evidence; agents access it through Cadre packets.
20
+ - **Team safety:** ownership, advisory leases, collision scans, review queues,
21
+ shared sync, and compact MCP dashboard resources.
22
+ - **Polyglot intelligence:** repo maps, dependency graphs, test impact,
23
+ workspace diagnostics, LSP setup, warm LSP review, and async job artifacts.
24
+ - **Two agent surfaces:** Claude Code and OpenAI Codex plugins are thin MCP
25
+ entrypoints. The global `cadre-mcp` runtime owns the skill contract,
26
+ protocols, references, templates, and packet tools.
27
+
28
+ ## Install
29
+
30
+ Install Beads first; setup requires the `bd` CLI to be available.
31
+
32
+ ```bash
33
+ npm install -g @beads/bd
34
+ bd --version
35
+ ```
36
+
37
+ Install Cadre from npm and let the CLI wire detected clients:
38
+
39
+ ```bash
40
+ npm install -g cadre-ai
41
+ cadre install
42
+ cadre doctor
43
+ ```
44
+
45
+ ## Use
46
+
47
+ In a target project, activate the Cadre skill and ask for the workflow you need:
48
+
49
+ ```text
50
+ $cadre
51
+ cadre-setup
52
+ cadre-newtrack "Add OAuth login"
53
+ cadre-implement
54
+ cadre-review
55
+ cadre-ship
56
+ ```
57
+
58
+ Cadre workflows are packet-owned. The agent verifies Cadre MCP, passes a
59
+ per-call `root`, and lets the runtime perform state reads/writes, Beads work,
60
+ parallel worker state, provider evidence write-back, and shared sync. Do not
61
+ maintain Cadre state by hand.
62
+
63
+ ## Setup Outputs
64
+
65
+ `cadre-setup` writes the project control plane:
66
+
67
+ - `cadre/product.json` plus generated `cadre/product.md`
68
+ - `cadre/product_guidelines.json` plus generated `cadre/product_guidelines.md`
69
+ - `cadre/tech-stack.json`
70
+ - `cadre/workflow.json` plus generated `cadre/workflow.md`
71
+ - `cadre/tracks.json` as the generated track index
72
+ - `cadre/patterns.jsonl` plus generated `cadre/patterns.md`
73
+ - `cadre/config.json`
74
+ - `cadre/beads.json`
75
+ - `cadre/styleguides/*.json` plus generated `cadre/code_styleguides/*.md`
76
+ - optional `cadre/repos.json` for polyrepo topology
77
+ - optional `cadre/lsp.json` for LSP recommendations
78
+
79
+ Setup also initializes Beads, can configure shared-sync merge attributes, and
80
+ can scaffold hosted CI checks when requested.
81
+
82
+ ## Team And Repo Modes
83
+
84
+ Cadre supports monorepos and polyrepo control repos. For teams, use shared sync
85
+ so ownership, leases, review state, blockers, and available work are visible to
86
+ everyone. Product code publication still happens through ship/land workflows;
87
+ shared sync is for the Cadre control plane.
88
+
89
+ Compact MCP resources provide bounded views for larger teams:
90
+
91
+ - team board and next actions
92
+ - review queue and handoff inbox
93
+ - quality gate and parallel worker state
94
+ - repo topology, repo map, workspace diagnostics, test impact, and LSP status
95
+ - provider action plans and async job results
96
+
97
+ ## Harness Development
98
+
99
+ This repository is the Cadre harness/package repo. Runtime sources live in
100
+ `src/`, master skill/protocol sources live in `skills/cadre/`, references live
101
+ in `scripts/agent-refs/`, and templates live in `templates/`.
102
+
103
+ Run package commands from the repository root:
104
+
105
+ ```bash
106
+ pnpm --filter cadre-ai generate
107
+ pnpm --filter cadre-ai check
108
+ ```
109
+
110
+ Generated plugin bundles under `.agents/`, `.claude/`, and `plugins/` are
111
+ rebuilt from master sources. They contain only platform manifests, MCP config,
112
+ and `SKILL.md`; the embedded MCP runtime is built under `scripts/`.
113
+
114
+ Public documentation lives in the repo-root `docs/` Next.js app. Markdown page
115
+ source is in `docs/content/`:
116
+
117
+ - [Documentation Home](../docs/content/overview.md)
118
+ - [Getting Started](../docs/content/getting-started.md)
119
+ - [How Cadre Works](../docs/content/how-cadre-works.md)
120
+ - [Workflows](../docs/content/workflows.md)
121
+ - [Architecture](../docs/content/architecture.md)
122
+ - [Team And Polyrepo](../docs/content/team-and-polyrepo.md)
123
+ - [Parallel Execution](../docs/content/parallel-execution.md)
124
+ - [Troubleshooting](../docs/content/troubleshooting.md)
package/package.json CHANGED
@@ -1,21 +1,63 @@
1
1
  {
2
2
  "name": "cadre-ai",
3
- "version": "0.0.0",
4
- "description": "Bootstrap package for configuring npm trusted publishing. Use a real release version for installation.",
5
- "license": "Apache-2.0",
3
+ "version": "1.0.0",
4
+ "description": "MCP-first Cadre workflows for context-driven development.",
5
+ "author": {
6
+ "name": "Vishal Kumar",
7
+ "url": "https://github.com/vishal-kr-barnwal"
8
+ },
9
+ "homepage": "https://cadre-docs.pages.dev/",
6
10
  "repository": {
7
11
  "type": "git",
8
- "url": "git+https://github.com/vishal-kr-barnwal/cadre.git",
12
+ "url": "https://github.com/vishal-kr-barnwal/cadre.git",
9
13
  "directory": "harness"
10
14
  },
15
+ "license": "Apache-2.0",
16
+ "keywords": [
17
+ "cadre",
18
+ "cadre-ai",
19
+ "context-driven-development",
20
+ "mcp",
21
+ "codex",
22
+ "claude",
23
+ "beads"
24
+ ],
25
+ "type": "commonjs",
26
+ "packageManager": "pnpm@11.4.0",
27
+ "main": "scripts/cadre-core.js",
11
28
  "bin": {
12
- "cadre": "index.js",
13
- "cadre-ai": "index.js",
14
- "cadre-mcp": "index.js"
29
+ "cadre": "scripts/cadre-cli.js",
30
+ "cadre-ai": "scripts/cadre-cli.js",
31
+ "cadre-lsp-setup": "scripts/cadre-lsp-setup.js",
32
+ "cadre-lsp-review": "scripts/cadre-lsp-review.js",
33
+ "cadre-mcp": "scripts/mcp/cadre-server.js"
15
34
  },
16
35
  "files": [
17
- "index.js",
36
+ "LICENSE",
18
37
  "README.md",
19
- "LICENSE"
20
- ]
38
+ "scripts/cadre-cli.js",
39
+ "scripts/cadre-core.js",
40
+ "scripts/cadre-job-runner.js",
41
+ "scripts/cadre-lsp-daemon.js",
42
+ "scripts/cadre-lsp-review.js",
43
+ "scripts/cadre-lsp-setup.js",
44
+ "scripts/mcp/cadre-server.js"
45
+ ],
46
+ "scripts": {
47
+ "build": "node scripts/build-runtime.mjs",
48
+ "build:runtime": "node scripts/build-runtime.mjs",
49
+ "generate": "bash scripts/generate-skills.sh",
50
+ "check": "pnpm check:typecheck && pnpm check:runtime && pnpm check:generated && pnpm check:test",
51
+ "check:typecheck": "pnpm typecheck",
52
+ "check:runtime": "pnpm build:runtime",
53
+ "check:generated": "bash scripts/generate-skills.sh --check",
54
+ "check:test": "pnpm test",
55
+ "test": "node --test scripts/source-architecture.test.js scripts/cadre-core.test.js scripts/cadre-cli.test.js scripts/mcp/cadre-server.test.js scripts/protocol-packet-only.test.js && node scripts/cadre-team-scale-sim.js",
56
+ "typecheck": "tsc --noEmit"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^24.0.4",
60
+ "esbuild": "^0.25.5",
61
+ "typescript": "^5.8.3"
62
+ }
21
63
  }
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+ // AUTO-GENERATED by pnpm build from src/*.ts -- do not edit by hand.
3
+ // Edit TypeScript sources under src/ instead.
4
+ const __CADRE_SKILL_SHIM__ = "---\nname: cadre\ndescription: |\n Context-driven development methodology for organized, spec-first coding. Use when:\n - Project has a `cadre/` directory\n - User mentions specs, plans, tracks, or context-driven development\n - Files like `cadre/tracks.json`, `cadre/product.json`, or `cadre/workflow.json` exist\n - User asks about project status, implementation progress, or track management\n - User wants to organize development work with TDD practices\n - User asks for a `cadre-*` workflow (setup, newtrack, implement, status, revert, validate, flag, revise, review, ship, land, archive, release, handoff, refresh, formula, artifacts)\n - User mentions documentation is outdated or wants to sync context with codebase changes\n - Project is a polyrepo control repo (`cadre/repos.json` with mode \"polyrepo\") spanning git-submodule product repos\n\n Interoperable across Claude Code and OpenAI Codex.\n Integrates with Beads for persistent task memory across sessions.\n---\n\n# Cadre Skill Shim\n\nCadre MCP is required for every Cadre workflow. Before acting, verify the MCP\nruntime with `cadre_project` using `{\"action\":\"ping\"}`. If Cadre MCP tools or\nresources are unavailable, halt and ask the user to install, enable, or restart\nthe Cadre plugin.\n\nLoad `cadre://skill-contract` for the authoritative `cadre.skill.v1` contract.\nUse `cadre://workflow-protocols` to discover workflow protocol resources, then\nload `cadre://workflow-protocol?workflow=<name>` for the active workflow.\nReferences and template inventory are also MCP-served through Cadre resources.\n";
5
+ "use strict";
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // src/cli/install.ts
30
+ var import_node_fs = __toESM(require("node:fs"));
31
+ var import_node_os = __toESM(require("node:os"));
32
+ var import_node_path = __toESM(require("node:path"));
33
+ var import_node_child_process = require("node:child_process");
34
+ var PACKAGE_PLUGIN_NAME = "cadre";
35
+ var PACKAGE_DISPLAY_NAME = "Cadre";
36
+ function usage() {
37
+ return [
38
+ "Cadre CLI",
39
+ "",
40
+ "Usage:",
41
+ " cadre install [--target codex|claude|all] [--scope user|project|local] [--dry-run] [--check] [--force] [--yes]",
42
+ " cadre doctor",
43
+ " cadre help"
44
+ ].join("\n");
45
+ }
46
+ function parseInstall(argv) {
47
+ const parsed = {
48
+ target: "auto",
49
+ scope: "user",
50
+ dryRun: false,
51
+ check: false,
52
+ force: false,
53
+ yes: false,
54
+ cadreHome: process.env.CADRE_HOME || import_node_path.default.join(import_node_os.default.homedir(), ".cadre")
55
+ };
56
+ for (let index = 0; index < argv.length; index += 1) {
57
+ const arg = argv[index];
58
+ if (arg === "--dry-run") parsed.dryRun = true;
59
+ else if (arg === "--check") parsed.check = true;
60
+ else if (arg === "--force") parsed.force = true;
61
+ else if (arg === "--yes" || arg === "-y") parsed.yes = true;
62
+ else if (arg === "--target") {
63
+ const value = argv[index + 1];
64
+ if (value !== "codex" && value !== "claude" && value !== "all") throw new Error("--target must be codex, claude, or all");
65
+ parsed.target = value;
66
+ index += 1;
67
+ } else if (arg === "--scope") {
68
+ const value = argv[index + 1];
69
+ if (value !== "user" && value !== "project" && value !== "local") throw new Error("--scope must be user, project, or local");
70
+ parsed.scope = value;
71
+ index += 1;
72
+ } else if (arg === "--home") {
73
+ const value = argv[index + 1];
74
+ if (!value) throw new Error("--home requires a path");
75
+ parsed.cadreHome = import_node_path.default.resolve(value);
76
+ index += 1;
77
+ } else {
78
+ throw new Error(`Unknown install option: ${arg}`);
79
+ }
80
+ }
81
+ return parsed;
82
+ }
83
+ function runtimePaths() {
84
+ const runtimeRoot = import_node_path.default.resolve(__dirname, "..");
85
+ return {
86
+ runtimeRoot,
87
+ nodePath: process.execPath,
88
+ mcpServer: import_node_path.default.join(runtimeRoot, "scripts", "mcp", "cadre-server.js")
89
+ };
90
+ }
91
+ function commandExists(command) {
92
+ const result = (0, import_node_child_process.spawnSync)(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [command] : ["-v", command], {
93
+ encoding: "utf8",
94
+ shell: process.platform !== "win32",
95
+ stdio: "ignore"
96
+ });
97
+ return result.status === 0;
98
+ }
99
+ function selectedTargets(options) {
100
+ if (options.target === "codex" || options.target === "claude") return [options.target];
101
+ if (options.target === "all") return ["codex", "claude"];
102
+ return ["codex", "claude"].filter((target) => commandExists(target));
103
+ }
104
+ function targetPaths(home, target) {
105
+ const pluginRoot = import_node_path.default.join(home, "plugins", target, PACKAGE_PLUGIN_NAME);
106
+ const marketplaceRoot = import_node_path.default.join(home, "marketplaces", target);
107
+ return {
108
+ pluginRoot,
109
+ marketplaceRoot,
110
+ marketplaceFile: target === "codex" ? import_node_path.default.join(marketplaceRoot, ".agents", "plugins", "marketplace.json") : import_node_path.default.join(marketplaceRoot, ".claude-plugin", "marketplace.json")
111
+ };
112
+ }
113
+ function readPackageMetadata(runtimeRoot) {
114
+ try {
115
+ const json = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(runtimeRoot, "package.json"), "utf8"));
116
+ return {
117
+ version: typeof json.version === "string" ? json.version : "0.0.0",
118
+ description: typeof json.description === "string" ? json.description : "MCP-first Cadre workflows.",
119
+ homepage: typeof json.homepage === "string" ? json.homepage : "https://cadre-docs.pages.dev/",
120
+ repository: typeof json.repository === "string" ? json.repository : "https://github.com/vishal-kr-barnwal/cadre",
121
+ license: typeof json.license === "string" ? json.license : "Apache-2.0"
122
+ };
123
+ } catch {
124
+ return {
125
+ version: "0.0.0",
126
+ description: "MCP-first Cadre workflows.",
127
+ homepage: "https://cadre-docs.pages.dev/",
128
+ repository: "https://github.com/vishal-kr-barnwal/cadre",
129
+ license: "Apache-2.0"
130
+ };
131
+ }
132
+ }
133
+ function pluginManifest(target, runtime) {
134
+ const metadata = readPackageMetadata(runtime.runtimeRoot);
135
+ const base = {
136
+ name: PACKAGE_PLUGIN_NAME,
137
+ version: metadata.version,
138
+ description: metadata.description,
139
+ author: { name: "Vishal Kumar", url: "https://github.com/vishal-kr-barnwal" },
140
+ homepage: metadata.homepage,
141
+ repository: metadata.repository,
142
+ license: metadata.license,
143
+ keywords: ["cadre", "context-driven-development", "skills", "beads", "mcp"],
144
+ skills: "./skills/"
145
+ };
146
+ if (target === "claude") {
147
+ return { ...base, displayName: PACKAGE_DISPLAY_NAME, mcpServers: "./mcp-config.json" };
148
+ }
149
+ return {
150
+ ...base,
151
+ mcpServers: "./.mcp.json",
152
+ interface: {
153
+ displayName: PACKAGE_DISPLAY_NAME,
154
+ shortDescription: "MCP-first planning, tracks, reviews, and packet tools.",
155
+ longDescription: "Cadre packages context-driven development workflows for Codex through one global MCP runtime.",
156
+ developerName: "Vishal Kumar",
157
+ category: "Productivity",
158
+ capabilities: ["Read", "Write", "Interactive"],
159
+ defaultPrompt: ["Set up this repo with Cadre.", "Show Cadre team status.", "Review the current Cadre track."],
160
+ brandColor: "#10A37F"
161
+ }
162
+ };
163
+ }
164
+ function mcpConfig(runtime) {
165
+ return {
166
+ mcpServers: {
167
+ cadre: {
168
+ command: runtime.nodePath,
169
+ args: [runtime.mcpServer],
170
+ cwd: runtime.runtimeRoot
171
+ }
172
+ }
173
+ };
174
+ }
175
+ function marketplace(target, pluginRoot, runtime) {
176
+ const metadata = readPackageMetadata(runtime.runtimeRoot);
177
+ if (target === "codex") {
178
+ return {
179
+ name: PACKAGE_PLUGIN_NAME,
180
+ interface: { displayName: PACKAGE_DISPLAY_NAME },
181
+ plugins: [{
182
+ name: PACKAGE_PLUGIN_NAME,
183
+ source: { source: "local", path: pluginRoot },
184
+ policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
185
+ category: "Productivity"
186
+ }]
187
+ };
188
+ }
189
+ return {
190
+ name: PACKAGE_PLUGIN_NAME,
191
+ owner: { name: "Vishal Kumar" },
192
+ description: "Cadre MCP-first workflows for Claude Code.",
193
+ plugins: [{
194
+ name: PACKAGE_PLUGIN_NAME,
195
+ source: pluginRoot,
196
+ description: metadata.description,
197
+ version: metadata.version,
198
+ author: { name: "Vishal Kumar" },
199
+ category: "productivity",
200
+ tags: ["cadre", "skills", "mcp", "context-driven-development"]
201
+ }]
202
+ };
203
+ }
204
+ function writeJson(file, value) {
205
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
206
+ import_node_fs.default.writeFileSync(file, `${JSON.stringify(value, null, 2)}
207
+ `);
208
+ }
209
+ function writeText(file, text) {
210
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
211
+ import_node_fs.default.writeFileSync(file, text.endsWith("\n") ? text : `${text}
212
+ `);
213
+ }
214
+ function writeThinPlugin(target, paths, runtime, skillShim) {
215
+ import_node_fs.default.rmSync(paths.pluginRoot, { recursive: true, force: true });
216
+ writeText(import_node_path.default.join(paths.pluginRoot, "skills", "cadre", "SKILL.md"), skillShim);
217
+ if (target === "codex") {
218
+ writeJson(import_node_path.default.join(paths.pluginRoot, ".codex-plugin", "plugin.json"), pluginManifest(target, runtime));
219
+ writeJson(import_node_path.default.join(paths.pluginRoot, ".mcp.json"), mcpConfig(runtime));
220
+ } else {
221
+ writeJson(import_node_path.default.join(paths.pluginRoot, ".claude-plugin", "plugin.json"), pluginManifest(target, runtime));
222
+ writeJson(import_node_path.default.join(paths.pluginRoot, "mcp-config.json"), mcpConfig(runtime));
223
+ }
224
+ writeJson(paths.marketplaceFile, marketplace(target, paths.pluginRoot, runtime));
225
+ }
226
+ function installCommands(target, paths, scope) {
227
+ if (target === "codex") {
228
+ return [
229
+ { command: "codex", args: ["plugin", "marketplace", "add", paths.marketplaceRoot] },
230
+ { command: "codex", args: ["plugin", "add", "cadre@cadre"] }
231
+ ];
232
+ }
233
+ return [
234
+ { command: "claude", args: ["plugin", "marketplace", "add", "--scope", scope, paths.marketplaceRoot] },
235
+ { command: "claude", args: ["plugin", "install", "--scope", scope, "cadre@cadre"] }
236
+ ];
237
+ }
238
+ function runCommand(plan) {
239
+ const result = (0, import_node_child_process.spawnSync)(plan.command, plan.args, { encoding: "utf8" });
240
+ return { ok: result.status === 0, status: result.status, stderr: result.stderr || "" };
241
+ }
242
+ function assertNoThinPluginPayload(paths) {
243
+ const forbidden = ["assets", "agents", "scripts"].filter((name) => import_node_fs.default.existsSync(import_node_path.default.join(paths.pluginRoot, name)));
244
+ return forbidden.map((name) => `${paths.pluginRoot}/${name}`);
245
+ }
246
+ function pingMcp(runtime) {
247
+ if (!import_node_fs.default.existsSync(runtime.mcpServer)) return { ok: false, reason: `missing MCP server: ${runtime.mcpServer}` };
248
+ const request = {
249
+ jsonrpc: "2.0",
250
+ id: 1,
251
+ method: "tools/call",
252
+ params: { name: "cadre_project", arguments: { action: "ping" } }
253
+ };
254
+ const result = (0, import_node_child_process.spawnSync)(runtime.nodePath, [runtime.mcpServer], {
255
+ cwd: runtime.runtimeRoot,
256
+ input: `${JSON.stringify(request)}
257
+ `,
258
+ encoding: "utf8",
259
+ timeout: 3e3
260
+ });
261
+ if (result.status !== 0 && !result.stdout.trim()) return { ok: false, reason: result.stderr || `MCP exited with ${result.status}` };
262
+ const line = result.stdout.split(/\r?\n/).find((entry) => entry.trim());
263
+ if (!line) return { ok: false, reason: "MCP returned no JSON-RPC response" };
264
+ try {
265
+ const parsed = JSON.parse(line);
266
+ if (parsed.error) return { ok: false, reason: parsed.error.message || "MCP returned an error" };
267
+ const text = parsed.result?.content?.[0]?.text;
268
+ const body = text ? JSON.parse(text) : null;
269
+ return body?.data?.ok === true ? { ok: true } : { ok: false, reason: "MCP ping did not return ok:true" };
270
+ } catch (error) {
271
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
272
+ }
273
+ }
274
+ function checkTarget(target, paths, runtime) {
275
+ const errors = [];
276
+ const skill = import_node_path.default.join(paths.pluginRoot, "skills", "cadre", "SKILL.md");
277
+ if (!import_node_fs.default.existsSync(skill)) errors.push(`missing ${skill}`);
278
+ const manifest = target === "codex" ? import_node_path.default.join(paths.pluginRoot, ".codex-plugin", "plugin.json") : import_node_path.default.join(paths.pluginRoot, ".claude-plugin", "plugin.json");
279
+ const mcp = target === "codex" ? import_node_path.default.join(paths.pluginRoot, ".mcp.json") : import_node_path.default.join(paths.pluginRoot, "mcp-config.json");
280
+ if (!import_node_fs.default.existsSync(manifest)) errors.push(`missing ${manifest}`);
281
+ if (!import_node_fs.default.existsSync(mcp)) errors.push(`missing ${mcp}`);
282
+ errors.push(...assertNoThinPluginPayload(paths).map((entry) => `thin plugin contains forbidden payload ${entry}`));
283
+ if (import_node_fs.default.existsSync(mcp)) {
284
+ const config = JSON.parse(import_node_fs.default.readFileSync(mcp, "utf8"));
285
+ const server = config.mcpServers?.cadre;
286
+ if (server?.command !== runtime.nodePath) errors.push(`${mcp} does not point at the current Node runtime`);
287
+ if (server?.args?.[0] !== runtime.mcpServer) errors.push(`${mcp} does not point at ${runtime.mcpServer}`);
288
+ if (server?.cwd !== runtime.runtimeRoot) errors.push(`${mcp} has wrong cwd`);
289
+ }
290
+ return errors;
291
+ }
292
+ function printPlan(target, paths, commands) {
293
+ process.stdout.write(`Cadre ${target} plugin: ${paths.pluginRoot}
294
+ `);
295
+ process.stdout.write(`Cadre ${target} marketplace: ${paths.marketplaceRoot}
296
+ `);
297
+ for (const command of commands) process.stdout.write(`Would run: ${command.command} ${command.args.join(" ")}
298
+ `);
299
+ }
300
+ function runInstall(argv, context) {
301
+ const options = parseInstall(argv);
302
+ const runtime = runtimePaths();
303
+ const targets = selectedTargets(options);
304
+ if (targets.length === 0) {
305
+ process.stderr.write("No supported client detected. Install Codex or Claude, or pass --target codex|claude.\n");
306
+ return 1;
307
+ }
308
+ const ping = pingMcp(runtime);
309
+ if (!ping.ok) {
310
+ process.stderr.write(`Cadre MCP check failed: ${ping.reason}
311
+ `);
312
+ return 1;
313
+ }
314
+ let ok = true;
315
+ for (const target of targets) {
316
+ const paths = targetPaths(options.cadreHome, target);
317
+ const commands = installCommands(target, paths, options.scope);
318
+ if (options.dryRun) {
319
+ printPlan(target, paths, commands);
320
+ continue;
321
+ }
322
+ if (!options.check) writeThinPlugin(target, paths, runtime, context.skillShim);
323
+ const errors = checkTarget(target, paths, runtime);
324
+ if (errors.length > 0) {
325
+ ok = false;
326
+ for (const error of errors) process.stderr.write(`${error}
327
+ `);
328
+ continue;
329
+ }
330
+ if (options.check) {
331
+ process.stdout.write(`Cadre ${target} plugin is installed and points at ${runtime.mcpServer}
332
+ `);
333
+ continue;
334
+ }
335
+ if (!commandExists(target)) {
336
+ ok = false;
337
+ process.stderr.write(`${target} command not found; plugin files were written but native registration was skipped.
338
+ `);
339
+ continue;
340
+ }
341
+ for (const command of commands) {
342
+ const result = runCommand(command);
343
+ if (!result.ok) {
344
+ ok = false;
345
+ process.stderr.write(`${command.command} ${command.args.join(" ")} failed: ${result.stderr}
346
+ `);
347
+ }
348
+ }
349
+ if (ok) process.stdout.write(`Installed Cadre ${target} plugin through ${paths.marketplaceRoot}
350
+ `);
351
+ }
352
+ return ok ? 0 : 1;
353
+ }
354
+ function runDoctor() {
355
+ const runtime = runtimePaths();
356
+ const ping = pingMcp(runtime);
357
+ const checks = [
358
+ `package root: ${runtime.runtimeRoot}`,
359
+ `node: ${runtime.nodePath}`,
360
+ `cadre-mcp: ${runtime.mcpServer}`,
361
+ `mcp ping: ${ping.ok ? "ok" : `failed (${ping.reason})`}`
362
+ ];
363
+ process.stdout.write(`${checks.join("\n")}
364
+ `);
365
+ return ping.ok ? 0 : 1;
366
+ }
367
+ async function runCli(argv, context) {
368
+ const command = argv[0] || "help";
369
+ let code = 0;
370
+ if (command === "install") code = runInstall(argv.slice(1), context);
371
+ else if (command === "doctor") code = runDoctor();
372
+ else if (command === "help" || command === "--help" || command === "-h") process.stdout.write(`${usage()}
373
+ `);
374
+ else {
375
+ process.stderr.write(`${usage()}
376
+ `);
377
+ code = 1;
378
+ }
379
+ if (code !== 0) process.exit(code);
380
+ }
381
+
382
+ // src/cli.ts
383
+ runCli(process.argv.slice(2), {
384
+ skillShim: typeof __CADRE_SKILL_SHIM__ === "string" ? __CADRE_SKILL_SHIM__ : ""
385
+ }).catch((error) => {
386
+ const message = error instanceof Error ? error.message : String(error);
387
+ process.stderr.write(`${message}
388
+ `);
389
+ process.exit(1);
390
+ });