contentrain 0.4.4 → 0.5.1

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 (109) hide show
  1. package/README.md +112 -34
  2. package/dist/{activity-CbkxcfDf.mjs → activity-B8j9gfsK.mjs} +3 -3
  3. package/dist/{branches-D2UWex1N.mjs → branches-CvpFbyrJ.mjs} +3 -3
  4. package/dist/{cdn-build-BzC2QPNi.mjs → cdn-build-DS9FprWU.mjs} +3 -3
  5. package/dist/{cdn-init-CsmqJIaQ.mjs → cdn-init-Bm4dpEJn.mjs} +3 -3
  6. package/dist/{connect-DJo9PSiw.mjs → connect-Bb0BrZsx.mjs} +3 -3
  7. package/dist/describe-DJ5H9Cxm.mjs +106 -0
  8. package/dist/describe-format-CUAqB0C4.mjs +50 -0
  9. package/dist/{diff--halNUpG.mjs → diff-ov1j9bAb.mjs} +67 -59
  10. package/dist/doctor-MOf_LQEL.mjs +95 -0
  11. package/dist/{generate-DrUojrk8.mjs → generate-ByscV5SN.mjs} +26 -9
  12. package/dist/index.mjs +50 -12
  13. package/dist/{init-CqoDCSV5.mjs → init-540qiNQJ.mjs} +6 -6
  14. package/dist/{login-DCXkOrQV.mjs → login-xvzL-UvR.mjs} +3 -3
  15. package/dist/{logout-nPt2UAeQ.mjs → logout-DzspSatR.mjs} +2 -2
  16. package/dist/mcp-client-DVWTex2i.mjs +44 -0
  17. package/dist/merge-DdPRttF-.mjs +82 -0
  18. package/dist/{resolve-context-Cg6JoVZt.mjs → resolve-context-hKVYPniC.mjs} +1 -1
  19. package/dist/scaffold-iuvrMh78.mjs +115 -0
  20. package/dist/{serve-CNeFIQ1i.mjs → serve-CxA2pKbp.mjs} +58 -5
  21. 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
  22. package/dist/serve-ui/assets/AppLayout-B-fbrfMk.js +21 -0
  23. package/dist/serve-ui/assets/BranchDetailPage-B3PL9PGp.js +17 -0
  24. package/dist/serve-ui/assets/{BranchesPage-CSwikNkd.js → BranchesPage-4YpE_szf.js} +2 -2
  25. package/dist/serve-ui/assets/CardContent.vue_vue_type_script_setup_true_lang-D1L2MnBU.js +1 -0
  26. package/dist/serve-ui/assets/CollapsibleTrigger.vue_vue_type_script_setup_true_lang-B4uDtllm.js +1 -0
  27. package/dist/serve-ui/assets/{Collection-DTB4xCCE.js → Collection-9URFko-Y.js} +2 -2
  28. package/dist/serve-ui/assets/ConfigProvider-CwkmRpT5.js +1 -0
  29. package/dist/serve-ui/assets/{ContentListPage-DF-LjAaS.js → ContentListPage-BMfSfg8x.js} +2 -2
  30. package/dist/serve-ui/assets/ContentPage-BXUnNJlB.js +1 -0
  31. package/dist/serve-ui/assets/DashboardPage-1UyYgg5J.js +21 -0
  32. 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
  33. package/dist/serve-ui/assets/DoctorPage-WnQ8xYWy.js +1 -0
  34. package/dist/serve-ui/assets/FormatReferencePage-DoKEaawl.js +1 -0
  35. 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
  36. package/dist/serve-ui/assets/{ModelDetailPage-_TIV9itx.js → ModelDetailPage-BysqN8IL.js} +3 -3
  37. package/dist/serve-ui/assets/ModelsPage-DFZrdBIi.js +1 -0
  38. package/dist/serve-ui/assets/{NormalizePage-Cde8vdk2.js → NormalizePage-4f5Ib_ES.js} +2 -2
  39. 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
  40. package/dist/serve-ui/assets/Presence-CA3xTrht.js +3 -0
  41. 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
  42. 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
  43. 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
  44. package/dist/serve-ui/assets/ValidatePage-BMTpV2KL.js +16 -0
  45. package/dist/serve-ui/assets/{arrow-down-BAJwpryD.js → arrow-down-IJu6H7Es.js} +1 -1
  46. package/dist/serve-ui/assets/{arrow-left-DANf_wrK.js → arrow-left-D8ZjVYAI.js} +1 -1
  47. package/dist/serve-ui/assets/{book-marked-DS8FXgjz.js → book-marked-BNyyZrq3.js} +1 -1
  48. package/dist/serve-ui/assets/{book-open-DnH5T78M.js → book-open-Dw1fQtSI.js} +1 -1
  49. package/dist/serve-ui/assets/{box-UJk5SvJJ.js → box-BoGMZDZR.js} +1 -1
  50. package/dist/serve-ui/assets/{chevron-down-C1nYx5Eu.js → chevron-down-UcXxhyyY.js} +1 -1
  51. package/dist/serve-ui/assets/{chevron-right-XnJVrezx.js → chevron-right-SpdM5LBc.js} +1 -1
  52. package/dist/serve-ui/assets/circle-alert-CEwKJpYl.js +6 -0
  53. package/dist/serve-ui/assets/{content-Ctqretwl.js → content-BQEzysvq.js} +1 -1
  54. package/dist/serve-ui/assets/{database-Wd_i2bM1.js → database-U6JU8oc9.js} +1 -1
  55. package/dist/serve-ui/assets/{file-code-ScWZN40B.js → file-code-_sq1QHQo.js} +1 -1
  56. package/dist/serve-ui/assets/{file-text-J3Wzwieh.js → file-text-BiMGJckO.js} +1 -1
  57. package/dist/serve-ui/assets/{git-branch-DqEFTZDt.js → git-branch-CdZMOuSP.js} +1 -1
  58. package/dist/serve-ui/assets/{git-merge-Bmz-MVBm.js → git-merge-D8Y-BwIh.js} +1 -1
  59. package/dist/serve-ui/assets/{hash-pCwLSzjB.js → hash-CrXakbCy.js} +1 -1
  60. package/dist/serve-ui/assets/index-DL_dw3XX.js +80 -0
  61. package/dist/serve-ui/assets/index-KZ2rT4mJ.css +1 -0
  62. package/dist/serve-ui/assets/{languages-CQqNMim-.js → languages-DvyMGPwy.js} +1 -1
  63. package/dist/serve-ui/assets/{plus-Cw1OnTR2.js → plus-Ddujj7Qx.js} +1 -1
  64. package/dist/serve-ui/assets/project-Dg2BVay_.js +1 -0
  65. package/dist/serve-ui/assets/{scan-search-UJ-_oiOR.js → scan-search-CsPYGmEs.js} +1 -1
  66. package/dist/serve-ui/assets/{settings-Dnm4yTIj.js → settings-IQvevsKM.js} +1 -1
  67. package/dist/serve-ui/assets/{shield-check-B67ELGUT.js → shield-check-BmSBU8Ai.js} +1 -1
  68. package/dist/serve-ui/assets/stethoscope-DgCa_7BL.js +6 -0
  69. package/dist/serve-ui/assets/{trash-2-B1QgZW1G.js → trash-2-CNTkluuz.js} +1 -1
  70. package/dist/serve-ui/assets/useDirection-DfPL7a9d.js +1 -0
  71. package/dist/serve-ui/assets/{useId-CPFWZ4Bo.js → useId-B-RTM02R.js} +1 -1
  72. package/dist/serve-ui/assets/{useNonce-BffIzuhE.js → useNonce-DR6M3tLq.js} +1 -1
  73. package/dist/serve-ui/assets/useWatch-D4Y-epQR.js +1 -0
  74. package/dist/serve-ui/index.html +2 -2
  75. package/dist/{server-DTdmutjb.mjs → server-B1PSjXbu.mjs} +313 -112
  76. package/dist/{setup-Y1LFQ9rZ.mjs → setup-BMqKibZt.mjs} +3 -3
  77. package/dist/{skills-Cp8KF1af.mjs → skills-CZ54F-97.mjs} +3 -3
  78. package/dist/{status-Cyv-jo1y.mjs → status-BJ6Lu3J4.mjs} +3 -3
  79. package/dist/{status-CoHgz6HN.mjs → status-Bmjfrk-B.mjs} +24 -11
  80. package/dist/studio-DPVJ-2oG.mjs +24 -0
  81. package/dist/{submissions-bIG-MWvv.mjs → submissions-zH_S7JIQ.mjs} +3 -3
  82. package/dist/{usage-B2xuc2DT.mjs → usage-z6eyT64a.mjs} +3 -3
  83. package/dist/{validate-B10L7RL7.mjs → validate-BG0X5g09.mjs} +102 -4
  84. package/dist/{webhooks-Dq3zFLP7.mjs → webhooks-CBG2Bs9P.mjs} +3 -3
  85. package/dist/{whoami-Ub5RznbU.mjs → whoami-CXVfvo7x.mjs} +2 -2
  86. package/package.json +7 -6
  87. package/dist/doctor-DyKjAIKH.mjs +0 -187
  88. package/dist/serve-ui/assets/AppLayout-C91DN08z.js +0 -21
  89. package/dist/serve-ui/assets/BranchDetailPage-BokOnhE7.js +0 -17
  90. package/dist/serve-ui/assets/CollapsibleTrigger.vue_vue_type_script_setup_true_lang-BRzXVXDG.js +0 -1
  91. package/dist/serve-ui/assets/ConfigProvider-Dvd7YoEV.js +0 -1
  92. package/dist/serve-ui/assets/ContentPage-BABg8pgn.js +0 -1
  93. package/dist/serve-ui/assets/DashboardPage-BFLFEJNZ.js +0 -21
  94. package/dist/serve-ui/assets/ModelsPage-DrQemHQ-.js +0 -1
  95. package/dist/serve-ui/assets/Presence-BXKCIfUk.js +0 -3
  96. package/dist/serve-ui/assets/ValidatePage-DRFAySNR.js +0 -21
  97. package/dist/serve-ui/assets/index-CG5NtNtt.css +0 -1
  98. package/dist/serve-ui/assets/index-De_z0714.js +0 -1
  99. package/dist/serve-ui/assets/index-RDdffBTh.js +0 -80
  100. package/dist/serve-ui/assets/project-BlZcHJw6.js +0 -1
  101. package/dist/serve-ui/assets/useDirection-B4S5kPsI.js +0 -1
  102. package/dist/serve-ui/assets/useWatch-B2FLRWr8.js +0 -1
  103. package/dist/studio-CJUGiIMd.mjs +0 -24
  104. package/dist/{client-C400qHcL.mjs → client-C9E9D4mb.mjs} +1 -1
  105. package/dist/{context-An3xrvcP.mjs → context-OskMGy8y.mjs} +1 -1
  106. package/dist/{demo-DcBt6fw8.mjs → demo-BOUcAoFZ.mjs} +3 -3
  107. package/dist/{ide-_C2gUPbQ.mjs → ide-DPDVYiaq.mjs} +1 -1
  108. package/dist/{oauth-server-Beppljfz.mjs → oauth-server-smSxDoH5.mjs} +1 -1
  109. /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 { n as requireInitialized, r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
2
- import { i as pc } from "./ui-B5mXontH.mjs";
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
- intro(pc.bold("contentrain generate"));
33
- const s = spinner();
34
- s.start("Generating SDK client...");
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.stop("SDK client generated");
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.stop("Generation failed");
70
- log.error(error instanceof Error ? error.message : String(error));
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
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.1";
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: "0.4.4",
42
+ version,
9
43
  description: "Contentrain CLI — AI content governance infrastructure"
10
44
  },
11
45
  subCommands: {
12
- init: () => import("./init-CqoDCSV5.mjs").then((m) => m.default),
13
- status: () => import("./status-CoHgz6HN.mjs").then((m) => m.default),
14
- doctor: () => import("./doctor-DyKjAIKH.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-Y1LFQ9rZ.mjs").then((m) => m.default),
20
- skills: () => import("./skills-Cp8KF1af.mjs").then((m) => m.default),
21
- studio: () => import("./studio-CJUGiIMd.mjs").then((m) => m.default)
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 { r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
2
- import { i as pc } from "./ui-B5mXontH.mjs";
3
- import { a as createPackageResolver, c as writeMcpConfig, i as addClaudeMdReference, n as IDE_CONFIGS, o as detectIdes, s as installIdeRulesAndSkills } from "./ide-_C2gUPbQ.mjs";
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 { appendFile, readFile, writeFile } from "node:fs/promises";
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-B5mXontH.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";
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-B5mXontH.mjs";
2
- import { a as clearCredentials, o as loadCredentials, t as StudioApiClient } from "./client-C400qHcL.mjs";
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-C400qHcL.mjs";
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 };
@@ -1,4 +1,4 @@
1
- import { r as resolveProjectRoot } from "./context-An3xrvcP.mjs";
1
+ import { r as resolveProjectRoot } from "./context-OskMGy8y.mjs";
2
2
  import { defineCommand } from "citty";
3
3
  import { createServer } from "@contentrain/mcp/server";
4
4
  import consola from "consola";
@@ -6,7 +6,7 @@ import consola from "consola";
6
6
  var serve_default = defineCommand({
7
7
  meta: {
8
8
  name: "serve",
9
- description: "Start local content viewer or MCP stdio server"
9
+ description: "Start the local content viewer or the MCP server (stdio for IDE agents, --mcpHttp for Studio / CI / remote drivers)"
10
10
  },
11
11
  args: {
12
12
  root: {
@@ -19,6 +19,16 @@ var serve_default = defineCommand({
19
19
  description: "Use stdio MCP transport for IDE integration (env: CONTENTRAIN_STDIO=true)",
20
20
  required: false
21
21
  },
22
+ mcpHttp: {
23
+ type: "boolean",
24
+ description: "Use HTTP MCP transport (serves tool calls at POST /mcp)",
25
+ required: false
26
+ },
27
+ authToken: {
28
+ type: "string",
29
+ description: "Bearer token required for HTTP MCP requests (env: CONTENTRAIN_AUTH_TOKEN)",
30
+ required: false
31
+ },
22
32
  port: {
23
33
  type: "string",
24
34
  description: "HTTP server port, default: 3333 (env: CONTENTRAIN_PORT)",
@@ -44,7 +54,7 @@ var serve_default = defineCommand({
44
54
  let projectRoot = await resolveProjectRoot(args.root);
45
55
  let demoDir;
46
56
  if (args.demo) {
47
- const { createDemoProject, cleanupDemoProject } = await import("./demo-DcBt6fw8.mjs");
57
+ const { createDemoProject, cleanupDemoProject } = await import("./demo-BOUcAoFZ.mjs");
48
58
  demoDir = await createDemoProject();
49
59
  projectRoot = demoDir;
50
60
  consola.info(`Demo project created at ${demoDir}`);
@@ -76,6 +86,50 @@ var serve_default = defineCommand({
76
86
  }
77
87
  const port = Number(args.port) || Number(process.env["CONTENTRAIN_PORT"]) || 3333;
78
88
  const host = args.host ?? process.env["CONTENTRAIN_HOST"] ?? "localhost";
89
+ const authToken = args.authToken ?? process.env["CONTENTRAIN_AUTH_TOKEN"];
90
+ if (args.mcpHttp || process.env["CONTENTRAIN_MCP_HTTP"] === "true" || process.env["CONTENTRAIN_MCP_HTTP"] === "1") {
91
+ const { startHttpMcpServer } = await import("@contentrain/mcp/server/http");
92
+ const handle = await startHttpMcpServer({
93
+ projectRoot,
94
+ host,
95
+ port,
96
+ authToken
97
+ });
98
+ consola.box({
99
+ title: "Contentrain MCP (HTTP)",
100
+ message: [
101
+ `Endpoint: ${handle.url}`,
102
+ `Root: ${projectRoot}`,
103
+ authToken ? "Auth: Bearer token required" : "Auth: none (local-only server)",
104
+ "",
105
+ "Press Ctrl+C to stop"
106
+ ].join("\n")
107
+ });
108
+ if (host === "0.0.0.0" && !authToken) consola.warn("HTTP MCP server is accessible from the network with no Bearer token. Set --auth-token or CONTENTRAIN_AUTH_TOKEN.");
109
+ process.on("SIGINT", () => {
110
+ handle.close().finally(() => process.exit(0));
111
+ });
112
+ process.on("SIGTERM", () => {
113
+ handle.close().finally(() => process.exit(0));
114
+ });
115
+ return;
116
+ }
117
+ if (host !== "localhost" && host !== "127.0.0.1" && !authToken) {
118
+ consola.error([
119
+ `Refusing to start serve UI on ${host} without a Bearer token.`,
120
+ "",
121
+ " The serve UI exposes the full .contentrain/ write surface to any",
122
+ " reachable host. Either bind to localhost:",
123
+ "",
124
+ " contentrain serve --host localhost",
125
+ "",
126
+ " Or pass an auth token (coming soon — today, use stdio MCP for remote agents):",
127
+ "",
128
+ " contentrain serve --mcpHttp --host 0.0.0.0 --authToken $(openssl rand -hex 32)"
129
+ ].join("\n"));
130
+ process.exitCode = 1;
131
+ return;
132
+ }
79
133
  const shouldOpen = args.open !== false && process.env["CONTENTRAIN_NO_OPEN"] !== "true";
80
134
  const { join, dirname } = await import("node:path");
81
135
  const { existsSync } = await import("node:fs");
@@ -85,7 +139,7 @@ var serve_default = defineCommand({
85
139
  consola.warn("Serve UI not found. Running in API-only mode.");
86
140
  consola.info("UI will be available after building: cd packages/cli/src/serve-ui && pnpm build");
87
141
  }
88
- const { createServeApp } = await import("./server-DTdmutjb.mjs");
142
+ const { createServeApp } = await import("./server-B1PSjXbu.mjs");
89
143
  const { app, handleUpgrade } = await createServeApp({
90
144
  projectRoot,
91
145
  port,
@@ -123,7 +177,6 @@ var serve_default = defineCommand({
123
177
  exec(`${process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"} ${url}`);
124
178
  });
125
179
  });
126
- if (host === "0.0.0.0") consola.warn("Server is accessible from the network. No authentication is configured.");
127
180
  }
128
181
  });
129
182
  //#endregion
@@ -1,4 +1,4 @@
1
- import{c as i,a as p,o as t,p as o,q as s,j as u,u as a,B as n,g as m,b as l,r as x,E as d,f as g}from"./index-RDdffBTh.js";/**
1
+ import{c as i,a as p,o as t,p as o,q as s,j as u,u as a,B as n,g as m,b as l,r as x,G as d,f as g}from"./index-DL_dw3XX.js";/**
2
2
  * @license lucide-vue-next v0.474.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.