contentrain 0.1.4 → 0.2.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/dist/index.mjs CHANGED
@@ -5,11 +5,11 @@ import { defineCommand, runMain } from "citty";
5
5
  runMain(defineCommand({
6
6
  meta: {
7
7
  name: "contentrain",
8
- version: "0.1.4",
8
+ version: "0.2.0",
9
9
  description: "Contentrain CLI — AI content governance infrastructure"
10
10
  },
11
11
  subCommands: {
12
- init: () => import("./init-D5L_7bKE.mjs").then((m) => m.default),
12
+ init: () => import("./init-BbtT9_sl.mjs").then((m) => m.default),
13
13
  status: () => import("./status-kF5miVty.mjs").then((m) => m.default),
14
14
  doctor: () => import("./doctor-DyKjAIKH.mjs").then((m) => m.default),
15
15
  validate: () => import("./validate-DnVamp3p.mjs").then((m) => m.default),
@@ -350,54 +350,168 @@ async function executeInit(projectRoot, opts) {
350
350
  await tx.cleanup();
351
351
  }
352
352
  }
353
+ const RULE_BASE_NAMES = [
354
+ "content-quality",
355
+ "seo-rules",
356
+ "i18n-quality",
357
+ "accessibility-rules",
358
+ "security-rules",
359
+ "media-rules",
360
+ "content-conventions",
361
+ "schema-rules",
362
+ "mcp-usage",
363
+ "workflow-rules",
364
+ "normalize-rules"
365
+ ];
366
+ const SKILL_FILE_NAMES = [
367
+ "contentrain-normalize.md",
368
+ "contentrain-content.md",
369
+ "contentrain-model.md",
370
+ "contentrain-bulk.md",
371
+ "contentrain-validate-fix.md",
372
+ "contentrain-review.md",
373
+ "contentrain-translate.md",
374
+ "contentrain-generate.md",
375
+ "contentrain-serve.md",
376
+ "contentrain-diff.md",
377
+ "contentrain-doctor.md",
378
+ "contentrain-init.md"
379
+ ];
353
380
  async function installRules(projectRoot) {
354
381
  try {
355
- const ruleFiles = {
356
- "claude-code": "ide/claude-code/contentrain.md",
357
- "cursor": "ide/cursor/contentrain.cursorrules",
358
- "windsurf": "ide/windsurf/contentrain.windsurfrules",
359
- "generic": "ide/generic/contentrain.md"
360
- };
361
382
  const { createRequire } = await import("node:module");
362
383
  const require = createRequire(import.meta.url);
363
- const resolveRuleFile = (rulePath) => require.resolve(`@contentrain/rules/${rulePath}`);
364
- const targets = [];
365
- if (await pathExists(join(projectRoot, "CLAUDE.md")) || await pathExists(join(projectRoot, ".claude"))) targets.push({
366
- ide: "Claude Code",
367
- source: resolveRuleFile(ruleFiles["claude-code"]),
368
- dest: join(projectRoot, "CLAUDE.md"),
369
- append: true
370
- });
371
- if (await pathExists(join(projectRoot, ".cursorrules")) || await pathExists(join(projectRoot, ".cursor"))) targets.push({
372
- ide: "Cursor",
373
- source: resolveRuleFile(ruleFiles["cursor"]),
374
- dest: join(projectRoot, ".cursorrules")
375
- });
376
- if (await pathExists(join(projectRoot, ".windsurf"))) targets.push({
377
- ide: "Windsurf",
378
- source: resolveRuleFile(ruleFiles["windsurf"]),
379
- dest: join(projectRoot, ".windsurfrules")
380
- });
381
- if (targets.length === 0) targets.push({
382
- ide: "Generic",
383
- source: resolveRuleFile(ruleFiles["generic"]),
384
- dest: join(projectRoot, "CLAUDE.md")
385
- });
386
- for (const target of targets) {
387
- const ruleContent = await readFile(target.source, "utf-8");
388
- const marker = "# Contentrain AI Rules";
389
- if (target.append && await pathExists(target.dest)) {
390
- if ((await readFile(target.dest, "utf-8")).includes(marker)) continue;
391
- await appendFile(target.dest, `\n\n${ruleContent}\n`);
392
- } else await writeFile(target.dest, ruleContent, "utf-8");
384
+ const resolveRuleFile = (p) => require.resolve(`@contentrain/rules/${p}`);
385
+ let resolveSkillFile = null;
386
+ try {
387
+ const requireSkills = createRequire(import.meta.url);
388
+ resolveSkillFile = (p) => requireSkills.resolve(`@contentrain/skills/${p}`);
389
+ } catch {}
390
+ const hasClaudeCode = await pathExists(join(projectRoot, "CLAUDE.md")) || await pathExists(join(projectRoot, ".claude"));
391
+ const hasCursorDir = await pathExists(join(projectRoot, ".cursor"));
392
+ const hasCursorRules = await pathExists(join(projectRoot, ".cursorrules"));
393
+ const hasWindsurfDir = await pathExists(join(projectRoot, ".windsurf"));
394
+ const noIDEDetected = !hasClaudeCode && !hasCursorDir && !hasCursorRules && !hasWindsurfDir;
395
+ if (hasClaudeCode) await installClaudeCodeRules(projectRoot, resolveRuleFile, resolveSkillFile);
396
+ if (hasCursorDir) await installCursorRules(projectRoot, resolveRuleFile);
397
+ else if (hasCursorRules) {
398
+ const source = resolveRuleFile("ide/cursor/contentrain.cursorrules");
399
+ const dest = join(projectRoot, ".cursorrules");
400
+ await writeFile(dest, await readFile(source, "utf-8"), "utf-8");
401
+ const git = simpleGit(projectRoot);
402
+ await git.add(dest);
403
+ try {
404
+ await git.commit("[contentrain] install Cursor rules");
405
+ } catch {}
406
+ }
407
+ if (hasWindsurfDir) await installWindsurfRules(projectRoot, resolveRuleFile);
408
+ if (noIDEDetected) {
409
+ const source = resolveRuleFile("ide/generic/contentrain.md");
410
+ const dest = join(projectRoot, "CLAUDE.md");
411
+ await writeFile(dest, await readFile(source, "utf-8"), "utf-8");
393
412
  const git = simpleGit(projectRoot);
394
- await git.add(target.dest);
413
+ await git.add(dest);
395
414
  try {
396
- await git.commit(`[contentrain] install ${target.ide} rules`);
415
+ await git.commit("[contentrain] install AI rules");
397
416
  } catch {}
398
417
  }
399
418
  } catch {}
400
419
  }
420
+ async function installClaudeCodeRules(projectRoot, resolveRuleFile, resolveSkillFile) {
421
+ const rulesDir = join(projectRoot, ".claude", "rules");
422
+ const skillsDir = join(projectRoot, ".claude", "skills");
423
+ const git = simpleGit(projectRoot);
424
+ const filesToAdd = [];
425
+ await ensureDir(rulesDir);
426
+ for (const baseName of RULE_BASE_NAMES) {
427
+ const fileName = `contentrain-${baseName}.md`;
428
+ const dest = join(rulesDir, fileName);
429
+ if (await pathExists(dest)) continue;
430
+ try {
431
+ await writeFile(dest, await readFile(resolveRuleFile(`ide/claude-code/rules/${fileName}`), "utf-8"), "utf-8");
432
+ filesToAdd.push(dest);
433
+ } catch {}
434
+ }
435
+ if (resolveSkillFile) {
436
+ await ensureDir(skillsDir);
437
+ for (const fileName of SKILL_FILE_NAMES) {
438
+ const dest = join(skillsDir, fileName);
439
+ if (await pathExists(dest)) continue;
440
+ try {
441
+ await writeFile(dest, await readFile(resolveSkillFile(`workflows/${fileName}`), "utf-8"), "utf-8");
442
+ filesToAdd.push(dest);
443
+ } catch {}
444
+ }
445
+ }
446
+ const claudeMdPath = join(projectRoot, "CLAUDE.md");
447
+ const marker = "# Contentrain AI Rules";
448
+ const reference = [
449
+ "# Contentrain AI Rules",
450
+ "",
451
+ "This project uses [Contentrain](https://ai.contentrain.io) for AI-driven content management.",
452
+ "",
453
+ "- **Rules** are in `.claude/rules/` — auto-loaded each conversation",
454
+ "- **Skills** are in `.claude/skills/` — invoke with `/contentrain-normalize` etc.",
455
+ "- **Docs:** https://ai.contentrain.io"
456
+ ].join("\n");
457
+ if (await pathExists(claudeMdPath)) {
458
+ if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
459
+ await appendFile(claudeMdPath, `\n\n${reference}\n`);
460
+ filesToAdd.push(claudeMdPath);
461
+ }
462
+ } else {
463
+ await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
464
+ filesToAdd.push(claudeMdPath);
465
+ }
466
+ if (filesToAdd.length > 0) {
467
+ await git.add(filesToAdd);
468
+ try {
469
+ await git.commit("[contentrain] install Claude Code rules and skills");
470
+ } catch {}
471
+ }
472
+ }
473
+ async function installCursorRules(projectRoot, resolveRuleFile) {
474
+ const rulesDir = join(projectRoot, ".cursor", "rules");
475
+ await ensureDir(rulesDir);
476
+ const git = simpleGit(projectRoot);
477
+ const filesToAdd = [];
478
+ for (const baseName of RULE_BASE_NAMES) {
479
+ const srcFile = `contentrain-${baseName}.mdc`;
480
+ const dest = join(rulesDir, srcFile);
481
+ if (await pathExists(dest)) continue;
482
+ try {
483
+ await writeFile(dest, await readFile(resolveRuleFile(`ide/cursor/rules/${srcFile}`), "utf-8"), "utf-8");
484
+ filesToAdd.push(dest);
485
+ } catch {}
486
+ }
487
+ if (filesToAdd.length > 0) {
488
+ await git.add(filesToAdd);
489
+ try {
490
+ await git.commit("[contentrain] install Cursor rules");
491
+ } catch {}
492
+ }
493
+ }
494
+ async function installWindsurfRules(projectRoot, resolveRuleFile) {
495
+ const rulesDir = join(projectRoot, ".windsurf", "rules");
496
+ await ensureDir(rulesDir);
497
+ const git = simpleGit(projectRoot);
498
+ const filesToAdd = [];
499
+ for (const baseName of RULE_BASE_NAMES) {
500
+ const fileName = `contentrain-${baseName}.md`;
501
+ const dest = join(rulesDir, fileName);
502
+ if (await pathExists(dest)) continue;
503
+ try {
504
+ await writeFile(dest, await readFile(resolveRuleFile(`ide/windsurf/rules/${fileName}`), "utf-8"), "utf-8");
505
+ filesToAdd.push(dest);
506
+ } catch {}
507
+ }
508
+ if (filesToAdd.length > 0) {
509
+ await git.add(filesToAdd);
510
+ try {
511
+ await git.commit("[contentrain] install Windsurf rules");
512
+ } catch {}
513
+ }
514
+ }
401
515
  async function updateGitignore(projectRoot) {
402
516
  const gitignorePath = join(projectRoot, ".gitignore");
403
517
  const ignoreEntries = [".contentrain/.cache/", ".contentrain/client/"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentrain",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
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.1.0",
50
- "@contentrain/mcp": "1.0.3",
51
- "@contentrain/query": "5.0.0",
52
- "@contentrain/types": "0.1.0"
49
+ "@contentrain/rules": "0.2.0",
50
+ "@contentrain/query": "5.0.1",
51
+ "@contentrain/types": "0.1.0",
52
+ "@contentrain/mcp": "1.0.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/ws": "^8.5.0",