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 +123 -2
- package/package.json +52 -10
- package/scripts/cadre-cli.js +390 -0
- package/scripts/cadre-core.js +9808 -0
- package/scripts/cadre-job-runner.js +2644 -0
- package/scripts/cadre-lsp-daemon.js +1209 -0
- package/scripts/cadre-lsp-review.js +1064 -0
- package/scripts/cadre-lsp-setup.js +467 -0
- package/scripts/mcp/cadre-server.js +12803 -0
- package/index.js +0 -5
package/README.md
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Cadre
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
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": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
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": "
|
|
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": "
|
|
13
|
-
"cadre-ai": "
|
|
14
|
-
"cadre-
|
|
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
|
-
"
|
|
36
|
+
"LICENSE",
|
|
18
37
|
"README.md",
|
|
19
|
-
"
|
|
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
|
+
});
|