contentrain 0.1.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -0
  3. package/dist/context-An3xrvcP.mjs +50 -0
  4. package/dist/diff-otz0kGqT.mjs +116 -0
  5. package/dist/doctor-DyKjAIKH.mjs +187 -0
  6. package/dist/generate-DDEDRLdy.mjs +76 -0
  7. package/dist/index.mjs +21 -0
  8. package/dist/init-D5L_7bKE.mjs +415 -0
  9. package/dist/serve-CaCZMeHF.mjs +111 -0
  10. package/dist/serve-ui/assets/AgentPromptGroup.vue_vue_type_script_setup_true_lang-Dy-5bpEY.js +16 -0
  11. package/dist/serve-ui/assets/AppLayout-CJFq2ApF.js +16 -0
  12. package/dist/serve-ui/assets/BranchDetailPage-DzSu3JlE.js +17 -0
  13. package/dist/serve-ui/assets/BranchesPage-ZSG-l44J.js +6 -0
  14. package/dist/serve-ui/assets/CollapsibleTrigger.vue_vue_type_script_setup_true_lang-C4oejwcu.js +1 -0
  15. package/dist/serve-ui/assets/Collection-zzx9pNd2.js +6 -0
  16. package/dist/serve-ui/assets/ConfigProvider-BGjDLViI.js +1 -0
  17. package/dist/serve-ui/assets/ContentListPage-DTFaQB8N.js +51 -0
  18. package/dist/serve-ui/assets/ContentListPage-vXxAeSg8.css +1 -0
  19. package/dist/serve-ui/assets/ContentPage-4sQsAlY2.js +1 -0
  20. package/dist/serve-ui/assets/DashboardPage-CZDlnvAE.js +21 -0
  21. package/dist/serve-ui/assets/DialogTitle.vue_vue_type_script_setup_true_lang-CBu_dmA6.js +1 -0
  22. package/dist/serve-ui/assets/Input.vue_vue_type_script_setup_true_lang-otPltlZG.js +6 -0
  23. package/dist/serve-ui/assets/ModelDetailPage-BMquKCtH.js +49 -0
  24. package/dist/serve-ui/assets/ModelsPage-C_K-aWXf.js +1 -0
  25. package/dist/serve-ui/assets/NormalizePage-CHkT6yLV.js +11 -0
  26. package/dist/serve-ui/assets/PageHeader.vue_vue_type_script_setup_true_lang-C7aWxNSw.js +11 -0
  27. package/dist/serve-ui/assets/Presence-cbB6AqkQ.js +3 -0
  28. package/dist/serve-ui/assets/Separator.vue_vue_type_script_setup_true_lang-fePFl24S.js +1 -0
  29. package/dist/serve-ui/assets/StudioHint.vue_vue_type_script_setup_true_lang-B45TG2r1.js +6 -0
  30. package/dist/serve-ui/assets/TrustBadge.vue_vue_type_script_setup_true_lang-CgoQGS6k.js +11 -0
  31. package/dist/serve-ui/assets/ValidatePage-BW8DO6WC.js +21 -0
  32. package/dist/serve-ui/assets/arrow-down-DqOfGL7C.js +6 -0
  33. package/dist/serve-ui/assets/arrow-left-BRvgeveW.js +6 -0
  34. package/dist/serve-ui/assets/book-marked-D8StxRZP.js +6 -0
  35. package/dist/serve-ui/assets/book-open-BrGAe3tk.js +6 -0
  36. package/dist/serve-ui/assets/box-7olVZr5D.js +6 -0
  37. package/dist/serve-ui/assets/chevron-down-B9YdCtsj.js +6 -0
  38. package/dist/serve-ui/assets/chevron-right-DFKsmZVd.js +6 -0
  39. package/dist/serve-ui/assets/content-BQE3NEm5.js +1 -0
  40. package/dist/serve-ui/assets/database-BGEFn4Oq.js +6 -0
  41. package/dist/serve-ui/assets/file-code-BGiS4dun.js +6 -0
  42. package/dist/serve-ui/assets/file-text-BDHXQ95W.js +6 -0
  43. package/dist/serve-ui/assets/getActiveElement-FUKBWQwh.js +1 -0
  44. package/dist/serve-ui/assets/git-branch-DdE7bk9-.js +6 -0
  45. package/dist/serve-ui/assets/git-merge-iStuJS69.js +6 -0
  46. package/dist/serve-ui/assets/hash-B-ibLpM1.js +6 -0
  47. package/dist/serve-ui/assets/index-C-RjMzFm.css +1 -0
  48. package/dist/serve-ui/assets/index-CoUshYEB.js +80 -0
  49. package/dist/serve-ui/assets/index-WHgvdYOs.js +1 -0
  50. package/dist/serve-ui/assets/languages-BTcd2Rl1.js +6 -0
  51. package/dist/serve-ui/assets/model-empty-state-B6AM4bil.js +1 -0
  52. package/dist/serve-ui/assets/plus-D8Nd3YnG.js +11 -0
  53. package/dist/serve-ui/assets/project-B1qSm0Fz.js +1 -0
  54. package/dist/serve-ui/assets/scan-search-DGPL_VJu.js +6 -0
  55. package/dist/serve-ui/assets/settings-DL-0RaxD.js +6 -0
  56. package/dist/serve-ui/assets/shield-check-CUL7KkSA.js +6 -0
  57. package/dist/serve-ui/assets/trash-2-83QhL6-T.js +11 -0
  58. package/dist/serve-ui/assets/useDirection-Cd3C7VdT.js +1 -0
  59. package/dist/serve-ui/assets/useFormatters-DgWFpsAv.js +1 -0
  60. package/dist/serve-ui/assets/useId-C0uR6mdj.js +1 -0
  61. package/dist/serve-ui/assets/useNonce-DTGvzOJz.js +1 -0
  62. package/dist/serve-ui/assets/useWatch-COpVIQvm.js +1 -0
  63. package/dist/serve-ui/favicon.svg +19 -0
  64. package/dist/serve-ui/for-developers-1.svg +65 -0
  65. package/dist/serve-ui/icon-color.svg +18 -0
  66. package/dist/serve-ui/index.html +17 -0
  67. package/dist/serve-ui/logo/color-icon-black-text.svg +65 -0
  68. package/dist/serve-ui/logo/color-icon-white-text.svg +61 -0
  69. package/dist/serve-ui/merge-1.svg +131 -0
  70. package/dist/serve-ui/merge-2.svg +161 -0
  71. package/dist/serve-ui/model-empty-state.svg +12 -0
  72. package/dist/serve-ui/select-data-type.svg +23 -0
  73. package/dist/server-DW0KXB0T.mjs +607 -0
  74. package/dist/status-kF5miVty.mjs +122 -0
  75. package/dist/ui-B5mXontH.mjs +33 -0
  76. package/dist/validate-DnVamp3p.mjs +155 -0
  77. package/package.json +66 -0
@@ -0,0 +1,122 @@
1
+ import { n as requireInitialized, r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
2
+ import { i as pc, n as formatPercent, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { intro, log, outro } from "@clack/prompts";
5
+ import { simpleGit } from "simple-git";
6
+ import { countEntries, readModel } from "@contentrain/mcp/core/model-manager";
7
+ import { validateProject } from "@contentrain/mcp/core/validator";
8
+ //#region src/commands/status.ts
9
+ var status_default = defineCommand({
10
+ meta: {
11
+ name: "status",
12
+ description: "Show project status overview"
13
+ },
14
+ args: {
15
+ root: {
16
+ type: "string",
17
+ description: "Project root path",
18
+ required: false
19
+ },
20
+ json: {
21
+ type: "boolean",
22
+ description: "JSON output for CI",
23
+ required: false
24
+ }
25
+ },
26
+ async run({ args }) {
27
+ const projectRoot = await resolveProjectRoot(args.root);
28
+ const ctx = await loadProjectContext(projectRoot);
29
+ if (args.json) {
30
+ const jsonResult = {
31
+ initialized: ctx.initialized,
32
+ config: ctx.config,
33
+ models: ctx.models,
34
+ context: ctx.context
35
+ };
36
+ if (ctx.initialized && ctx.config) {
37
+ try {
38
+ const validation = await validateProject(projectRoot, {});
39
+ jsonResult["validation"] = {
40
+ valid: validation.valid,
41
+ errors: validation.summary.errors,
42
+ warnings: validation.summary.warnings
43
+ };
44
+ } catch {}
45
+ try {
46
+ jsonResult["pending_branches"] = (await simpleGit(projectRoot).branch(["--list", "contentrain/*"])).all;
47
+ } catch {}
48
+ }
49
+ process.stdout.write(JSON.stringify(jsonResult, null, 2));
50
+ return;
51
+ }
52
+ intro(pc.bold("contentrain status"));
53
+ if (!ctx.initialized) {
54
+ log.warning("Project not initialized. Run `contentrain init` to get started.");
55
+ outro("");
56
+ return;
57
+ }
58
+ requireInitialized(ctx);
59
+ log.info(pc.bold("Configuration"));
60
+ log.message(` Stack: ${pc.cyan(ctx.config.stack)}`);
61
+ log.message(` Locales: ${ctx.config.locales.supported.map((l) => l === ctx.config.locales.default ? pc.green(`${l} (default)`) : l).join(", ")}`);
62
+ log.message(` Domains: ${ctx.config.domains.join(", ")}`);
63
+ log.message(` Workflow: ${ctx.config.workflow === "auto-merge" ? pc.green("auto-merge") : pc.yellow("review")}`);
64
+ if (ctx.models.length > 0) {
65
+ log.info(pc.bold(`\nModels (${ctx.models.length})`));
66
+ const rows = [];
67
+ for (const model of ctx.models) {
68
+ const full = await readModel(projectRoot, model.id);
69
+ if (!full) continue;
70
+ const counts = await countEntries(projectRoot, full);
71
+ const defaultCount = counts.locales[ctx.config.locales.default] ?? 0;
72
+ let i18nStatus = "—";
73
+ if (model.i18n && ctx.config.locales.supported.length > 1) {
74
+ const completions = ctx.config.locales.supported.filter((l) => l !== ctx.config.locales.default).map((l) => counts.locales[l] ?? 0);
75
+ i18nStatus = formatPercent(completions.length > 0 ? completions.reduce((a, b) => a + b, 0) / completions.length : 0, defaultCount);
76
+ }
77
+ rows.push([
78
+ model.id,
79
+ model.kind,
80
+ model.domain,
81
+ String(counts.total),
82
+ model.i18n ? i18nStatus : "—"
83
+ ]);
84
+ }
85
+ log.message(formatTable([
86
+ "Model",
87
+ "Kind",
88
+ "Domain",
89
+ "Entries",
90
+ "i18n"
91
+ ], rows));
92
+ } else log.message(" No models yet. Run `contentrain init` with a template or create models.");
93
+ try {
94
+ const branches = await simpleGit(projectRoot).branch(["--list", "contentrain/*"]);
95
+ if (branches.all.length > 0) {
96
+ const count = branches.all.length;
97
+ if (count >= 80) {
98
+ log.error(pc.bold(`\nBLOCKED: ${count} active contentrain branches (limit: 80)`));
99
+ log.message(` New writes are blocked. Merge or delete old branches with ${pc.cyan("contentrain diff")}.`);
100
+ } else if (count >= 50) {
101
+ log.warning(pc.bold(`\nWARNING: ${count} active contentrain branches (limit: 50)`));
102
+ log.message(` Consider merging or deleting old branches with ${pc.cyan("contentrain diff")}.`);
103
+ } else log.info(pc.bold(`\nPending branches (${count})`));
104
+ for (const branch of branches.all) log.message(` ${pc.yellow("●")} ${branch}`);
105
+ log.message(` Run ${pc.cyan("contentrain diff")} to review.`);
106
+ }
107
+ } catch {}
108
+ if (ctx.context?.lastOperation) {
109
+ const op = ctx.context.lastOperation;
110
+ log.info(pc.bold("\nLast operation"));
111
+ log.message(` ${op.tool} → ${op.model || "(init)"} @ ${new Date(op.timestamp).toLocaleString()}`);
112
+ }
113
+ try {
114
+ const result = await validateProject(projectRoot);
115
+ if (result.summary.errors > 0) log.warning(`\n${formatCount(result.summary.errors, "validation error")}. Run ${pc.cyan("contentrain validate --fix")} to fix.`);
116
+ else log.success("\nValidation: clean");
117
+ } catch {}
118
+ outro("");
119
+ }
120
+ });
121
+ //#endregion
122
+ export { status_default as default };
@@ -0,0 +1,33 @@
1
+ import pc from "picocolors";
2
+ //#region src/utils/ui.ts
3
+ function severityColor(severity) {
4
+ switch (severity) {
5
+ case "error": return pc.red;
6
+ case "warning": return pc.yellow;
7
+ case "notice": return pc.blue;
8
+ }
9
+ }
10
+ function formatCount(n, singular, plural) {
11
+ return `${n} ${n === 1 ? singular : plural ?? `${singular}s`}`;
12
+ }
13
+ function formatPercent(part, total) {
14
+ if (total === 0) return "—";
15
+ const pct = Math.round(part / total * 100);
16
+ return (pct === 100 ? pc.green : pct >= 75 ? pc.yellow : pc.red)(`${pct}%`);
17
+ }
18
+ function padRight(str, len) {
19
+ return str.length >= len ? str : str + " ".repeat(len - str.length);
20
+ }
21
+ function formatTable(headers, rows) {
22
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
23
+ return [
24
+ headers.map((h, i) => padRight(h, widths[i] ?? h.length)).join(" "),
25
+ widths.map((w) => "─".repeat(w)).join("──"),
26
+ ...rows.map((row) => row.map((cell, i) => padRight(cell, widths[i] ?? cell.length)).join(" "))
27
+ ].join("\n");
28
+ }
29
+ function statusIcon(pass) {
30
+ return pass ? pc.green("✓") : pc.red("✗");
31
+ }
32
+ //#endregion
33
+ export { severityColor as a, pc as i, formatPercent as n, statusIcon as o, formatTable as r, formatCount as t };
@@ -0,0 +1,155 @@
1
+ import { n as requireInitialized, r as resolveProjectRoot, t as loadProjectContext } from "./context-An3xrvcP.mjs";
2
+ import { a as severityColor, i as pc, t as formatCount } from "./ui-B5mXontH.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
5
+ import { writeContext } from "@contentrain/mcp/core/context";
6
+ import { buildBranchName, createTransaction } from "@contentrain/mcp/git/transaction";
7
+ import { validateProject } from "@contentrain/mcp/core/validator";
8
+ //#region src/commands/validate.ts
9
+ var validate_default = defineCommand({
10
+ meta: {
11
+ name: "validate",
12
+ description: "Validate project content against schemas"
13
+ },
14
+ args: {
15
+ root: {
16
+ type: "string",
17
+ description: "Project root path",
18
+ required: false
19
+ },
20
+ fix: {
21
+ type: "boolean",
22
+ description: "Auto-fix issues",
23
+ required: false
24
+ },
25
+ interactive: {
26
+ type: "boolean",
27
+ description: "Interactive fix mode",
28
+ required: false
29
+ },
30
+ json: {
31
+ type: "boolean",
32
+ description: "JSON output for CI",
33
+ required: false
34
+ },
35
+ model: {
36
+ type: "string",
37
+ description: "Validate single model",
38
+ required: false
39
+ }
40
+ },
41
+ async run({ args }) {
42
+ const projectRoot = await resolveProjectRoot(args.root);
43
+ requireInitialized(await loadProjectContext(projectRoot));
44
+ if (!args.json) intro(pc.bold("contentrain validate"));
45
+ const s = !args.json ? spinner() : null;
46
+ s?.start("Validating...");
47
+ let result;
48
+ if (args.fix) {
49
+ const { checkBranchHealth } = await import("@contentrain/mcp/git/branch-lifecycle");
50
+ const health = await checkBranchHealth(projectRoot);
51
+ if (health.blocked) {
52
+ s?.stop("Blocked");
53
+ log.error(health.message);
54
+ outro("");
55
+ process.exitCode = 1;
56
+ return;
57
+ }
58
+ const tx = await createTransaction(projectRoot, buildBranchName("fix", "validate"));
59
+ try {
60
+ await tx.write(async (wt) => {
61
+ result = await validateProject(wt, {
62
+ model: args.model,
63
+ fix: true
64
+ });
65
+ await writeContext(wt, {
66
+ tool: "contentrain_validate",
67
+ model: args.model ?? "*"
68
+ });
69
+ });
70
+ await tx.commit("[contentrain] validate: auto-fix");
71
+ await tx.complete();
72
+ } finally {
73
+ await tx.cleanup();
74
+ }
75
+ } else result = await validateProject(projectRoot, {
76
+ model: args.model,
77
+ fix: false
78
+ });
79
+ s?.stop("Validation complete");
80
+ if (args.json) {
81
+ process.stdout.write(JSON.stringify(result, null, 2));
82
+ return;
83
+ }
84
+ log.info(pc.bold("Summary"));
85
+ log.message(` Models checked: ${result.summary.models_checked}`);
86
+ log.message(` Entries checked: ${result.summary.entries_checked}`);
87
+ log.message(` Errors: ${result.summary.errors > 0 ? pc.red(String(result.summary.errors)) : pc.green("0")}`);
88
+ log.message(` Warnings: ${result.summary.warnings > 0 ? pc.yellow(String(result.summary.warnings)) : pc.green("0")}`);
89
+ log.message(` Notices: ${result.summary.notices > 0 ? pc.blue(String(result.summary.notices)) : "0"}`);
90
+ if (args.fix && result.fixed > 0) log.success(`Fixed ${result.fixed} issue(s)`);
91
+ if (result.issues.length > 0) {
92
+ log.info(pc.bold("\nIssues"));
93
+ for (const issue of result.issues) {
94
+ const prefix = severityColor(issue.severity)(`[${issue.severity.toUpperCase()}]`);
95
+ const location = [
96
+ issue.model,
97
+ issue.field,
98
+ issue.entry
99
+ ].filter(Boolean).join(" → ");
100
+ log.message(` ${prefix} ${location}: ${issue.message}`);
101
+ }
102
+ if (args.interactive && result.issues.some((i) => i.severity === "error" || i.severity === "warning")) {
103
+ log.info("\n" + pc.bold("Interactive fix mode"));
104
+ const fixChoice = await select({
105
+ message: "How would you like to proceed?",
106
+ options: [{
107
+ value: "fix-all",
108
+ label: "Fix all auto-fixable issues"
109
+ }, {
110
+ value: "skip",
111
+ label: "Skip — I'll fix manually"
112
+ }]
113
+ });
114
+ if (!isCancel(fixChoice) && fixChoice === "fix-all") {
115
+ const fixS = spinner();
116
+ fixS.start("Fixing issues...");
117
+ const fixBranch = buildBranchName("fix", "validate");
118
+ const fixTx = await createTransaction(projectRoot, fixBranch);
119
+ let fixResult;
120
+ try {
121
+ await fixTx.write(async (wt) => {
122
+ fixResult = await validateProject(wt, {
123
+ model: args.model,
124
+ fix: true
125
+ });
126
+ await writeContext(wt, {
127
+ tool: "contentrain_validate",
128
+ model: args.model ?? "*"
129
+ });
130
+ });
131
+ await fixTx.commit("[contentrain] validate: interactive auto-fix");
132
+ await fixTx.complete();
133
+ } finally {
134
+ await fixTx.cleanup();
135
+ }
136
+ fixS.stop(`Fixed ${fixResult.fixed} issue(s)`);
137
+ const { readConfig } = await import("@contentrain/mcp/core/config");
138
+ if ((await readConfig(projectRoot))?.workflow === "review") log.info(`Fixes committed to branch ${pc.cyan(fixBranch)}. Run ${pc.cyan("contentrain diff")} to review and merge.`);
139
+ else {
140
+ const recheck = await validateProject(projectRoot, { model: args.model });
141
+ if (recheck.summary.errors === 0) log.success("All errors resolved!");
142
+ else log.warning(`${formatCount(recheck.summary.errors, "error")} remaining (may need manual fix)`);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ if (result.valid) outro(pc.green("Project is valid!"));
148
+ else {
149
+ outro(pc.yellow(`${formatCount(result.summary.errors, "error")}, ${formatCount(result.summary.warnings, "warning")}`));
150
+ if (!args.fix && !args.interactive) log.message(`Run ${pc.cyan("contentrain validate --fix")} to auto-fix.`);
151
+ }
152
+ }
153
+ });
154
+ //#endregion
155
+ export { validate_default as default };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "contentrain",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "CLI for Contentrain — AI content governance infrastructure",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/contentrain/contentrain-ai.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/contentrain/contentrain-ai/tree/main/packages/cli",
13
+ "bugs": {
14
+ "url": "https://github.com/contentrain/contentrain-ai/issues"
15
+ },
16
+ "keywords": [
17
+ "contentrain",
18
+ "cli",
19
+ "mcp",
20
+ "content-governance",
21
+ "ai",
22
+ "i18n",
23
+ "cms"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "contentrain": "./dist/index.mjs"
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "dependencies": {
35
+ "@clack/prompts": "^0.10.0",
36
+ "@modelcontextprotocol/sdk": "^1.12.0",
37
+ "chokidar": "^4.0.0",
38
+ "citty": "^0.1.0",
39
+ "consola": "^3.4.0",
40
+ "crossws": "^0.3.0",
41
+ "h3": "^1.15.0",
42
+ "picocolors": "^1.1.0",
43
+ "simple-git": "^3.27.0",
44
+ "ws": "^8.18.0",
45
+ "@contentrain/rules": "0.1.0",
46
+ "@contentrain/types": "0.1.0",
47
+ "@contentrain/mcp": "1.0.0",
48
+ "@contentrain/query": "5.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/ws": "^8.5.0",
52
+ "@types/node": "^22.0.0",
53
+ "tsdown": "^0.21.0",
54
+ "typescript": "^5.7.0",
55
+ "vitest": "^3.0.0"
56
+ },
57
+ "scripts": {
58
+ "build": "tsdown src/index.ts --format esm && cd src/serve-ui && npx vite build",
59
+ "build:cli-only": "tsdown src/index.ts --format esm",
60
+ "build:ui-only": "cd src/serve-ui && npx vite build",
61
+ "dev": "tsdown src/index.ts --format esm --watch",
62
+ "test": "vitest run",
63
+ "typecheck": "tsc --noEmit",
64
+ "clean": "rm -rf dist"
65
+ }
66
+ }