contentrain 0.4.1 → 0.4.2
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/dist/{activity-DMQsYlzz.mjs → activity-CbkxcfDf.mjs} +2 -2
- package/dist/{branches-D330IxDO.mjs → branches-D2UWex1N.mjs} +2 -2
- package/dist/{cdn-build-D_d3p4OZ.mjs → cdn-build-BzC2QPNi.mjs} +2 -2
- package/dist/{cdn-init-Bhnil0F8.mjs → cdn-init-CsmqJIaQ.mjs} +2 -2
- package/dist/{connect-DiSYnxFd.mjs → connect-DJo9PSiw.mjs} +2 -2
- package/dist/demo-DcBt6fw8.mjs +80 -0
- package/dist/ide-Bg-Z8QZk.mjs +227 -0
- package/dist/index.mjs +8 -6
- package/dist/{init-Q9B0iYtu.mjs → init-DFyQ-l-E.mjs} +22 -152
- package/dist/{login-wRBvHDCi.mjs → login-DCXkOrQV.mjs} +2 -2
- package/dist/{logout-xPTGu6vx.mjs → logout-nPt2UAeQ.mjs} +1 -1
- package/dist/{resolve-context-BwcikcF0.mjs → resolve-context-Cg6JoVZt.mjs} +1 -1
- package/dist/{serve-DL4LhWxl.mjs → serve-CNeFIQ1i.mjs} +30 -11
- package/dist/setup-DJy-jNEU.mjs +108 -0
- package/dist/skills-D-tFY7WA.mjs +92 -0
- package/dist/{status-UvjbO4eS.mjs → status-Cyv-jo1y.mjs} +2 -2
- package/dist/studio-CJUGiIMd.mjs +24 -0
- package/dist/{submissions-Dw0ChFDi.mjs → submissions-bIG-MWvv.mjs} +2 -2
- package/dist/{usage-BqriRRqE.mjs → usage-B2xuc2DT.mjs} +2 -2
- package/dist/{webhooks-B1OhcCtZ.mjs → webhooks-Dq3zFLP7.mjs} +2 -2
- package/dist/{whoami-CWvTYzum.mjs → whoami-Ub5RznbU.mjs} +1 -1
- package/package.json +5 -5
- package/dist/studio-B8lF0fQx.mjs +0 -24
- /package/dist/{client-CLV4XoJz.mjs → client-C400qHcL.mjs} +0 -0
- /package/dist/{oauth-server-1p6y1H30.mjs → oauth-server-Beppljfz.mjs} +0 -0
- /package/dist/{server-WLDOAZa4.mjs → server-DTdmutjb.mjs} +0 -0
- /package/dist/{status-BmQZGdZX.mjs → status-CoHgz6HN.mjs} +0 -0
- /package/dist/{validate-DnVamp3p.mjs → validate-B10L7RL7.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/activity.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/branches.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/cdn-build.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, outro, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/cdn-init.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { c as saveDefaults, n as resolveStudioClient } from "./client-
|
|
3
|
-
import { n as openBrowser, t as startOAuthServer } from "./oauth-server-
|
|
2
|
+
import { c as saveDefaults, n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { n as openBrowser, t as startOAuthServer } from "./oauth-server-Beppljfz.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
6
6
|
import { simpleGit } from "simple-git";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { simpleGit } from "simple-git";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { writeModel } from "@contentrain/mcp/core/model-manager";
|
|
5
|
+
import { ensureDir, writeJson } from "@contentrain/mcp/util/fs";
|
|
6
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
7
|
+
import { getTemplate } from "@contentrain/mcp/templates";
|
|
8
|
+
//#region src/utils/demo.ts
|
|
9
|
+
/**
|
|
10
|
+
* Creates a temporary demo project with a blog template.
|
|
11
|
+
* Returns the project root path.
|
|
12
|
+
*/
|
|
13
|
+
async function createDemoProject() {
|
|
14
|
+
const dir = await mkdtemp(join(tmpdir(), "contentrain-demo-"));
|
|
15
|
+
const git = simpleGit(dir);
|
|
16
|
+
await git.init();
|
|
17
|
+
await git.addConfig("user.email", "demo@contentrain.io");
|
|
18
|
+
await git.addConfig("user.name", "Contentrain Demo");
|
|
19
|
+
const crDir = join(dir, ".contentrain");
|
|
20
|
+
await Promise.all([
|
|
21
|
+
"models",
|
|
22
|
+
"content",
|
|
23
|
+
"meta"
|
|
24
|
+
].map(async (sub) => {
|
|
25
|
+
await ensureDir(join(crDir, sub));
|
|
26
|
+
await writeFile(join(crDir, sub, ".gitkeep"), "", "utf-8");
|
|
27
|
+
}));
|
|
28
|
+
await writeJson(join(crDir, "config.json"), {
|
|
29
|
+
version: 1,
|
|
30
|
+
stack: "other",
|
|
31
|
+
workflow: "auto-merge",
|
|
32
|
+
locales: {
|
|
33
|
+
default: "en",
|
|
34
|
+
supported: ["en"]
|
|
35
|
+
},
|
|
36
|
+
domains: [
|
|
37
|
+
"marketing",
|
|
38
|
+
"blog",
|
|
39
|
+
"system"
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
await writeJson(join(crDir, "vocabulary.json"), {
|
|
43
|
+
version: 1,
|
|
44
|
+
terms: {}
|
|
45
|
+
});
|
|
46
|
+
await writeJson(join(crDir, "context.json"), {
|
|
47
|
+
version: "1",
|
|
48
|
+
lastOperation: {
|
|
49
|
+
tool: "contentrain_init",
|
|
50
|
+
model: "",
|
|
51
|
+
locale: "en",
|
|
52
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
53
|
+
source: "demo"
|
|
54
|
+
},
|
|
55
|
+
stats: {
|
|
56
|
+
models: 0,
|
|
57
|
+
entries: 0,
|
|
58
|
+
locales: ["en"],
|
|
59
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString()
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const tmpl = getTemplate("blog");
|
|
63
|
+
if (tmpl) for (const model of tmpl.models) await writeModel(dir, model);
|
|
64
|
+
await git.add(".");
|
|
65
|
+
await git.commit("[contentrain] demo project");
|
|
66
|
+
return dir;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Clean up a demo project directory.
|
|
70
|
+
*/
|
|
71
|
+
async function cleanupDemoProject(dir) {
|
|
72
|
+
try {
|
|
73
|
+
await rm(dir, {
|
|
74
|
+
recursive: true,
|
|
75
|
+
force: true
|
|
76
|
+
});
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { cleanupDemoProject, createDemoProject };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { simpleGit } from "simple-git";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ensureDir, pathExists } from "@contentrain/mcp/util/fs";
|
|
4
|
+
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
//#region src/utils/ide.ts
|
|
6
|
+
const AGENT_SKILL_NAMES = [
|
|
7
|
+
"contentrain",
|
|
8
|
+
"contentrain-normalize",
|
|
9
|
+
"contentrain-quality",
|
|
10
|
+
"contentrain-sdk",
|
|
11
|
+
"contentrain-content",
|
|
12
|
+
"contentrain-model",
|
|
13
|
+
"contentrain-init",
|
|
14
|
+
"contentrain-bulk",
|
|
15
|
+
"contentrain-validate-fix",
|
|
16
|
+
"contentrain-review",
|
|
17
|
+
"contentrain-translate",
|
|
18
|
+
"contentrain-generate",
|
|
19
|
+
"contentrain-serve",
|
|
20
|
+
"contentrain-diff",
|
|
21
|
+
"contentrain-doctor"
|
|
22
|
+
];
|
|
23
|
+
const OLD_RULE_FILES = [
|
|
24
|
+
"contentrain-content-quality.md",
|
|
25
|
+
"contentrain-seo-rules.md",
|
|
26
|
+
"contentrain-i18n-quality.md",
|
|
27
|
+
"contentrain-accessibility-rules.md",
|
|
28
|
+
"contentrain-security-rules.md",
|
|
29
|
+
"contentrain-media-rules.md",
|
|
30
|
+
"contentrain-content-conventions.md",
|
|
31
|
+
"contentrain-schema-rules.md",
|
|
32
|
+
"contentrain-mcp-usage.md",
|
|
33
|
+
"contentrain-workflow-rules.md",
|
|
34
|
+
"contentrain-normalize-rules.md"
|
|
35
|
+
];
|
|
36
|
+
const IDE_CONFIGS = {
|
|
37
|
+
"claude-code": {
|
|
38
|
+
name: "Claude Code",
|
|
39
|
+
rulesDir: ".claude/rules",
|
|
40
|
+
skillsDir: ".claude/skills",
|
|
41
|
+
guardrailsFileName: "contentrain-essentials.md"
|
|
42
|
+
},
|
|
43
|
+
cursor: {
|
|
44
|
+
name: "Cursor",
|
|
45
|
+
rulesDir: ".cursor/rules",
|
|
46
|
+
skillsDir: ".cursor/skills",
|
|
47
|
+
guardrailsFileName: "contentrain-essentials.mdc",
|
|
48
|
+
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\nalwaysApply: true\n---\n\n"
|
|
49
|
+
},
|
|
50
|
+
windsurf: {
|
|
51
|
+
name: "Windsurf",
|
|
52
|
+
rulesDir: ".windsurf/rules",
|
|
53
|
+
skillsDir: ".windsurf/skills",
|
|
54
|
+
guardrailsFileName: "contentrain-essentials.md",
|
|
55
|
+
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\ntrigger: always_on\n---\n\n"
|
|
56
|
+
},
|
|
57
|
+
copilot: {
|
|
58
|
+
name: "GitHub Copilot",
|
|
59
|
+
rulesDir: ".github",
|
|
60
|
+
skillsDir: ".agents/skills",
|
|
61
|
+
guardrailsFileName: "copilot-instructions.md"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const MCP_CONFIGS = {
|
|
65
|
+
"claude-code": ".mcp.json",
|
|
66
|
+
cursor: ".cursor/mcp.json",
|
|
67
|
+
windsurf: ".windsurf/mcp.json",
|
|
68
|
+
vscode: ".vscode/mcp.json",
|
|
69
|
+
copilot: ".vscode/mcp.json"
|
|
70
|
+
};
|
|
71
|
+
const STANDARD_MCP_CONFIG = { mcpServers: { contentrain: {
|
|
72
|
+
command: "npx",
|
|
73
|
+
args: [
|
|
74
|
+
"contentrain",
|
|
75
|
+
"serve",
|
|
76
|
+
"--stdio"
|
|
77
|
+
]
|
|
78
|
+
} } };
|
|
79
|
+
async function detectIdes(projectRoot) {
|
|
80
|
+
const ides = [];
|
|
81
|
+
if (await pathExists(join(projectRoot, "CLAUDE.md")) || await pathExists(join(projectRoot, ".claude"))) ides.push("claude-code");
|
|
82
|
+
if (await pathExists(join(projectRoot, ".cursor"))) ides.push("cursor");
|
|
83
|
+
if (await pathExists(join(projectRoot, ".windsurf"))) ides.push("windsurf");
|
|
84
|
+
if (await pathExists(join(projectRoot, ".github"))) ides.push("copilot");
|
|
85
|
+
return ides;
|
|
86
|
+
}
|
|
87
|
+
async function writeMcpConfig(projectRoot, agent) {
|
|
88
|
+
const relativePath = MCP_CONFIGS[agent];
|
|
89
|
+
if (!relativePath) return {
|
|
90
|
+
written: false,
|
|
91
|
+
path: "",
|
|
92
|
+
skipped: `Unknown agent: "${agent}"`
|
|
93
|
+
};
|
|
94
|
+
const configPath = join(projectRoot, relativePath);
|
|
95
|
+
if (await pathExists(configPath)) try {
|
|
96
|
+
const existing = JSON.parse(await readFile(configPath, "utf-8"));
|
|
97
|
+
if (existing?.mcpServers?.contentrain) return {
|
|
98
|
+
written: false,
|
|
99
|
+
path: relativePath,
|
|
100
|
+
skipped: "Already configured"
|
|
101
|
+
};
|
|
102
|
+
existing.mcpServers = {
|
|
103
|
+
...existing.mcpServers,
|
|
104
|
+
...STANDARD_MCP_CONFIG.mcpServers
|
|
105
|
+
};
|
|
106
|
+
await ensureDir(join(configPath, ".."));
|
|
107
|
+
await writeFile(configPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
108
|
+
return {
|
|
109
|
+
written: true,
|
|
110
|
+
path: relativePath
|
|
111
|
+
};
|
|
112
|
+
} catch {}
|
|
113
|
+
await ensureDir(join(configPath, ".."));
|
|
114
|
+
await writeFile(configPath, JSON.stringify(STANDARD_MCP_CONFIG, null, 2) + "\n", "utf-8");
|
|
115
|
+
return {
|
|
116
|
+
written: true,
|
|
117
|
+
path: relativePath
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile, forceUpdate = false) {
|
|
121
|
+
let installed = 0;
|
|
122
|
+
let updated = 0;
|
|
123
|
+
const rulesDir = join(projectRoot, ide.rulesDir);
|
|
124
|
+
const git = simpleGit(projectRoot);
|
|
125
|
+
const filesToAdd = [];
|
|
126
|
+
await ensureDir(rulesDir);
|
|
127
|
+
const guardrailsDest = join(rulesDir, ide.guardrailsFileName);
|
|
128
|
+
try {
|
|
129
|
+
let content = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
|
|
130
|
+
if (ide.guardrailsFrontmatter) content = ide.guardrailsFrontmatter + content;
|
|
131
|
+
if (await pathExists(guardrailsDest)) {
|
|
132
|
+
if (forceUpdate) {
|
|
133
|
+
await writeFile(guardrailsDest, content, "utf-8");
|
|
134
|
+
updated++;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
if (ide.name === "GitHub Copilot" && await pathExists(guardrailsDest)) {
|
|
138
|
+
if (!(await readFile(guardrailsDest, "utf-8")).includes("# Contentrain")) await appendFile(guardrailsDest, `\n\n${content}`);
|
|
139
|
+
} else await writeFile(guardrailsDest, content, "utf-8");
|
|
140
|
+
installed++;
|
|
141
|
+
}
|
|
142
|
+
filesToAdd.push(guardrailsDest);
|
|
143
|
+
} catch {}
|
|
144
|
+
for (const oldFile of OLD_RULE_FILES) {
|
|
145
|
+
const oldPath = join(rulesDir, oldFile);
|
|
146
|
+
if (await pathExists(oldPath)) {
|
|
147
|
+
const { unlink } = await import("node:fs/promises");
|
|
148
|
+
await unlink(oldPath);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (resolveSkillFile) {
|
|
152
|
+
const skillsDir = join(projectRoot, ide.skillsDir);
|
|
153
|
+
await ensureDir(skillsDir);
|
|
154
|
+
for (const skillName of AGENT_SKILL_NAMES) {
|
|
155
|
+
const skillDir = join(skillsDir, skillName);
|
|
156
|
+
const skillMd = join(skillDir, "SKILL.md");
|
|
157
|
+
try {
|
|
158
|
+
const src = resolveSkillFile(`skills/${skillName}/SKILL.md`);
|
|
159
|
+
const srcContent = await readFile(src, "utf-8");
|
|
160
|
+
await ensureDir(skillDir);
|
|
161
|
+
if (await pathExists(skillMd)) {
|
|
162
|
+
if (forceUpdate) {
|
|
163
|
+
await writeFile(skillMd, srcContent, "utf-8");
|
|
164
|
+
updated++;
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
await writeFile(skillMd, srcContent, "utf-8");
|
|
168
|
+
installed++;
|
|
169
|
+
}
|
|
170
|
+
const { readdirSync } = await import("node:fs");
|
|
171
|
+
try {
|
|
172
|
+
const refsDir = join(src, "..", "references");
|
|
173
|
+
const refs = readdirSync(refsDir);
|
|
174
|
+
if (refs.length > 0) {
|
|
175
|
+
const destRefsDir = join(skillDir, "references");
|
|
176
|
+
await ensureDir(destRefsDir);
|
|
177
|
+
for (const ref of refs) {
|
|
178
|
+
const refDest = join(destRefsDir, ref);
|
|
179
|
+
if (forceUpdate || !await pathExists(refDest)) await writeFile(refDest, await readFile(join(refsDir, ref), "utf-8"), "utf-8");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
} catch {}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (filesToAdd.length > 0) {
|
|
187
|
+
await git.add(filesToAdd);
|
|
188
|
+
try {
|
|
189
|
+
await git.commit(`[contentrain] install ${ide.name} rules and skills`);
|
|
190
|
+
} catch {}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
installed,
|
|
194
|
+
updated
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function addClaudeMdReference(projectRoot) {
|
|
198
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
199
|
+
const marker = "# Contentrain AI Rules";
|
|
200
|
+
const reference = [
|
|
201
|
+
"# Contentrain AI Rules",
|
|
202
|
+
"",
|
|
203
|
+
"This project uses [Contentrain](https://ai.contentrain.io) for AI-driven content management.",
|
|
204
|
+
"",
|
|
205
|
+
"- **Rules** are in `.claude/rules/contentrain-essentials.md` — auto-loaded each conversation",
|
|
206
|
+
"- **Skills** are in `.claude/skills/` — loaded on demand by the agent",
|
|
207
|
+
"- **Docs:** https://ai.contentrain.io"
|
|
208
|
+
].join("\n");
|
|
209
|
+
const git = simpleGit(projectRoot);
|
|
210
|
+
if (await pathExists(claudeMdPath)) {
|
|
211
|
+
if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
|
|
212
|
+
await appendFile(claudeMdPath, `\n\n${reference}\n`);
|
|
213
|
+
await git.add(claudeMdPath);
|
|
214
|
+
try {
|
|
215
|
+
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
216
|
+
} catch {}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
|
|
220
|
+
await git.add(claudeMdPath);
|
|
221
|
+
try {
|
|
222
|
+
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
223
|
+
} catch {}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
export { detectIdes as a, addClaudeMdReference as i, IDE_CONFIGS as n, installIdeRulesAndSkills as o, MCP_CONFIGS as r, writeMcpConfig as s, AGENT_SKILL_NAMES as t };
|
package/dist/index.mjs
CHANGED
|
@@ -5,18 +5,20 @@ import { defineCommand, runMain } from "citty";
|
|
|
5
5
|
runMain(defineCommand({
|
|
6
6
|
meta: {
|
|
7
7
|
name: "contentrain",
|
|
8
|
-
version: "0.4.
|
|
8
|
+
version: "0.4.2",
|
|
9
9
|
description: "Contentrain CLI — AI content governance infrastructure"
|
|
10
10
|
},
|
|
11
11
|
subCommands: {
|
|
12
|
-
init: () => import("./init-
|
|
13
|
-
status: () => import("./status-
|
|
12
|
+
init: () => import("./init-DFyQ-l-E.mjs").then((m) => m.default),
|
|
13
|
+
status: () => import("./status-CoHgz6HN.mjs").then((m) => m.default),
|
|
14
14
|
doctor: () => import("./doctor-DyKjAIKH.mjs").then((m) => m.default),
|
|
15
|
-
validate: () => import("./validate-
|
|
16
|
-
serve: () => import("./serve-
|
|
15
|
+
validate: () => import("./validate-B10L7RL7.mjs").then((m) => m.default),
|
|
16
|
+
serve: () => import("./serve-CNeFIQ1i.mjs").then((m) => m.default),
|
|
17
17
|
generate: () => import("./generate-DrUojrk8.mjs").then((m) => m.default),
|
|
18
18
|
diff: () => import("./diff--halNUpG.mjs").then((m) => m.default),
|
|
19
|
-
|
|
19
|
+
setup: () => import("./setup-DJy-jNEU.mjs").then((m) => m.default),
|
|
20
|
+
skills: () => import("./skills-D-tFY7WA.mjs").then((m) => m.default),
|
|
21
|
+
studio: () => import("./studio-CJUGiIMd.mjs").then((m) => m.default)
|
|
20
22
|
}
|
|
21
23
|
}));
|
|
22
24
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
|
|
2
2
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
3
|
+
import { a as detectIdes, i as addClaudeMdReference, n as IDE_CONFIGS, o as installIdeRulesAndSkills, s as writeMcpConfig } from "./ide-Bg-Z8QZk.mjs";
|
|
3
4
|
import { defineCommand } from "citty";
|
|
4
5
|
import { confirm, intro, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
5
6
|
import { simpleGit } from "simple-git";
|
|
@@ -346,6 +347,7 @@ async function executeInit(projectRoot, opts) {
|
|
|
346
347
|
await tx.complete();
|
|
347
348
|
s.stop("Initialized");
|
|
348
349
|
await installRules(projectRoot);
|
|
350
|
+
await configureMcp(projectRoot);
|
|
349
351
|
} catch (error) {
|
|
350
352
|
s.stop("Failed");
|
|
351
353
|
throw error;
|
|
@@ -353,64 +355,6 @@ async function executeInit(projectRoot, opts) {
|
|
|
353
355
|
await tx.cleanup();
|
|
354
356
|
}
|
|
355
357
|
}
|
|
356
|
-
const AGENT_SKILL_NAMES = [
|
|
357
|
-
"contentrain",
|
|
358
|
-
"contentrain-normalize",
|
|
359
|
-
"contentrain-quality",
|
|
360
|
-
"contentrain-sdk",
|
|
361
|
-
"contentrain-content",
|
|
362
|
-
"contentrain-model",
|
|
363
|
-
"contentrain-init",
|
|
364
|
-
"contentrain-bulk",
|
|
365
|
-
"contentrain-validate-fix",
|
|
366
|
-
"contentrain-review",
|
|
367
|
-
"contentrain-translate",
|
|
368
|
-
"contentrain-generate",
|
|
369
|
-
"contentrain-serve",
|
|
370
|
-
"contentrain-diff",
|
|
371
|
-
"contentrain-doctor"
|
|
372
|
-
];
|
|
373
|
-
const OLD_RULE_FILES = [
|
|
374
|
-
"contentrain-content-quality.md",
|
|
375
|
-
"contentrain-seo-rules.md",
|
|
376
|
-
"contentrain-i18n-quality.md",
|
|
377
|
-
"contentrain-accessibility-rules.md",
|
|
378
|
-
"contentrain-security-rules.md",
|
|
379
|
-
"contentrain-media-rules.md",
|
|
380
|
-
"contentrain-content-conventions.md",
|
|
381
|
-
"contentrain-schema-rules.md",
|
|
382
|
-
"contentrain-mcp-usage.md",
|
|
383
|
-
"contentrain-workflow-rules.md",
|
|
384
|
-
"contentrain-normalize-rules.md"
|
|
385
|
-
];
|
|
386
|
-
const IDE_CONFIGS = {
|
|
387
|
-
"claude-code": {
|
|
388
|
-
name: "Claude Code",
|
|
389
|
-
rulesDir: ".claude/rules",
|
|
390
|
-
skillsDir: ".claude/skills",
|
|
391
|
-
guardrailsFileName: "contentrain-essentials.md"
|
|
392
|
-
},
|
|
393
|
-
cursor: {
|
|
394
|
-
name: "Cursor",
|
|
395
|
-
rulesDir: ".cursor/rules",
|
|
396
|
-
skillsDir: ".cursor/skills",
|
|
397
|
-
guardrailsFileName: "contentrain-essentials.mdc",
|
|
398
|
-
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\nalwaysApply: true\n---\n\n"
|
|
399
|
-
},
|
|
400
|
-
windsurf: {
|
|
401
|
-
name: "Windsurf",
|
|
402
|
-
rulesDir: ".windsurf/rules",
|
|
403
|
-
skillsDir: ".windsurf/skills",
|
|
404
|
-
guardrailsFileName: "contentrain-essentials.md",
|
|
405
|
-
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\ntrigger: always_on\n---\n\n"
|
|
406
|
-
},
|
|
407
|
-
copilot: {
|
|
408
|
-
name: "GitHub Copilot",
|
|
409
|
-
rulesDir: ".github",
|
|
410
|
-
skillsDir: ".agents/skills",
|
|
411
|
-
guardrailsFileName: "copilot-instructions.md"
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
358
|
async function installRules(projectRoot) {
|
|
415
359
|
try {
|
|
416
360
|
const { createRequire } = await import("node:module");
|
|
@@ -421,21 +365,13 @@ async function installRules(projectRoot) {
|
|
|
421
365
|
const requireSkills = createRequire(import.meta.url);
|
|
422
366
|
resolveSkillFile = (p) => requireSkills.resolve(`@contentrain/skills/${p}`);
|
|
423
367
|
} catch {}
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const hasCopilot = await pathExists(join(projectRoot, ".github"));
|
|
428
|
-
const noIDEDetected = !hasClaudeCode && !hasCursor && !hasWindsurf && !hasCopilot;
|
|
429
|
-
const detectedIdes = [];
|
|
430
|
-
if (hasClaudeCode) detectedIdes.push("claude-code");
|
|
431
|
-
if (hasCursor) detectedIdes.push("cursor");
|
|
432
|
-
if (hasWindsurf) detectedIdes.push("windsurf");
|
|
433
|
-
if (hasCopilot) detectedIdes.push("copilot");
|
|
434
|
-
for (const ideKey of detectedIdes) {
|
|
368
|
+
const detectedIdeKeys = await detectIdes(projectRoot);
|
|
369
|
+
const noIDEDetected = detectedIdeKeys.length === 0;
|
|
370
|
+
for (const ideKey of detectedIdeKeys) {
|
|
435
371
|
const ide = IDE_CONFIGS[ideKey];
|
|
436
372
|
await installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile);
|
|
437
373
|
}
|
|
438
|
-
if (
|
|
374
|
+
if (detectedIdeKeys.includes("claude-code")) await addClaudeMdReference(projectRoot);
|
|
439
375
|
if (noIDEDetected) {
|
|
440
376
|
const essentials = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
|
|
441
377
|
const dest = join(projectRoot, "CLAUDE.md");
|
|
@@ -448,92 +384,26 @@ async function installRules(projectRoot) {
|
|
|
448
384
|
}
|
|
449
385
|
} catch {}
|
|
450
386
|
}
|
|
451
|
-
async function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (!(await readFile(guardrailsDest, "utf-8")).includes("# Contentrain")) await appendFile(guardrailsDest, `\n\n${content}`);
|
|
463
|
-
} else await writeFile(guardrailsDest, content, "utf-8");
|
|
464
|
-
filesToAdd.push(guardrailsDest);
|
|
465
|
-
} catch {}
|
|
466
|
-
for (const oldFile of OLD_RULE_FILES) {
|
|
467
|
-
const oldPath = join(rulesDir, oldFile);
|
|
468
|
-
if (await pathExists(oldPath)) {
|
|
469
|
-
const { unlink } = await import("node:fs/promises");
|
|
470
|
-
await unlink(oldPath);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
if (resolveSkillFile) {
|
|
474
|
-
await ensureDir(skillsDir);
|
|
475
|
-
for (const skillName of AGENT_SKILL_NAMES) {
|
|
476
|
-
const skillDir = join(skillsDir, skillName);
|
|
477
|
-
const skillMd = join(skillDir, "SKILL.md");
|
|
478
|
-
if (await pathExists(skillMd)) continue;
|
|
479
|
-
try {
|
|
480
|
-
const src = resolveSkillFile(`skills/${skillName}/SKILL.md`);
|
|
481
|
-
await ensureDir(skillDir);
|
|
482
|
-
await writeFile(skillMd, await readFile(src, "utf-8"), "utf-8");
|
|
483
|
-
filesToAdd.push(skillMd);
|
|
484
|
-
const { readdirSync } = await import("node:fs");
|
|
485
|
-
try {
|
|
486
|
-
const refsDir = join(resolveSkillFile(`skills/${skillName}/SKILL.md`), "..", "references");
|
|
487
|
-
const refs = readdirSync(refsDir);
|
|
488
|
-
if (refs.length > 0) {
|
|
489
|
-
const destRefsDir = join(skillDir, "references");
|
|
490
|
-
await ensureDir(destRefsDir);
|
|
491
|
-
for (const ref of refs) {
|
|
492
|
-
const refSrc = join(refsDir, ref);
|
|
493
|
-
const refDest = join(destRefsDir, ref);
|
|
494
|
-
await writeFile(refDest, await readFile(refSrc, "utf-8"), "utf-8");
|
|
495
|
-
filesToAdd.push(refDest);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
} catch {}
|
|
499
|
-
} catch {}
|
|
387
|
+
async function configureMcp(projectRoot) {
|
|
388
|
+
try {
|
|
389
|
+
const detectedIdeKeys = await detectIdes(projectRoot);
|
|
390
|
+
if (detectedIdeKeys.length === 0) detectedIdeKeys.push("claude-code");
|
|
391
|
+
const writtenPaths = [];
|
|
392
|
+
for (const ideKey of detectedIdeKeys) {
|
|
393
|
+
const result = await writeMcpConfig(projectRoot, ideKey);
|
|
394
|
+
if (result.written) {
|
|
395
|
+
log.info(`MCP configured: ${pc.cyan(result.path)}`);
|
|
396
|
+
writtenPaths.push(join(projectRoot, result.path));
|
|
397
|
+
}
|
|
500
398
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
try {
|
|
505
|
-
await git.commit(`[contentrain] install ${ide.name} rules and skills`);
|
|
506
|
-
} catch {}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
async function addClaudeMdReference(projectRoot) {
|
|
510
|
-
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
511
|
-
const marker = "# Contentrain AI Rules";
|
|
512
|
-
const reference = [
|
|
513
|
-
"# Contentrain AI Rules",
|
|
514
|
-
"",
|
|
515
|
-
"This project uses [Contentrain](https://ai.contentrain.io) for AI-driven content management.",
|
|
516
|
-
"",
|
|
517
|
-
"- **Rules** are in `.claude/rules/contentrain-essentials.md` — auto-loaded each conversation",
|
|
518
|
-
"- **Skills** are in `.claude/skills/` — loaded on demand by the agent",
|
|
519
|
-
"- **Docs:** https://ai.contentrain.io"
|
|
520
|
-
].join("\n");
|
|
521
|
-
const git = simpleGit(projectRoot);
|
|
522
|
-
if (await pathExists(claudeMdPath)) {
|
|
523
|
-
if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
|
|
524
|
-
await appendFile(claudeMdPath, `\n\n${reference}\n`);
|
|
525
|
-
await git.add(claudeMdPath);
|
|
399
|
+
if (writtenPaths.length > 0) {
|
|
400
|
+
const git = simpleGit(projectRoot);
|
|
401
|
+
await git.add(writtenPaths);
|
|
526
402
|
try {
|
|
527
|
-
await git.commit("[contentrain]
|
|
403
|
+
await git.commit("[contentrain] configure MCP servers");
|
|
528
404
|
} catch {}
|
|
529
405
|
}
|
|
530
|
-
}
|
|
531
|
-
await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
|
|
532
|
-
await git.add(claudeMdPath);
|
|
533
|
-
try {
|
|
534
|
-
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
535
|
-
} catch {}
|
|
536
|
-
}
|
|
406
|
+
} catch {}
|
|
537
407
|
}
|
|
538
408
|
async function updateGitignore(projectRoot) {
|
|
539
409
|
const gitignorePath = join(projectRoot, ".gitignore");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { i as checkPermissions, o as loadCredentials, s as saveCredentials, t as StudioApiClient } from "./client-
|
|
3
|
-
import { n as openBrowser, t as startOAuthServer } from "./oauth-server-
|
|
2
|
+
import { i as checkPermissions, o as loadCredentials, s as saveCredentials, t as StudioApiClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { n as openBrowser, t as startOAuthServer } from "./oauth-server-Beppljfz.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/login.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { a as clearCredentials, o as loadCredentials, t as StudioApiClient } from "./client-
|
|
2
|
+
import { a as clearCredentials, o as loadCredentials, t as StudioApiClient } from "./client-C400qHcL.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { intro, log, outro } from "@clack/prompts";
|
|
5
5
|
//#region src/studio/commands/logout.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as saveDefaults, o as loadCredentials } from "./client-
|
|
1
|
+
import { c as saveDefaults, o as loadCredentials } from "./client-C400qHcL.mjs";
|
|
2
2
|
import { confirm, isCancel, select } from "@clack/prompts";
|
|
3
3
|
//#region src/studio/resolve-context.ts
|
|
4
4
|
/**
|
|
@@ -11,32 +11,51 @@ var serve_default = defineCommand({
|
|
|
11
11
|
args: {
|
|
12
12
|
root: {
|
|
13
13
|
type: "string",
|
|
14
|
-
description: "Project root path",
|
|
14
|
+
description: "Project root path (env: CONTENTRAIN_PROJECT_ROOT)",
|
|
15
15
|
required: false
|
|
16
16
|
},
|
|
17
17
|
stdio: {
|
|
18
18
|
type: "boolean",
|
|
19
|
-
description: "Use stdio MCP transport
|
|
19
|
+
description: "Use stdio MCP transport for IDE integration (env: CONTENTRAIN_STDIO=true)",
|
|
20
20
|
required: false
|
|
21
21
|
},
|
|
22
22
|
port: {
|
|
23
23
|
type: "string",
|
|
24
|
-
description: "HTTP server port
|
|
24
|
+
description: "HTTP server port, default: 3333 (env: CONTENTRAIN_PORT)",
|
|
25
25
|
required: false
|
|
26
26
|
},
|
|
27
27
|
open: {
|
|
28
28
|
type: "boolean",
|
|
29
|
-
description: "Open browser automatically",
|
|
29
|
+
description: "Open browser automatically (env: CONTENTRAIN_NO_OPEN=true to disable)",
|
|
30
30
|
required: false
|
|
31
31
|
},
|
|
32
32
|
host: {
|
|
33
33
|
type: "string",
|
|
34
|
-
description: "Bind address
|
|
34
|
+
description: "Bind address, default: localhost (env: CONTENTRAIN_HOST)",
|
|
35
|
+
required: false
|
|
36
|
+
},
|
|
37
|
+
demo: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
description: "Start with a temporary demo project (no setup needed)",
|
|
35
40
|
required: false
|
|
36
41
|
}
|
|
37
42
|
},
|
|
38
43
|
async run({ args }) {
|
|
39
|
-
|
|
44
|
+
let projectRoot = await resolveProjectRoot(args.root);
|
|
45
|
+
let demoDir;
|
|
46
|
+
if (args.demo) {
|
|
47
|
+
const { createDemoProject, cleanupDemoProject } = await import("./demo-DcBt6fw8.mjs");
|
|
48
|
+
demoDir = await createDemoProject();
|
|
49
|
+
projectRoot = demoDir;
|
|
50
|
+
consola.info(`Demo project created at ${demoDir}`);
|
|
51
|
+
process.on("exit", () => {
|
|
52
|
+
cleanupDemoProject(demoDir);
|
|
53
|
+
});
|
|
54
|
+
process.on("SIGINT", () => {
|
|
55
|
+
cleanupDemoProject(demoDir);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
40
59
|
process.env["CONTENTRAIN_PROJECT_ROOT"] = projectRoot;
|
|
41
60
|
process.env["CONTENTRAIN_SOURCE"] = "mcp-local";
|
|
42
61
|
try {
|
|
@@ -47,7 +66,7 @@ var serve_default = defineCommand({
|
|
|
47
66
|
if (name && typeof name === "string") process.env["CONTENTRAIN_AUTHOR_NAME"] = name;
|
|
48
67
|
if (email && typeof email === "string") process.env["CONTENTRAIN_AUTHOR_EMAIL"] = email;
|
|
49
68
|
} catch {}
|
|
50
|
-
if (args.stdio) {
|
|
69
|
+
if (args.stdio || process.env["CONTENTRAIN_STDIO"] === "true" || process.env["CONTENTRAIN_STDIO"] === "1") {
|
|
51
70
|
const server = createServer(projectRoot);
|
|
52
71
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
53
72
|
const transport = new StdioServerTransport();
|
|
@@ -55,9 +74,9 @@ var serve_default = defineCommand({
|
|
|
55
74
|
await server.connect(transport);
|
|
56
75
|
return;
|
|
57
76
|
}
|
|
58
|
-
const port = Number(args.port) || 3333;
|
|
59
|
-
const host = args.host ?? "localhost";
|
|
60
|
-
const shouldOpen = args.open !== false;
|
|
77
|
+
const port = Number(args.port) || Number(process.env["CONTENTRAIN_PORT"]) || 3333;
|
|
78
|
+
const host = args.host ?? process.env["CONTENTRAIN_HOST"] ?? "localhost";
|
|
79
|
+
const shouldOpen = args.open !== false && process.env["CONTENTRAIN_NO_OPEN"] !== "true";
|
|
61
80
|
const { join, dirname } = await import("node:path");
|
|
62
81
|
const { existsSync } = await import("node:fs");
|
|
63
82
|
const { fileURLToPath } = await import("node:url");
|
|
@@ -66,7 +85,7 @@ var serve_default = defineCommand({
|
|
|
66
85
|
consola.warn("Serve UI not found. Running in API-only mode.");
|
|
67
86
|
consola.info("UI will be available after building: cd packages/cli/src/serve-ui && pnpm build");
|
|
68
87
|
}
|
|
69
|
-
const { createServeApp } = await import("./server-
|
|
88
|
+
const { createServeApp } = await import("./server-DTdmutjb.mjs");
|
|
70
89
|
const { app, handleUpgrade } = await createServeApp({
|
|
71
90
|
projectRoot,
|
|
72
91
|
port,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
|
|
2
|
+
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
3
|
+
import { a as detectIdes, n as IDE_CONFIGS, o as installIdeRulesAndSkills, r as MCP_CONFIGS, s as writeMcpConfig } from "./ide-Bg-Z8QZk.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
|
+
//#region src/commands/setup.ts
|
|
7
|
+
const SUPPORTED_AGENTS = Object.keys(MCP_CONFIGS);
|
|
8
|
+
var setup_default = defineCommand({
|
|
9
|
+
meta: {
|
|
10
|
+
name: "setup",
|
|
11
|
+
description: "Configure MCP server and AI rules for your IDE"
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
agent: {
|
|
15
|
+
type: "positional",
|
|
16
|
+
description: `IDE agent: ${SUPPORTED_AGENTS.join(", ")}`,
|
|
17
|
+
required: false
|
|
18
|
+
},
|
|
19
|
+
root: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Project root path",
|
|
22
|
+
required: false
|
|
23
|
+
},
|
|
24
|
+
all: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
description: "Configure all detected IDEs",
|
|
27
|
+
required: false
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async run({ args }) {
|
|
31
|
+
const projectRoot = await resolveProjectRoot(args.root);
|
|
32
|
+
intro(pc.bold("contentrain setup"));
|
|
33
|
+
const agent = args.agent;
|
|
34
|
+
if (!agent && !args.all) {
|
|
35
|
+
log.error("Please specify an agent or use --all:");
|
|
36
|
+
log.message("");
|
|
37
|
+
log.message(` ${pc.cyan("contentrain setup claude-code")}`);
|
|
38
|
+
log.message(` ${pc.cyan("contentrain setup cursor")}`);
|
|
39
|
+
log.message(` ${pc.cyan("contentrain setup vscode")}`);
|
|
40
|
+
log.message(` ${pc.cyan("contentrain setup windsurf")}`);
|
|
41
|
+
log.message(` ${pc.cyan("contentrain setup copilot")}`);
|
|
42
|
+
log.message(` ${pc.cyan("contentrain setup --all")}`);
|
|
43
|
+
outro("");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (agent && !SUPPORTED_AGENTS.includes(agent)) {
|
|
47
|
+
log.error(`Unknown agent: "${agent}"`);
|
|
48
|
+
log.message(`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`);
|
|
49
|
+
outro("");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!(await loadProjectContext(projectRoot)).initialized) {
|
|
53
|
+
log.warning("Project not initialized. Run `contentrain init` first.");
|
|
54
|
+
outro("");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const s = spinner();
|
|
58
|
+
let agents;
|
|
59
|
+
if (args.all) {
|
|
60
|
+
agents = await detectIdes(projectRoot);
|
|
61
|
+
if (agents.length === 0) agents = ["claude-code"];
|
|
62
|
+
s.start(`Configuring MCP for ${agents.map((a) => MCP_CONFIGS[a] ? a : "").filter(Boolean).join(", ")}...`);
|
|
63
|
+
} else {
|
|
64
|
+
agents = [agent];
|
|
65
|
+
s.start(`Configuring MCP for ${agent}...`);
|
|
66
|
+
}
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const agentKey of agents) {
|
|
69
|
+
const mcpResult = await writeMcpConfig(projectRoot, agentKey);
|
|
70
|
+
let rulesStatus = "skipped";
|
|
71
|
+
const ideConfig = IDE_CONFIGS[agentKey];
|
|
72
|
+
if (ideConfig) try {
|
|
73
|
+
const { createRequire } = await import("node:module");
|
|
74
|
+
const req = createRequire(import.meta.url);
|
|
75
|
+
const resolveRuleFile = (p) => req.resolve(`@contentrain/rules/${p}`);
|
|
76
|
+
let resolveSkillFile = null;
|
|
77
|
+
try {
|
|
78
|
+
resolveSkillFile = (p) => req.resolve(`@contentrain/skills/${p}`);
|
|
79
|
+
} catch {}
|
|
80
|
+
const result = await installIdeRulesAndSkills(projectRoot, ideConfig, resolveRuleFile, resolveSkillFile);
|
|
81
|
+
if (result.installed > 0) rulesStatus = `${result.installed} installed`;
|
|
82
|
+
else rulesStatus = "up to date";
|
|
83
|
+
} catch {
|
|
84
|
+
rulesStatus = "failed (packages not installed)";
|
|
85
|
+
}
|
|
86
|
+
results.push({
|
|
87
|
+
agent: agentKey,
|
|
88
|
+
mcp: mcpResult.written ? `${pc.green("✓")} ${mcpResult.path}` : `${pc.dim("–")} ${mcpResult.skipped ?? mcpResult.path}`,
|
|
89
|
+
rules: rulesStatus
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
s.stop("Done");
|
|
93
|
+
for (const r of results) {
|
|
94
|
+
log.info(`${pc.bold(r.agent)}`);
|
|
95
|
+
log.message(` MCP config: ${r.mcp}`);
|
|
96
|
+
log.message(` Rules/skills: ${r.rules}`);
|
|
97
|
+
}
|
|
98
|
+
log.message("");
|
|
99
|
+
log.info(pc.bold("Next steps:"));
|
|
100
|
+
log.message(` 1. Restart your IDE to pick up the MCP config`);
|
|
101
|
+
log.message(` 2. Ask your agent: ${pc.cyan("\"Create a hero section model\"")}`);
|
|
102
|
+
log.message("");
|
|
103
|
+
log.message(pc.dim(` Tip: Run ${pc.cyan("contentrain serve")} to open the local review UI`));
|
|
104
|
+
outro("");
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
export { setup_default as default };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { r as resolveProjectRoot } from "./context-An3xrvcP.mjs";
|
|
2
|
+
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
3
|
+
import { a as detectIdes, n as IDE_CONFIGS, o as installIdeRulesAndSkills, t as AGENT_SKILL_NAMES } from "./ide-Bg-Z8QZk.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { pathExists } from "@contentrain/mcp/util/fs";
|
|
8
|
+
//#region src/commands/skills.ts
|
|
9
|
+
var skills_default = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "skills",
|
|
12
|
+
description: "Install, update, or list Contentrain AI skills and rules for your IDE"
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
root: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Project root path",
|
|
18
|
+
required: false
|
|
19
|
+
},
|
|
20
|
+
update: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "Force update all skills and rules",
|
|
23
|
+
required: false
|
|
24
|
+
},
|
|
25
|
+
list: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "List installed skills and their status",
|
|
28
|
+
required: false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
async run({ args }) {
|
|
32
|
+
const projectRoot = await resolveProjectRoot(args.root);
|
|
33
|
+
intro(pc.bold("contentrain skills"));
|
|
34
|
+
if (args.list) {
|
|
35
|
+
await listInstalledSkills(projectRoot);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const s = spinner();
|
|
39
|
+
s.start("Installing skills and rules...");
|
|
40
|
+
try {
|
|
41
|
+
const { createRequire } = await import("node:module");
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const resolveRuleFile = (p) => require.resolve(`@contentrain/rules/${p}`);
|
|
44
|
+
let resolveSkillFile = null;
|
|
45
|
+
try {
|
|
46
|
+
const requireSkills = createRequire(import.meta.url);
|
|
47
|
+
resolveSkillFile = (p) => requireSkills.resolve(`@contentrain/skills/${p}`);
|
|
48
|
+
} catch {
|
|
49
|
+
s.stop("Failed");
|
|
50
|
+
log.error("@contentrain/skills package not found. Install it:");
|
|
51
|
+
log.message(pc.cyan(" pnpm add -D @contentrain/skills @contentrain/rules"));
|
|
52
|
+
outro("");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const detectedIdes = await detectIdes(projectRoot);
|
|
56
|
+
if (detectedIdes.length === 0) detectedIdes.push("claude-code");
|
|
57
|
+
let totalInstalled = 0;
|
|
58
|
+
let totalUpdated = 0;
|
|
59
|
+
for (const ideKey of detectedIdes) {
|
|
60
|
+
const ide = IDE_CONFIGS[ideKey];
|
|
61
|
+
const result = await installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile, args.update ?? false);
|
|
62
|
+
totalInstalled += result.installed;
|
|
63
|
+
totalUpdated += result.updated;
|
|
64
|
+
}
|
|
65
|
+
s.stop(`Done — ${detectedIdes.map((k) => IDE_CONFIGS[k].name).join(", ")}`);
|
|
66
|
+
if (totalUpdated > 0) log.success(`Updated ${totalUpdated} skill(s)`);
|
|
67
|
+
if (totalInstalled > 0) log.success(`Installed ${totalInstalled} new skill(s)`);
|
|
68
|
+
if (totalInstalled === 0 && totalUpdated === 0) log.info("All skills are up to date");
|
|
69
|
+
outro("");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
s.stop("Failed");
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
async function listInstalledSkills(projectRoot) {
|
|
77
|
+
let found = false;
|
|
78
|
+
for (const [, ide] of Object.entries(IDE_CONFIGS)) {
|
|
79
|
+
const skillsDir = join(projectRoot, ide.skillsDir);
|
|
80
|
+
if (!await pathExists(skillsDir)) continue;
|
|
81
|
+
found = true;
|
|
82
|
+
log.info(`${pc.bold(ide.name)} (${ide.skillsDir}/):`);
|
|
83
|
+
for (const skillName of AGENT_SKILL_NAMES) if (await pathExists(join(skillsDir, skillName, "SKILL.md"))) log.message(` ${pc.green("✓")} ${skillName}`);
|
|
84
|
+
else log.message(` ${pc.red("✗")} ${skillName} ${pc.dim("(missing)")}`);
|
|
85
|
+
if (await pathExists(join(projectRoot, ide.rulesDir, ide.guardrailsFileName))) log.message(` ${pc.green("✓")} essential rules`);
|
|
86
|
+
else log.message(` ${pc.red("✗")} essential rules ${pc.dim("(missing)")}`);
|
|
87
|
+
}
|
|
88
|
+
if (!found) log.warning("No IDE skills directories found. Run `contentrain skills` to install.");
|
|
89
|
+
outro("");
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { skills_default as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/status.ts
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
//#region src/studio/index.ts
|
|
3
|
+
var studio_default = defineCommand({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "studio",
|
|
6
|
+
description: "Interact with Contentrain Studio"
|
|
7
|
+
},
|
|
8
|
+
subCommands: {
|
|
9
|
+
login: () => import("./login-DCXkOrQV.mjs").then((m) => m.default),
|
|
10
|
+
logout: () => import("./logout-nPt2UAeQ.mjs").then((m) => m.default),
|
|
11
|
+
whoami: () => import("./whoami-Ub5RznbU.mjs").then((m) => m.default),
|
|
12
|
+
connect: () => import("./connect-DJo9PSiw.mjs").then((m) => m.default),
|
|
13
|
+
status: () => import("./status-Cyv-jo1y.mjs").then((m) => m.default),
|
|
14
|
+
activity: () => import("./activity-CbkxcfDf.mjs").then((m) => m.default),
|
|
15
|
+
usage: () => import("./usage-B2xuc2DT.mjs").then((m) => m.default),
|
|
16
|
+
branches: () => import("./branches-D2UWex1N.mjs").then((m) => m.default),
|
|
17
|
+
"cdn-init": () => import("./cdn-init-CsmqJIaQ.mjs").then((m) => m.default),
|
|
18
|
+
"cdn-build": () => import("./cdn-build-BzC2QPNi.mjs").then((m) => m.default),
|
|
19
|
+
webhooks: () => import("./webhooks-Dq3zFLP7.mjs").then((m) => m.default),
|
|
20
|
+
submissions: () => import("./submissions-bIG-MWvv.mjs").then((m) => m.default)
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
//#endregion
|
|
24
|
+
export { studio_default as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/submissions.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, n as formatPercent, r as formatTable } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/usage.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as pc, o as statusIcon, r as formatTable } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { n as resolveStudioClient } from "./client-
|
|
3
|
-
import { t as resolveStudioContext } from "./resolve-context-
|
|
2
|
+
import { n as resolveStudioClient } from "./client-C400qHcL.mjs";
|
|
3
|
+
import { t as resolveStudioContext } from "./resolve-context-Cg6JoVZt.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
6
6
|
//#region src/studio/commands/webhooks.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as pc } from "./ui-B5mXontH.mjs";
|
|
2
|
-
import { i as checkPermissions, n as resolveStudioClient, r as AuthExpiredError } from "./client-
|
|
2
|
+
import { i as checkPermissions, n as resolveStudioClient, r as AuthExpiredError } from "./client-C400qHcL.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { intro, log, outro } from "@clack/prompts";
|
|
5
5
|
//#region src/studio/commands/whoami.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contentrain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "CLI for Contentrain — AI content governance infrastructure",
|
|
6
6
|
"type": "module",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"picocolors": "^1.1.0",
|
|
47
47
|
"simple-git": "^3.27.0",
|
|
48
48
|
"ws": "^8.18.0",
|
|
49
|
-
"@contentrain/rules": "0.3.
|
|
50
|
-
"@contentrain/
|
|
51
|
-
"@contentrain/
|
|
52
|
-
"@contentrain/
|
|
49
|
+
"@contentrain/rules": "0.3.2",
|
|
50
|
+
"@contentrain/mcp": "1.2.0",
|
|
51
|
+
"@contentrain/query": "5.1.4",
|
|
52
|
+
"@contentrain/types": "0.4.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/ws": "^8.5.0",
|
package/dist/studio-B8lF0fQx.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
//#region src/studio/index.ts
|
|
3
|
-
var studio_default = defineCommand({
|
|
4
|
-
meta: {
|
|
5
|
-
name: "studio",
|
|
6
|
-
description: "Interact with Contentrain Studio"
|
|
7
|
-
},
|
|
8
|
-
subCommands: {
|
|
9
|
-
login: () => import("./login-wRBvHDCi.mjs").then((m) => m.default),
|
|
10
|
-
logout: () => import("./logout-xPTGu6vx.mjs").then((m) => m.default),
|
|
11
|
-
whoami: () => import("./whoami-CWvTYzum.mjs").then((m) => m.default),
|
|
12
|
-
connect: () => import("./connect-DiSYnxFd.mjs").then((m) => m.default),
|
|
13
|
-
status: () => import("./status-UvjbO4eS.mjs").then((m) => m.default),
|
|
14
|
-
activity: () => import("./activity-DMQsYlzz.mjs").then((m) => m.default),
|
|
15
|
-
usage: () => import("./usage-BqriRRqE.mjs").then((m) => m.default),
|
|
16
|
-
branches: () => import("./branches-D330IxDO.mjs").then((m) => m.default),
|
|
17
|
-
"cdn-init": () => import("./cdn-init-Bhnil0F8.mjs").then((m) => m.default),
|
|
18
|
-
"cdn-build": () => import("./cdn-build-D_d3p4OZ.mjs").then((m) => m.default),
|
|
19
|
-
webhooks: () => import("./webhooks-B1OhcCtZ.mjs").then((m) => m.default),
|
|
20
|
-
submissions: () => import("./submissions-Dw0ChFDi.mjs").then((m) => m.default)
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
//#endregion
|
|
24
|
-
export { studio_default as default };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|