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.
Files changed (30) hide show
  1. package/dist/{activity-DMQsYlzz.mjs → activity-CbkxcfDf.mjs} +2 -2
  2. package/dist/{branches-D330IxDO.mjs → branches-D2UWex1N.mjs} +2 -2
  3. package/dist/{cdn-build-D_d3p4OZ.mjs → cdn-build-BzC2QPNi.mjs} +2 -2
  4. package/dist/{cdn-init-Bhnil0F8.mjs → cdn-init-CsmqJIaQ.mjs} +2 -2
  5. package/dist/{connect-DiSYnxFd.mjs → connect-DJo9PSiw.mjs} +2 -2
  6. package/dist/demo-DcBt6fw8.mjs +80 -0
  7. package/dist/{diff-Cq6iSnaD.mjs → diff--halNUpG.mjs} +1 -0
  8. package/dist/{generate-DDEDRLdy.mjs → generate-DrUojrk8.mjs} +1 -0
  9. package/dist/ide-Bg-Z8QZk.mjs +227 -0
  10. package/dist/index.mjs +10 -8
  11. package/dist/{init-BARgfYUV.mjs → init-DFyQ-l-E.mjs} +25 -152
  12. package/dist/{login-wRBvHDCi.mjs → login-DCXkOrQV.mjs} +2 -2
  13. package/dist/{logout-xPTGu6vx.mjs → logout-nPt2UAeQ.mjs} +1 -1
  14. package/dist/{resolve-context-BwcikcF0.mjs → resolve-context-Cg6JoVZt.mjs} +1 -1
  15. package/dist/{serve-DL4LhWxl.mjs → serve-CNeFIQ1i.mjs} +30 -11
  16. package/dist/setup-DJy-jNEU.mjs +108 -0
  17. package/dist/skills-D-tFY7WA.mjs +92 -0
  18. package/dist/{status-Bi9HYgcA.mjs → status-CoHgz6HN.mjs} +1 -0
  19. package/dist/{status-UvjbO4eS.mjs → status-Cyv-jo1y.mjs} +2 -2
  20. package/dist/studio-CJUGiIMd.mjs +24 -0
  21. package/dist/{submissions-Dw0ChFDi.mjs → submissions-bIG-MWvv.mjs} +2 -2
  22. package/dist/{usage-BqriRRqE.mjs → usage-B2xuc2DT.mjs} +2 -2
  23. package/dist/{webhooks-B1OhcCtZ.mjs → webhooks-Dq3zFLP7.mjs} +2 -2
  24. package/dist/{whoami-CWvTYzum.mjs → whoami-Ub5RznbU.mjs} +1 -1
  25. package/package.json +5 -5
  26. package/dist/studio-B8lF0fQx.mjs +0 -24
  27. /package/dist/{client-CLV4XoJz.mjs → client-C400qHcL.mjs} +0 -0
  28. /package/dist/{oauth-server-1p6y1H30.mjs → oauth-server-Beppljfz.mjs} +0 -0
  29. /package/dist/{server-WLDOAZa4.mjs → server-DTdmutjb.mjs} +0 -0
  30. /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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { n as openBrowser, t as startOAuthServer } from "./oauth-server-1p6y1H30.mjs";
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.0",
8
+ version: "0.4.2",
9
9
  description: "Contentrain CLI — AI content governance infrastructure"
10
10
  },
11
11
  subCommands: {
12
- init: () => import("./init-BARgfYUV.mjs").then((m) => m.default),
13
- status: () => import("./status-Bi9HYgcA.mjs").then((m) => m.default),
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-DnVamp3p.mjs").then((m) => m.default),
16
- serve: () => import("./serve-DL4LhWxl.mjs").then((m) => m.default),
17
- generate: () => import("./generate-DDEDRLdy.mjs").then((m) => m.default),
18
- diff: () => import("./diff-Cq6iSnaD.mjs").then((m) => m.default),
19
- studio: () => import("./studio-B8lF0fQx.mjs").then((m) => m.default)
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 hasClaudeCode = await pathExists(join(projectRoot, "CLAUDE.md")) || await pathExists(join(projectRoot, ".claude"));
422
- const hasCursor = await pathExists(join(projectRoot, ".cursor"));
423
- const hasWindsurf = await pathExists(join(projectRoot, ".windsurf"));
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 (hasClaudeCode) await addClaudeMdReference(projectRoot);
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 installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile) {
449
- const rulesDir = join(projectRoot, ide.rulesDir);
450
- const skillsDir = join(projectRoot, ide.skillsDir);
451
- const git = simpleGit(projectRoot);
452
- const filesToAdd = [];
453
- await ensureDir(rulesDir);
454
- const guardrailsDest = join(rulesDir, ide.guardrailsFileName);
455
- if (!await pathExists(guardrailsDest)) try {
456
- let content = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
457
- if (ide.guardrailsFrontmatter) content = ide.guardrailsFrontmatter + content;
458
- if (ide.name === "GitHub Copilot" && await pathExists(guardrailsDest)) {
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
- if (filesToAdd.length > 0) {
500
- await git.add(filesToAdd);
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] add CLAUDE.md reference");
403
+ await git.commit("[contentrain] configure MCP servers");
525
404
  } catch {}
526
405
  }
527
- } else {
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-CLV4XoJz.mjs";
3
- import { n as openBrowser, t as startOAuthServer } from "./oauth-server-1p6y1H30.mjs";
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-CLV4XoJz.mjs";
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-CLV4XoJz.mjs";
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 (for IDE integration)",
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 (default: 3333)",
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 (default: localhost)",
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
- const projectRoot = await resolveProjectRoot(args.root);
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-WLDOAZa4.mjs");
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
3
- import { t as resolveStudioContext } from "./resolve-context-BwcikcF0.mjs";
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-CLV4XoJz.mjs";
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.0",
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.1",
50
- "@contentrain/mcp": "1.1.2",
51
- "@contentrain/query": "5.1.3",
52
- "@contentrain/types": "0.4.1"
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",
@@ -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 };