contentrain 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -34
- package/dist/{activity-CbkxcfDf.mjs → activity-B8j9gfsK.mjs} +3 -3
- package/dist/{branches-D2UWex1N.mjs → branches-CvpFbyrJ.mjs} +3 -3
- package/dist/{cdn-build-BzC2QPNi.mjs → cdn-build-DS9FprWU.mjs} +3 -3
- package/dist/{cdn-init-CsmqJIaQ.mjs → cdn-init-Bm4dpEJn.mjs} +3 -3
- package/dist/{connect-DJo9PSiw.mjs → connect-Bb0BrZsx.mjs} +3 -3
- package/dist/describe-DJ5H9Cxm.mjs +106 -0
- package/dist/describe-format-CUAqB0C4.mjs +50 -0
- package/dist/{diff--halNUpG.mjs → diff-ov1j9bAb.mjs} +67 -59
- package/dist/doctor-MOf_LQEL.mjs +95 -0
- package/dist/{generate-DrUojrk8.mjs → generate-ByscV5SN.mjs} +26 -9
- package/dist/{ide-4ZGiAtYB.mjs → ide-DPDVYiaq.mjs} +6 -8
- package/dist/index.mjs +50 -12
- package/dist/{init-BVlisNcW.mjs → init-540qiNQJ.mjs} +6 -6
- package/dist/{login-DCXkOrQV.mjs → login-xvzL-UvR.mjs} +3 -3
- package/dist/{logout-nPt2UAeQ.mjs → logout-DzspSatR.mjs} +2 -2
- package/dist/mcp-client-DVWTex2i.mjs +44 -0
- package/dist/merge-DdPRttF-.mjs +82 -0
- package/dist/{resolve-context-Cg6JoVZt.mjs → resolve-context-hKVYPniC.mjs} +1 -1
- package/dist/scaffold-iuvrMh78.mjs +115 -0
- package/dist/{serve-CNeFIQ1i.mjs → serve-CxA2pKbp.mjs} +58 -5
- package/dist/serve-ui/assets/{AgentPromptGroup.vue_vue_type_script_setup_true_lang-Ulb-9Tr3.js → AgentPromptGroup.vue_vue_type_script_setup_true_lang-Bftt9tx_.js} +1 -1
- package/dist/serve-ui/assets/AppLayout-B-fbrfMk.js +21 -0
- package/dist/serve-ui/assets/BranchDetailPage-B3PL9PGp.js +17 -0
- package/dist/serve-ui/assets/{BranchesPage-CSwikNkd.js → BranchesPage-4YpE_szf.js} +2 -2
- package/dist/serve-ui/assets/CardContent.vue_vue_type_script_setup_true_lang-D1L2MnBU.js +1 -0
- package/dist/serve-ui/assets/CollapsibleTrigger.vue_vue_type_script_setup_true_lang-B4uDtllm.js +1 -0
- package/dist/serve-ui/assets/{Collection-DTB4xCCE.js → Collection-9URFko-Y.js} +2 -2
- package/dist/serve-ui/assets/ConfigProvider-CwkmRpT5.js +1 -0
- package/dist/serve-ui/assets/{ContentListPage-DF-LjAaS.js → ContentListPage-BMfSfg8x.js} +2 -2
- package/dist/serve-ui/assets/ContentPage-BXUnNJlB.js +1 -0
- package/dist/serve-ui/assets/DashboardPage-1UyYgg5J.js +21 -0
- package/dist/serve-ui/assets/{DialogTitle.vue_vue_type_script_setup_true_lang-2sQAJn_Y.js → DialogTitle.vue_vue_type_script_setup_true_lang-M4LOzTva.js} +1 -1
- package/dist/serve-ui/assets/DoctorPage-WnQ8xYWy.js +1 -0
- package/dist/serve-ui/assets/FormatReferencePage-DoKEaawl.js +1 -0
- package/dist/serve-ui/assets/{Input.vue_vue_type_script_setup_true_lang-BveqfZsR.js → Input.vue_vue_type_script_setup_true_lang-tW0hgReH.js} +2 -2
- package/dist/serve-ui/assets/{ModelDetailPage-_TIV9itx.js → ModelDetailPage-BysqN8IL.js} +3 -3
- package/dist/serve-ui/assets/ModelsPage-DFZrdBIi.js +1 -0
- package/dist/serve-ui/assets/{NormalizePage-Cde8vdk2.js → NormalizePage-4f5Ib_ES.js} +2 -2
- package/dist/serve-ui/assets/{PageHeader.vue_vue_type_script_setup_true_lang-CEnjONvd.js → PageHeader.vue_vue_type_script_setup_true_lang-Bln1czg7.js} +1 -1
- package/dist/serve-ui/assets/Presence-CA3xTrht.js +3 -0
- package/dist/serve-ui/assets/{Separator.vue_vue_type_script_setup_true_lang-1pFZ2PR2.js → Separator.vue_vue_type_script_setup_true_lang-PECBq3nc.js} +1 -1
- package/dist/serve-ui/assets/{StudioHint.vue_vue_type_script_setup_true_lang-C6JN5tPq.js → StudioHint.vue_vue_type_script_setup_true_lang-DYTEoyjw.js} +1 -1
- package/dist/serve-ui/assets/{TrustBadge.vue_vue_type_script_setup_true_lang-KRiTnLVk.js → TrustBadge.vue_vue_type_script_setup_true_lang-BRXWvOYU.js} +2 -2
- package/dist/serve-ui/assets/ValidatePage-BMTpV2KL.js +16 -0
- package/dist/serve-ui/assets/{arrow-down-BAJwpryD.js → arrow-down-IJu6H7Es.js} +1 -1
- package/dist/serve-ui/assets/{arrow-left-DANf_wrK.js → arrow-left-D8ZjVYAI.js} +1 -1
- package/dist/serve-ui/assets/{book-marked-DS8FXgjz.js → book-marked-BNyyZrq3.js} +1 -1
- package/dist/serve-ui/assets/{book-open-DnH5T78M.js → book-open-Dw1fQtSI.js} +1 -1
- package/dist/serve-ui/assets/{box-UJk5SvJJ.js → box-BoGMZDZR.js} +1 -1
- package/dist/serve-ui/assets/{chevron-down-C1nYx5Eu.js → chevron-down-UcXxhyyY.js} +1 -1
- package/dist/serve-ui/assets/{chevron-right-XnJVrezx.js → chevron-right-SpdM5LBc.js} +1 -1
- package/dist/serve-ui/assets/circle-alert-CEwKJpYl.js +6 -0
- package/dist/serve-ui/assets/{content-Ctqretwl.js → content-BQEzysvq.js} +1 -1
- package/dist/serve-ui/assets/{database-Wd_i2bM1.js → database-U6JU8oc9.js} +1 -1
- package/dist/serve-ui/assets/{file-code-ScWZN40B.js → file-code-_sq1QHQo.js} +1 -1
- package/dist/serve-ui/assets/{file-text-J3Wzwieh.js → file-text-BiMGJckO.js} +1 -1
- package/dist/serve-ui/assets/{git-branch-DqEFTZDt.js → git-branch-CdZMOuSP.js} +1 -1
- package/dist/serve-ui/assets/{git-merge-Bmz-MVBm.js → git-merge-D8Y-BwIh.js} +1 -1
- package/dist/serve-ui/assets/{hash-pCwLSzjB.js → hash-CrXakbCy.js} +1 -1
- package/dist/serve-ui/assets/index-DL_dw3XX.js +80 -0
- package/dist/serve-ui/assets/index-KZ2rT4mJ.css +1 -0
- package/dist/serve-ui/assets/{languages-CQqNMim-.js → languages-DvyMGPwy.js} +1 -1
- package/dist/serve-ui/assets/{plus-Cw1OnTR2.js → plus-Ddujj7Qx.js} +1 -1
- package/dist/serve-ui/assets/project-Dg2BVay_.js +1 -0
- package/dist/serve-ui/assets/{scan-search-UJ-_oiOR.js → scan-search-CsPYGmEs.js} +1 -1
- package/dist/serve-ui/assets/{settings-Dnm4yTIj.js → settings-IQvevsKM.js} +1 -1
- package/dist/serve-ui/assets/{shield-check-B67ELGUT.js → shield-check-BmSBU8Ai.js} +1 -1
- package/dist/serve-ui/assets/stethoscope-DgCa_7BL.js +6 -0
- package/dist/serve-ui/assets/{trash-2-B1QgZW1G.js → trash-2-CNTkluuz.js} +1 -1
- package/dist/serve-ui/assets/useDirection-DfPL7a9d.js +1 -0
- package/dist/serve-ui/assets/{useId-CPFWZ4Bo.js → useId-B-RTM02R.js} +1 -1
- package/dist/serve-ui/assets/{useNonce-BffIzuhE.js → useNonce-DR6M3tLq.js} +1 -1
- package/dist/serve-ui/assets/useWatch-D4Y-epQR.js +1 -0
- package/dist/serve-ui/index.html +2 -2
- package/dist/{server-DTdmutjb.mjs → server-B1PSjXbu.mjs} +313 -112
- package/dist/{setup-4mS8RuCy.mjs → setup-BMqKibZt.mjs} +3 -3
- package/dist/{skills-4nhto0AZ.mjs → skills-CZ54F-97.mjs} +3 -3
- package/dist/{status-Cyv-jo1y.mjs → status-BJ6Lu3J4.mjs} +3 -3
- package/dist/{status-CoHgz6HN.mjs → status-Bmjfrk-B.mjs} +24 -11
- package/dist/studio-DPVJ-2oG.mjs +24 -0
- package/dist/{submissions-bIG-MWvv.mjs → submissions-zH_S7JIQ.mjs} +3 -3
- package/dist/{usage-B2xuc2DT.mjs → usage-z6eyT64a.mjs} +3 -3
- package/dist/{validate-B10L7RL7.mjs → validate-BG0X5g09.mjs} +102 -4
- package/dist/{webhooks-Dq3zFLP7.mjs → webhooks-CBG2Bs9P.mjs} +3 -3
- package/dist/{whoami-Ub5RznbU.mjs → whoami-CXVfvo7x.mjs} +2 -2
- package/package.json +7 -6
- package/dist/doctor-DyKjAIKH.mjs +0 -187
- package/dist/serve-ui/assets/AppLayout-C91DN08z.js +0 -21
- package/dist/serve-ui/assets/BranchDetailPage-BokOnhE7.js +0 -17
- package/dist/serve-ui/assets/CollapsibleTrigger.vue_vue_type_script_setup_true_lang-BRzXVXDG.js +0 -1
- package/dist/serve-ui/assets/ConfigProvider-Dvd7YoEV.js +0 -1
- package/dist/serve-ui/assets/ContentPage-BABg8pgn.js +0 -1
- package/dist/serve-ui/assets/DashboardPage-BFLFEJNZ.js +0 -21
- package/dist/serve-ui/assets/ModelsPage-DrQemHQ-.js +0 -1
- package/dist/serve-ui/assets/Presence-BXKCIfUk.js +0 -3
- package/dist/serve-ui/assets/ValidatePage-DRFAySNR.js +0 -21
- package/dist/serve-ui/assets/index-CG5NtNtt.css +0 -1
- package/dist/serve-ui/assets/index-De_z0714.js +0 -1
- package/dist/serve-ui/assets/index-RDdffBTh.js +0 -80
- package/dist/serve-ui/assets/project-BlZcHJw6.js +0 -1
- package/dist/serve-ui/assets/useDirection-B4S5kPsI.js +0 -1
- package/dist/serve-ui/assets/useWatch-B2FLRWr8.js +0 -1
- package/dist/studio-CJUGiIMd.mjs +0 -24
- package/dist/{client-C400qHcL.mjs → client-C9E9D4mb.mjs} +1 -1
- package/dist/{context-An3xrvcP.mjs → context-OskMGy8y.mjs} +1 -1
- package/dist/{demo-DcBt6fw8.mjs → demo-BOUcAoFZ.mjs} +3 -3
- package/dist/{oauth-server-Beppljfz.mjs → oauth-server-smSxDoH5.mjs} +1 -1
- /package/dist/{ui-B5mXontH.mjs → ui-B8l_LC36.mjs} +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { i as pc, o as statusIcon } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { r as resolveProjectRoot } from "./context-OskMGy8y.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
5
|
+
import { runDoctor } from "@contentrain/mcp/core/doctor";
|
|
6
|
+
//#region src/commands/doctor.ts
|
|
7
|
+
/**
|
|
8
|
+
* Thin CLI wrapper over the shared `runDoctor()` from `@contentrain/mcp`.
|
|
9
|
+
*
|
|
10
|
+
* All health-check logic lives in the MCP core so the same report
|
|
11
|
+
* drives three consumers: this command, the `contentrain_doctor` MCP
|
|
12
|
+
* tool, and the Serve UI's `/api/doctor` route. Behaviour in the
|
|
13
|
+
* default (non-`--json`) mode is unchanged — same check labels, same
|
|
14
|
+
* details, same grouped usage output.
|
|
15
|
+
*/
|
|
16
|
+
var doctor_default = defineCommand({
|
|
17
|
+
meta: {
|
|
18
|
+
name: "doctor",
|
|
19
|
+
description: "Check project health and environment"
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
root: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Project root path",
|
|
25
|
+
required: false
|
|
26
|
+
},
|
|
27
|
+
usage: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Analyze content key usage in source files",
|
|
30
|
+
required: false
|
|
31
|
+
},
|
|
32
|
+
json: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Emit the raw DoctorReport JSON",
|
|
35
|
+
required: false
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
async run({ args }) {
|
|
39
|
+
const projectRoot = await resolveProjectRoot(args.root);
|
|
40
|
+
if (Boolean(args.json)) {
|
|
41
|
+
const report = await runDoctor(projectRoot, { usage: Boolean(args.usage) });
|
|
42
|
+
process.stdout.write(JSON.stringify(report, null, 2));
|
|
43
|
+
if (report.summary.failed > 0) process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
intro(pc.bold("contentrain doctor"));
|
|
47
|
+
const s = spinner();
|
|
48
|
+
s.start("Running health checks...");
|
|
49
|
+
if (args.usage) s.message("Analyzing content key usage...");
|
|
50
|
+
const report = await runDoctor(projectRoot, { usage: Boolean(args.usage) });
|
|
51
|
+
s.stop("Health checks complete");
|
|
52
|
+
for (const check of report.checks) log.message(`${statusIcon(check.pass)} ${pc.bold(check.name)}: ${check.detail}`);
|
|
53
|
+
if (report.usage?.unusedKeys.length) {
|
|
54
|
+
log.message("");
|
|
55
|
+
log.message(pc.bold(" Unused keys:"));
|
|
56
|
+
const grouped = groupBy(report.usage.unusedKeys, (e) => e.model);
|
|
57
|
+
for (const [model, entries] of Object.entries(grouped)) {
|
|
58
|
+
const keyList = entries.length <= 5 ? entries.map((e) => e.key).join(", ") : `${entries.slice(0, 5).map((e) => e.key).join(", ")} (+${entries.length - 5} more)`;
|
|
59
|
+
log.message(` ${pc.dim(model)}: ${pc.yellow(keyList)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (report.usage?.duplicateValues.length) {
|
|
63
|
+
log.message("");
|
|
64
|
+
log.message(pc.bold(" Duplicate values:"));
|
|
65
|
+
for (const dv of report.usage.duplicateValues.slice(0, 10)) {
|
|
66
|
+
const truncated = dv.value.length > 30 ? `${dv.value.slice(0, 30)}...` : dv.value;
|
|
67
|
+
log.message(` ${pc.dim(`${dv.model}/${dv.locale}`)}: ${pc.yellow(`"${truncated}"`)} → [${dv.keys.join(", ")}]`);
|
|
68
|
+
}
|
|
69
|
+
if (report.usage.duplicateValues.length > 10) log.message(` ... and ${report.usage.duplicateValues.length - 10} more`);
|
|
70
|
+
}
|
|
71
|
+
if (report.usage?.missingLocaleKeys.length) {
|
|
72
|
+
log.message("");
|
|
73
|
+
log.message(pc.bold(" Missing translations:"));
|
|
74
|
+
const grouped = groupBy(report.usage.missingLocaleKeys, (e) => `${e.model}/${e.missingIn}`);
|
|
75
|
+
for (const [label, entries] of Object.entries(grouped)) log.message(` ${pc.dim(label)}: ${pc.yellow(`${entries.length} key(s)`)}`);
|
|
76
|
+
}
|
|
77
|
+
const { passed, failed } = report.summary;
|
|
78
|
+
if (failed === 0) outro(pc.green(`All ${passed} checks passed!`));
|
|
79
|
+
else {
|
|
80
|
+
outro(pc.yellow(`${passed} passed, ${failed} failed`));
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
function groupBy(arr, keyFn) {
|
|
86
|
+
const result = {};
|
|
87
|
+
for (const item of arr) {
|
|
88
|
+
const key = keyFn(item);
|
|
89
|
+
if (!result[key]) result[key] = [];
|
|
90
|
+
result[key].push(item);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { doctor_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { n as requireInitialized, r as resolveProjectRoot, t as loadProjectContext } from "./context-OskMGy8y.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { intro, log, outro, spinner } from "@clack/prompts";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -24,18 +24,33 @@ var generate_default = defineCommand({
|
|
|
24
24
|
type: "boolean",
|
|
25
25
|
description: "Watch for changes and regenerate",
|
|
26
26
|
required: false
|
|
27
|
+
},
|
|
28
|
+
json: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: "Emit the generate result as JSON (silences pretty output)",
|
|
31
|
+
required: false
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
async run({ args }) {
|
|
30
35
|
const projectRoot = await resolveProjectRoot(args.root);
|
|
31
36
|
requireInitialized(await loadProjectContext(projectRoot));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
s
|
|
37
|
+
const useJson = Boolean(args.json);
|
|
38
|
+
if (!useJson) intro(pc.bold("contentrain generate"));
|
|
39
|
+
const s = useJson ? null : spinner();
|
|
40
|
+
s?.start("Generating SDK client...");
|
|
35
41
|
try {
|
|
36
42
|
const { generate } = await import("@contentrain/query/generate");
|
|
37
43
|
const result = await generate({ projectRoot });
|
|
38
|
-
s
|
|
44
|
+
s?.stop("SDK client generated");
|
|
45
|
+
if (useJson) {
|
|
46
|
+
process.stdout.write(JSON.stringify({
|
|
47
|
+
generatedFiles: result.generatedFiles,
|
|
48
|
+
typesCount: result.typesCount,
|
|
49
|
+
dataModulesCount: result.dataModulesCount,
|
|
50
|
+
packageJsonUpdated: Boolean(result.packageJsonUpdated)
|
|
51
|
+
}, null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
39
54
|
log.success(`Output: ${pc.cyan(".contentrain/client/")}`);
|
|
40
55
|
log.message(` Files: ${result.generatedFiles.length}`);
|
|
41
56
|
log.message(` Types: ${result.typesCount}`);
|
|
@@ -66,11 +81,13 @@ var generate_default = defineCommand({
|
|
|
66
81
|
await new Promise(() => {});
|
|
67
82
|
}
|
|
68
83
|
} catch (error) {
|
|
69
|
-
s
|
|
70
|
-
|
|
84
|
+
s?.stop("Generation failed");
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
if (useJson) process.stdout.write(JSON.stringify({ error: message }, null, 2));
|
|
87
|
+
else log.error(message);
|
|
71
88
|
process.exitCode = 1;
|
|
72
89
|
}
|
|
73
|
-
outro("");
|
|
90
|
+
if (!useJson) outro("");
|
|
74
91
|
}
|
|
75
92
|
});
|
|
76
93
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { simpleGit } from "simple-git";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { ensureDir, pathExists } from "@contentrain/mcp/util/fs";
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
4
|
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
//#region src/utils/ide.ts
|
|
6
6
|
const AGENT_SKILL_NAMES = [
|
|
@@ -205,12 +205,10 @@ async function installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resol
|
|
|
205
205
|
} catch {}
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
if (filesToAdd.length > 0) {
|
|
208
|
+
if (filesToAdd.length > 0) try {
|
|
209
209
|
await git.add(filesToAdd);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch {}
|
|
213
|
-
}
|
|
210
|
+
await git.commit(`[contentrain] install ${ide.name} rules and skills`);
|
|
211
|
+
} catch {}
|
|
214
212
|
return {
|
|
215
213
|
installed,
|
|
216
214
|
updated
|
|
@@ -232,15 +230,15 @@ async function addClaudeMdReference(projectRoot) {
|
|
|
232
230
|
if (await pathExists(claudeMdPath)) {
|
|
233
231
|
if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
|
|
234
232
|
await appendFile(claudeMdPath, `\n\n${reference}\n`);
|
|
235
|
-
await git.add(claudeMdPath);
|
|
236
233
|
try {
|
|
234
|
+
await git.add(claudeMdPath);
|
|
237
235
|
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
238
236
|
} catch {}
|
|
239
237
|
}
|
|
240
238
|
} else {
|
|
241
239
|
await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
|
|
242
|
-
await git.add(claudeMdPath);
|
|
243
240
|
try {
|
|
241
|
+
await git.add(claudeMdPath);
|
|
244
242
|
await git.commit("[contentrain] add CLAUDE.md reference");
|
|
245
243
|
} catch {}
|
|
246
244
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,25 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
3
|
import { defineCommand, runMain } from "citty";
|
|
4
|
+
//#region package.json
|
|
5
|
+
var version = "0.5.0";
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/utils/debug.ts
|
|
8
|
+
/**
|
|
9
|
+
* Debug logging — silent by default, enabled by either the
|
|
10
|
+
* `--debug` flag (recognised per-command) or the
|
|
11
|
+
* `CONTENTRAIN_DEBUG=1` env var. Intended for diagnosing
|
|
12
|
+
* git transactions, MCP calls, and file writes when the
|
|
13
|
+
* user-facing output isn't enough.
|
|
14
|
+
*
|
|
15
|
+
* The tiny helper here is deliberately local — we do not want
|
|
16
|
+
* to ship a logging dependency for something this small.
|
|
17
|
+
* Everything routes through stderr so `--json` stdout payloads
|
|
18
|
+
* stay clean and CI can still capture diagnostic output.
|
|
19
|
+
*/
|
|
20
|
+
let enabled = Boolean(process.env["CONTENTRAIN_DEBUG"]);
|
|
21
|
+
/** Turn debug on for the rest of the process. Command runners call
|
|
22
|
+
* this when `--debug` is passed. Once on, stays on. */
|
|
23
|
+
function enableDebug() {
|
|
24
|
+
enabled = true;
|
|
25
|
+
}
|
|
26
|
+
/** Opening banner for a debug block. `context` is a short label — the
|
|
27
|
+
* command name or helper — that lets the reader grep the noise. */
|
|
28
|
+
function debug(context, message) {
|
|
29
|
+
if (!enabled) return;
|
|
30
|
+
process.stderr.write(`${pc.dim(`[debug:${context}]`)} ${message}\n`);
|
|
31
|
+
}
|
|
3
32
|
//#endregion
|
|
4
33
|
//#region src/index.ts
|
|
34
|
+
const debugIndex = process.argv.findIndex((a) => a === "--debug");
|
|
35
|
+
if (debugIndex !== -1) {
|
|
36
|
+
enableDebug();
|
|
37
|
+
process.argv.splice(debugIndex, 1);
|
|
38
|
+
}
|
|
5
39
|
runMain(defineCommand({
|
|
6
40
|
meta: {
|
|
7
41
|
name: "contentrain",
|
|
8
|
-
version
|
|
42
|
+
version,
|
|
9
43
|
description: "Contentrain CLI — AI content governance infrastructure"
|
|
10
44
|
},
|
|
11
45
|
subCommands: {
|
|
12
|
-
init: () => import("./init-
|
|
13
|
-
status: () => import("./status-
|
|
14
|
-
doctor: () => import("./doctor-
|
|
15
|
-
validate: () => import("./validate-
|
|
16
|
-
serve: () => import("./serve-
|
|
17
|
-
generate: () => import("./generate-
|
|
18
|
-
diff: () => import("./diff
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
46
|
+
init: () => import("./init-540qiNQJ.mjs").then((m) => m.default),
|
|
47
|
+
status: () => import("./status-Bmjfrk-B.mjs").then((m) => m.default),
|
|
48
|
+
doctor: () => import("./doctor-MOf_LQEL.mjs").then((m) => m.default),
|
|
49
|
+
validate: () => import("./validate-BG0X5g09.mjs").then((m) => m.default),
|
|
50
|
+
serve: () => import("./serve-CxA2pKbp.mjs").then((m) => m.default),
|
|
51
|
+
generate: () => import("./generate-ByscV5SN.mjs").then((m) => m.default),
|
|
52
|
+
diff: () => import("./diff-ov1j9bAb.mjs").then((m) => m.default),
|
|
53
|
+
merge: () => import("./merge-DdPRttF-.mjs").then((m) => m.default),
|
|
54
|
+
describe: () => import("./describe-DJ5H9Cxm.mjs").then((m) => m.default),
|
|
55
|
+
"describe-format": () => import("./describe-format-CUAqB0C4.mjs").then((m) => m.default),
|
|
56
|
+
scaffold: () => import("./scaffold-iuvrMh78.mjs").then((m) => m.default),
|
|
57
|
+
setup: () => import("./setup-BMqKibZt.mjs").then((m) => m.default),
|
|
58
|
+
skills: () => import("./skills-CZ54F-97.mjs").then((m) => m.default),
|
|
59
|
+
studio: () => import("./studio-DPVJ-2oG.mjs").then((m) => m.default)
|
|
22
60
|
}
|
|
23
61
|
}));
|
|
24
62
|
//#endregion
|
|
25
|
-
export {};
|
|
63
|
+
export { debug as t };
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as createPackageResolver, c as writeMcpConfig, i as addClaudeMdReference, n as IDE_CONFIGS, o as detectIdes, s as installIdeRulesAndSkills } from "./ide-
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { r as resolveProjectRoot, t as loadProjectContext } from "./context-OskMGy8y.mjs";
|
|
3
|
+
import { a as createPackageResolver, c as writeMcpConfig, i as addClaudeMdReference, n as IDE_CONFIGS, o as detectIdes, s as installIdeRulesAndSkills } from "./ide-DPDVYiaq.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { confirm, intro, isCancel, log, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
6
|
-
import { simpleGit } from "simple-git";
|
|
7
6
|
import { join } from "node:path";
|
|
8
7
|
import { writeModel } from "@contentrain/mcp/core/model-manager";
|
|
9
8
|
import { ensureDir, pathExists, writeJson } from "@contentrain/mcp/util/fs";
|
|
10
|
-
import {
|
|
9
|
+
import { simpleGit } from "simple-git";
|
|
10
|
+
import { buildBranchName, createTransaction } from "@contentrain/mcp/git/transaction";
|
|
11
11
|
import { detectStackInfo } from "@contentrain/mcp/util/detect";
|
|
12
12
|
import { getTemplate, listTemplates } from "@contentrain/mcp/templates";
|
|
13
|
-
import { buildBranchName, createTransaction } from "@contentrain/mcp/git/transaction";
|
|
14
13
|
import { scanSummary } from "@contentrain/mcp/core/scanner";
|
|
14
|
+
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
15
15
|
//#region src/commands/init.ts
|
|
16
16
|
const COMMON_LOCALES = [
|
|
17
17
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as pc } from "./ui-
|
|
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-
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { i as checkPermissions, o as loadCredentials, s as saveCredentials, t as StudioApiClient } from "./client-C9E9D4mb.mjs";
|
|
3
|
+
import { n as openBrowser, t as startOAuthServer } from "./oauth-server-smSxDoH5.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
|
-
import { i as pc } from "./ui-
|
|
2
|
-
import { a as clearCredentials, o as loadCredentials, t as StudioApiClient } from "./client-
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { a as clearCredentials, o as loadCredentials, t as StudioApiClient } from "./client-C9E9D4mb.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { intro, log, outro } from "@clack/prompts";
|
|
5
5
|
//#region src/studio/commands/logout.ts
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createServer } from "@contentrain/mcp/server";
|
|
2
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
3
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
+
//#region src/utils/mcp-client.ts
|
|
5
|
+
/**
|
|
6
|
+
* Start an in-memory MCP session against a local project root. Always
|
|
7
|
+
* `await session.close()` — typically in a `finally` block — so the
|
|
8
|
+
* underlying transports release cleanly.
|
|
9
|
+
*/
|
|
10
|
+
async function openMcpSession(projectRoot) {
|
|
11
|
+
const server = createServer(projectRoot);
|
|
12
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
13
|
+
const client = new Client({
|
|
14
|
+
name: "contentrain-cli",
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
});
|
|
17
|
+
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
|
18
|
+
return {
|
|
19
|
+
async call(name, args = {}, options = {}) {
|
|
20
|
+
const result = await client.callTool({
|
|
21
|
+
name,
|
|
22
|
+
arguments: args
|
|
23
|
+
});
|
|
24
|
+
const textContent = (result.content ?? []).find((c) => c.type === "text");
|
|
25
|
+
if (!textContent || typeof textContent.text !== "string") throw new Error(`Tool ${name} returned no text content`);
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(textContent.text);
|
|
29
|
+
} catch {
|
|
30
|
+
parsed = textContent.text;
|
|
31
|
+
}
|
|
32
|
+
if (result.isError && options.throwOnError !== false) {
|
|
33
|
+
const msg = typeof parsed === "object" && parsed !== null && "error" in parsed ? String(parsed.error) : textContent.text;
|
|
34
|
+
throw new Error(msg);
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
},
|
|
38
|
+
async close() {
|
|
39
|
+
await Promise.all([client.close().catch(() => {}), server.close().catch(() => {})]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { openMcpSession as t };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { r as resolveProjectRoot } from "./context-OskMGy8y.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { confirm, intro, isCancel, log, outro } from "@clack/prompts";
|
|
5
|
+
import { simpleGit } from "simple-git";
|
|
6
|
+
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
7
|
+
import { mergeBranch } from "@contentrain/mcp/git/transaction";
|
|
8
|
+
//#region src/commands/merge.ts
|
|
9
|
+
/**
|
|
10
|
+
* One-shot merge of a single contentrain feature branch.
|
|
11
|
+
*
|
|
12
|
+
* `contentrain diff` drives an interactive review over every pending
|
|
13
|
+
* branch; this command is the scriptable, one-branch sibling —
|
|
14
|
+
* shell-friendly for agents, CI, and manual triage. It reuses the exact
|
|
15
|
+
* same MCP helper (`mergeBranch`) that the interactive command
|
|
16
|
+
* delegates to, so the worktree transaction + selective-sync + dirty-
|
|
17
|
+
* file protections stay on one code path.
|
|
18
|
+
*/
|
|
19
|
+
var merge_default = defineCommand({
|
|
20
|
+
meta: {
|
|
21
|
+
name: "merge",
|
|
22
|
+
description: "Merge one contentrain feature branch into the content branch"
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
branch: {
|
|
26
|
+
type: "positional",
|
|
27
|
+
description: "Feature branch name (e.g. cr/content/blog/...)",
|
|
28
|
+
required: true
|
|
29
|
+
},
|
|
30
|
+
root: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Project root path",
|
|
33
|
+
required: false
|
|
34
|
+
},
|
|
35
|
+
yes: {
|
|
36
|
+
type: "boolean",
|
|
37
|
+
description: "Skip confirmation prompt",
|
|
38
|
+
required: false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
async run({ args }) {
|
|
42
|
+
const projectRoot = await resolveProjectRoot(args.root);
|
|
43
|
+
const branch = String(args.branch);
|
|
44
|
+
intro(pc.bold("contentrain merge"));
|
|
45
|
+
if (branch === CONTENTRAIN_BRANCH) {
|
|
46
|
+
log.error(`Cannot merge the content-tracking branch (${CONTENTRAIN_BRANCH}) into itself.`);
|
|
47
|
+
outro("");
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!(await simpleGit(projectRoot).branchLocal()).all.includes(branch)) {
|
|
52
|
+
log.error(`Branch "${branch}" does not exist locally.`);
|
|
53
|
+
outro("");
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!args.yes) {
|
|
58
|
+
const ok = await confirm({ message: `Merge ${branch} into ${CONTENTRAIN_BRANCH} and fast-forward the base branch?` });
|
|
59
|
+
if (isCancel(ok) || !ok) {
|
|
60
|
+
outro("");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const result = await mergeBranch(projectRoot, branch);
|
|
66
|
+
log.success(`Merged ${branch} (commit ${result.commit.slice(0, 8)})`);
|
|
67
|
+
if (result.sync?.synced?.length) log.message(pc.dim(` Synced ${result.sync.synced.length} file(s) to working tree.`));
|
|
68
|
+
if (result.sync?.skipped?.length) {
|
|
69
|
+
log.warning(`${result.sync.skipped.length} file(s) skipped during sync — you have uncommitted changes:`);
|
|
70
|
+
for (const f of result.sync.skipped) log.message(pc.dim(` ${f}`));
|
|
71
|
+
if (result.sync.warning) log.message(pc.dim(` ${result.sync.warning}`));
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
log.error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
outro("");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
//#endregion
|
|
82
|
+
export { merge_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as saveDefaults, o as loadCredentials } from "./client-
|
|
1
|
+
import { c as saveDefaults, o as loadCredentials } from "./client-C9E9D4mb.mjs";
|
|
2
2
|
import { confirm, isCancel, select } from "@clack/prompts";
|
|
3
3
|
//#region src/studio/resolve-context.ts
|
|
4
4
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { i as pc } from "./ui-B8l_LC36.mjs";
|
|
2
|
+
import { r as resolveProjectRoot } from "./context-OskMGy8y.mjs";
|
|
3
|
+
import { t as openMcpSession } from "./mcp-client-DVWTex2i.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
|
|
6
|
+
//#region src/commands/scaffold.ts
|
|
7
|
+
const KNOWN_TEMPLATES = [
|
|
8
|
+
"blog",
|
|
9
|
+
"landing",
|
|
10
|
+
"docs",
|
|
11
|
+
"ecommerce",
|
|
12
|
+
"saas",
|
|
13
|
+
"i18n",
|
|
14
|
+
"mobile"
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Template-based project seeding, wrapping `contentrain_scaffold`.
|
|
18
|
+
*
|
|
19
|
+
* Agents normally drive scaffolding through the MCP tool directly; this
|
|
20
|
+
* command gives humans the same entry point with an interactive
|
|
21
|
+
* template picker and a bit more actionable next-step output.
|
|
22
|
+
*/
|
|
23
|
+
var scaffold_default = defineCommand({
|
|
24
|
+
meta: {
|
|
25
|
+
name: "scaffold",
|
|
26
|
+
description: "Apply a Contentrain template (models + sample content + vocabulary)"
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
root: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Project root path",
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
template: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: `Template ID (${KNOWN_TEMPLATES.join(", ")})`,
|
|
37
|
+
required: false
|
|
38
|
+
},
|
|
39
|
+
locales: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Comma-separated locale override (e.g. \"en,tr,de\")",
|
|
42
|
+
required: false
|
|
43
|
+
},
|
|
44
|
+
"no-sample": {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
description: "Skip sample content",
|
|
47
|
+
required: false
|
|
48
|
+
},
|
|
49
|
+
json: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
description: "Emit raw JSON for scripts",
|
|
52
|
+
required: false
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async run({ args }) {
|
|
56
|
+
const projectRoot = await resolveProjectRoot(args.root);
|
|
57
|
+
let templateId = args.template ? String(args.template) : void 0;
|
|
58
|
+
if (!templateId && !args.json) {
|
|
59
|
+
intro(pc.bold("contentrain scaffold"));
|
|
60
|
+
const pick = await select({
|
|
61
|
+
message: "Choose a template",
|
|
62
|
+
options: KNOWN_TEMPLATES.map((t) => ({
|
|
63
|
+
value: t,
|
|
64
|
+
label: t
|
|
65
|
+
}))
|
|
66
|
+
});
|
|
67
|
+
if (isCancel(pick)) {
|
|
68
|
+
outro("");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
templateId = pick;
|
|
72
|
+
}
|
|
73
|
+
if (!templateId) {
|
|
74
|
+
log.error("Template is required. Pass --template <id> or run interactively.");
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const locales = args.locales ? String(args.locales).split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
79
|
+
const session = await openMcpSession(projectRoot);
|
|
80
|
+
const s = args.json ? null : spinner();
|
|
81
|
+
s?.start(`Applying template "${templateId}"...`);
|
|
82
|
+
try {
|
|
83
|
+
const result = await session.call("contentrain_scaffold", {
|
|
84
|
+
template: templateId,
|
|
85
|
+
...locales ? { locales } : {},
|
|
86
|
+
with_sample_content: !args["no-sample"]
|
|
87
|
+
});
|
|
88
|
+
s?.stop(`Template "${templateId}" applied.`);
|
|
89
|
+
if (args.json) {
|
|
90
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const git = result["git"];
|
|
94
|
+
log.success(`Scaffolded ${pc.bold(templateId)} on ${pc.cyan(git?.branch ?? "(branch)")} (${git?.action ?? "committed"})`);
|
|
95
|
+
const models = result["models_created"];
|
|
96
|
+
if (models?.length) log.message(` Models: ${models.map((m) => m.id).join(", ")}`);
|
|
97
|
+
if (typeof result["content_created"] === "number") log.message(` Sample entries: ${result["content_created"]}`);
|
|
98
|
+
if (typeof result["vocabulary_terms_added"] === "number" && result["vocabulary_terms_added"]) log.message(` Vocabulary terms added: ${result["vocabulary_terms_added"]}`);
|
|
99
|
+
const next = result["next_steps"];
|
|
100
|
+
if (next?.length) {
|
|
101
|
+
log.info(pc.bold("\nNext steps"));
|
|
102
|
+
for (const step of next) log.message(` • ${step}`);
|
|
103
|
+
}
|
|
104
|
+
outro("");
|
|
105
|
+
} catch (error) {
|
|
106
|
+
s?.stop("Scaffold failed.");
|
|
107
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
} finally {
|
|
110
|
+
await session.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
//#endregion
|
|
115
|
+
export { scaffold_default as default };
|