contentrain 0.4.0 → 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/{diff-Cq6iSnaD.mjs → diff--halNUpG.mjs} +1 -0
- package/dist/{generate-DDEDRLdy.mjs → generate-DrUojrk8.mjs} +1 -0
- package/dist/ide-Bg-Z8QZk.mjs +227 -0
- package/dist/index.mjs +10 -8
- package/dist/{init-BARgfYUV.mjs → init-DFyQ-l-E.mjs} +25 -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-Bi9HYgcA.mjs → status-CoHgz6HN.mjs} +1 -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/{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 };
|
|
@@ -141,6 +141,7 @@ var diff_default = defineCommand({
|
|
|
141
141
|
log.error(`Delete failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
log.message(pc.dim(`\n Tip: ${pc.cyan("contentrain studio branches")} — visual review and approval in ${pc.bold("Contentrain Studio")} → ${pc.underline("https://studio.contentrain.io")}`));
|
|
144
145
|
outro("");
|
|
145
146
|
}
|
|
146
147
|
});
|
|
@@ -41,6 +41,7 @@ var generate_default = defineCommand({
|
|
|
41
41
|
log.message(` Types: ${result.typesCount}`);
|
|
42
42
|
log.message(` Data: ${result.dataModulesCount} modules`);
|
|
43
43
|
if (result.packageJsonUpdated) log.info(`${pc.cyan("#contentrain")} imports added to package.json`);
|
|
44
|
+
log.message(pc.dim(`\n Tip: ${pc.cyan("contentrain studio cdn-init")} — deliver content via ${pc.bold("Contentrain Studio")} CDN → ${pc.underline("https://studio.contentrain.io")}`));
|
|
44
45
|
if (args.watch) {
|
|
45
46
|
log.info("Watching for changes... (Ctrl+C to stop)");
|
|
46
47
|
const crDir = contentrainDir(projectRoot);
|
|
@@ -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-
|
|
17
|
-
generate: () => import("./generate-
|
|
18
|
-
diff: () => import("./diff
|
|
19
|
-
|
|
15
|
+
validate: () => import("./validate-B10L7RL7.mjs").then((m) => m.default),
|
|
16
|
+
serve: () => import("./serve-CNeFIQ1i.mjs").then((m) => m.default),
|
|
17
|
+
generate: () => import("./generate-DrUojrk8.mjs").then((m) => m.default),
|
|
18
|
+
diff: () => import("./diff--halNUpG.mjs").then((m) => m.default),
|
|
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";
|
|
@@ -278,6 +279,9 @@ var init_default = defineCommand({
|
|
|
278
279
|
log.message(` ${pc.cyan("contentrain doctor")} — verify setup health`);
|
|
279
280
|
if (templateChoice === "none") log.message(` ${pc.cyan("contentrain serve")} — open the local review and normalize UI`);
|
|
280
281
|
log.message(` ${pc.cyan("contentrain generate")} — generate SDK client`);
|
|
282
|
+
log.message("");
|
|
283
|
+
log.message(pc.dim(` Tip: ${pc.cyan("contentrain studio connect")} — link this project to ${pc.bold("Contentrain Studio")} for`));
|
|
284
|
+
log.message(pc.dim(` team review, CDN delivery, and collaboration → ${pc.underline("https://studio.contentrain.io")}`));
|
|
281
285
|
outro("");
|
|
282
286
|
}
|
|
283
287
|
});
|
|
@@ -343,6 +347,7 @@ async function executeInit(projectRoot, opts) {
|
|
|
343
347
|
await tx.complete();
|
|
344
348
|
s.stop("Initialized");
|
|
345
349
|
await installRules(projectRoot);
|
|
350
|
+
await configureMcp(projectRoot);
|
|
346
351
|
} catch (error) {
|
|
347
352
|
s.stop("Failed");
|
|
348
353
|
throw error;
|
|
@@ -350,64 +355,6 @@ async function executeInit(projectRoot, opts) {
|
|
|
350
355
|
await tx.cleanup();
|
|
351
356
|
}
|
|
352
357
|
}
|
|
353
|
-
const AGENT_SKILL_NAMES = [
|
|
354
|
-
"contentrain",
|
|
355
|
-
"contentrain-normalize",
|
|
356
|
-
"contentrain-quality",
|
|
357
|
-
"contentrain-sdk",
|
|
358
|
-
"contentrain-content",
|
|
359
|
-
"contentrain-model",
|
|
360
|
-
"contentrain-init",
|
|
361
|
-
"contentrain-bulk",
|
|
362
|
-
"contentrain-validate-fix",
|
|
363
|
-
"contentrain-review",
|
|
364
|
-
"contentrain-translate",
|
|
365
|
-
"contentrain-generate",
|
|
366
|
-
"contentrain-serve",
|
|
367
|
-
"contentrain-diff",
|
|
368
|
-
"contentrain-doctor"
|
|
369
|
-
];
|
|
370
|
-
const OLD_RULE_FILES = [
|
|
371
|
-
"contentrain-content-quality.md",
|
|
372
|
-
"contentrain-seo-rules.md",
|
|
373
|
-
"contentrain-i18n-quality.md",
|
|
374
|
-
"contentrain-accessibility-rules.md",
|
|
375
|
-
"contentrain-security-rules.md",
|
|
376
|
-
"contentrain-media-rules.md",
|
|
377
|
-
"contentrain-content-conventions.md",
|
|
378
|
-
"contentrain-schema-rules.md",
|
|
379
|
-
"contentrain-mcp-usage.md",
|
|
380
|
-
"contentrain-workflow-rules.md",
|
|
381
|
-
"contentrain-normalize-rules.md"
|
|
382
|
-
];
|
|
383
|
-
const IDE_CONFIGS = {
|
|
384
|
-
"claude-code": {
|
|
385
|
-
name: "Claude Code",
|
|
386
|
-
rulesDir: ".claude/rules",
|
|
387
|
-
skillsDir: ".claude/skills",
|
|
388
|
-
guardrailsFileName: "contentrain-essentials.md"
|
|
389
|
-
},
|
|
390
|
-
cursor: {
|
|
391
|
-
name: "Cursor",
|
|
392
|
-
rulesDir: ".cursor/rules",
|
|
393
|
-
skillsDir: ".cursor/skills",
|
|
394
|
-
guardrailsFileName: "contentrain-essentials.mdc",
|
|
395
|
-
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\nalwaysApply: true\n---\n\n"
|
|
396
|
-
},
|
|
397
|
-
windsurf: {
|
|
398
|
-
name: "Windsurf",
|
|
399
|
-
rulesDir: ".windsurf/rules",
|
|
400
|
-
skillsDir: ".windsurf/skills",
|
|
401
|
-
guardrailsFileName: "contentrain-essentials.md",
|
|
402
|
-
guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\ntrigger: always_on\n---\n\n"
|
|
403
|
-
},
|
|
404
|
-
copilot: {
|
|
405
|
-
name: "GitHub Copilot",
|
|
406
|
-
rulesDir: ".github",
|
|
407
|
-
skillsDir: ".agents/skills",
|
|
408
|
-
guardrailsFileName: "copilot-instructions.md"
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
358
|
async function installRules(projectRoot) {
|
|
412
359
|
try {
|
|
413
360
|
const { createRequire } = await import("node:module");
|
|
@@ -418,21 +365,13 @@ async function installRules(projectRoot) {
|
|
|
418
365
|
const requireSkills = createRequire(import.meta.url);
|
|
419
366
|
resolveSkillFile = (p) => requireSkills.resolve(`@contentrain/skills/${p}`);
|
|
420
367
|
} catch {}
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const hasCopilot = await pathExists(join(projectRoot, ".github"));
|
|
425
|
-
const noIDEDetected = !hasClaudeCode && !hasCursor && !hasWindsurf && !hasCopilot;
|
|
426
|
-
const detectedIdes = [];
|
|
427
|
-
if (hasClaudeCode) detectedIdes.push("claude-code");
|
|
428
|
-
if (hasCursor) detectedIdes.push("cursor");
|
|
429
|
-
if (hasWindsurf) detectedIdes.push("windsurf");
|
|
430
|
-
if (hasCopilot) detectedIdes.push("copilot");
|
|
431
|
-
for (const ideKey of detectedIdes) {
|
|
368
|
+
const detectedIdeKeys = await detectIdes(projectRoot);
|
|
369
|
+
const noIDEDetected = detectedIdeKeys.length === 0;
|
|
370
|
+
for (const ideKey of detectedIdeKeys) {
|
|
432
371
|
const ide = IDE_CONFIGS[ideKey];
|
|
433
372
|
await installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile);
|
|
434
373
|
}
|
|
435
|
-
if (
|
|
374
|
+
if (detectedIdeKeys.includes("claude-code")) await addClaudeMdReference(projectRoot);
|
|
436
375
|
if (noIDEDetected) {
|
|
437
376
|
const essentials = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
|
|
438
377
|
const dest = join(projectRoot, "CLAUDE.md");
|
|
@@ -445,92 +384,26 @@ async function installRules(projectRoot) {
|
|
|
445
384
|
}
|
|
446
385
|
} catch {}
|
|
447
386
|
}
|
|
448
|
-
async function
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (!(await readFile(guardrailsDest, "utf-8")).includes("# Contentrain")) await appendFile(guardrailsDest, `\n\n${content}`);
|
|
460
|
-
} else await writeFile(guardrailsDest, content, "utf-8");
|
|
461
|
-
filesToAdd.push(guardrailsDest);
|
|
462
|
-
} catch {}
|
|
463
|
-
for (const oldFile of OLD_RULE_FILES) {
|
|
464
|
-
const oldPath = join(rulesDir, oldFile);
|
|
465
|
-
if (await pathExists(oldPath)) {
|
|
466
|
-
const { unlink } = await import("node:fs/promises");
|
|
467
|
-
await unlink(oldPath);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
if (resolveSkillFile) {
|
|
471
|
-
await ensureDir(skillsDir);
|
|
472
|
-
for (const skillName of AGENT_SKILL_NAMES) {
|
|
473
|
-
const skillDir = join(skillsDir, skillName);
|
|
474
|
-
const skillMd = join(skillDir, "SKILL.md");
|
|
475
|
-
if (await pathExists(skillMd)) continue;
|
|
476
|
-
try {
|
|
477
|
-
const src = resolveSkillFile(`skills/${skillName}/SKILL.md`);
|
|
478
|
-
await ensureDir(skillDir);
|
|
479
|
-
await writeFile(skillMd, await readFile(src, "utf-8"), "utf-8");
|
|
480
|
-
filesToAdd.push(skillMd);
|
|
481
|
-
const { readdirSync } = await import("node:fs");
|
|
482
|
-
try {
|
|
483
|
-
const refsDir = join(resolveSkillFile(`skills/${skillName}/SKILL.md`), "..", "references");
|
|
484
|
-
const refs = readdirSync(refsDir);
|
|
485
|
-
if (refs.length > 0) {
|
|
486
|
-
const destRefsDir = join(skillDir, "references");
|
|
487
|
-
await ensureDir(destRefsDir);
|
|
488
|
-
for (const ref of refs) {
|
|
489
|
-
const refSrc = join(refsDir, ref);
|
|
490
|
-
const refDest = join(destRefsDir, ref);
|
|
491
|
-
await writeFile(refDest, await readFile(refSrc, "utf-8"), "utf-8");
|
|
492
|
-
filesToAdd.push(refDest);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
} catch {}
|
|
496
|
-
} 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
|
+
}
|
|
497
398
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
await git.commit(`[contentrain] install ${ide.name} rules and skills`);
|
|
503
|
-
} catch {}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
async function addClaudeMdReference(projectRoot) {
|
|
507
|
-
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
508
|
-
const marker = "# Contentrain AI Rules";
|
|
509
|
-
const reference = [
|
|
510
|
-
"# Contentrain AI Rules",
|
|
511
|
-
"",
|
|
512
|
-
"This project uses [Contentrain](https://ai.contentrain.io) for AI-driven content management.",
|
|
513
|
-
"",
|
|
514
|
-
"- **Rules** are in `.claude/rules/contentrain-essentials.md` — auto-loaded each conversation",
|
|
515
|
-
"- **Skills** are in `.claude/skills/` — loaded on demand by the agent",
|
|
516
|
-
"- **Docs:** https://ai.contentrain.io"
|
|
517
|
-
].join("\n");
|
|
518
|
-
const git = simpleGit(projectRoot);
|
|
519
|
-
if (await pathExists(claudeMdPath)) {
|
|
520
|
-
if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
|
|
521
|
-
await appendFile(claudeMdPath, `\n\n${reference}\n`);
|
|
522
|
-
await git.add(claudeMdPath);
|
|
399
|
+
if (writtenPaths.length > 0) {
|
|
400
|
+
const git = simpleGit(projectRoot);
|
|
401
|
+
await git.add(writtenPaths);
|
|
523
402
|
try {
|
|
524
|
-
await git.commit("[contentrain]
|
|
403
|
+
await git.commit("[contentrain] configure MCP servers");
|
|
525
404
|
} catch {}
|
|
526
405
|
}
|
|
527
|
-
}
|
|
528
|
-
await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
|
|
529
|
-
await git.add(claudeMdPath);
|
|
530
|
-
try {
|
|
531
|
-
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
532
|
-
} catch {}
|
|
533
|
-
}
|
|
406
|
+
} catch {}
|
|
534
407
|
}
|
|
535
408
|
async function updateGitignore(projectRoot) {
|
|
536
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 };
|
|
@@ -148,6 +148,7 @@ var status_default = defineCommand({
|
|
|
148
148
|
if (result.summary.errors > 0) log.warning(`\n${formatCount(result.summary.errors, "validation error")}. Run ${pc.cyan("contentrain validate --fix")} to fix.`);
|
|
149
149
|
else log.success("\nValidation: clean");
|
|
150
150
|
} catch {}
|
|
151
|
+
log.message(pc.dim(`\n Tip: ${pc.cyan("contentrain studio status")} — team activity, usage, and CDN in ${pc.bold("Contentrain Studio")} → ${pc.underline("https://studio.contentrain.io")}`));
|
|
151
152
|
outro("");
|
|
152
153
|
}
|
|
153
154
|
});
|
|
@@ -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/mcp": "1.
|
|
51
|
-
"@contentrain/query": "5.1.
|
|
52
|
-
"@contentrain/types": "0.4.
|
|
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
|