@lnai/core 0.1.0 → 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.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  declare const UNIFIED_DIR = ".ai";
4
- declare const TOOL_IDS: readonly ["claudeCode", "opencode"];
4
+ declare const TOOL_IDS: readonly ["claudeCode", "opencode", "cursor"];
5
5
  type ToolId = (typeof TOOL_IDS)[number];
6
6
  declare const CONFIG_FILES: {
7
7
  readonly config: "config.json";
@@ -65,6 +65,7 @@ declare const toolConfigSchema: z.ZodObject<{
65
65
  declare const toolIdSchema: z.ZodEnum<{
66
66
  claudeCode: "claudeCode";
67
67
  opencode: "opencode";
68
+ cursor: "cursor";
68
69
  }>;
69
70
  /** Settings configuration (Claude format as source of truth) */
70
71
  declare const settingsSchema: z.ZodObject<{
@@ -87,6 +88,7 @@ declare const settingsSchema: z.ZodObject<{
87
88
  overrides: z.ZodOptional<z.ZodObject<{
88
89
  claudeCode: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
89
90
  opencode: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
91
+ cursor: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
90
92
  }, z.core.$strip>>;
91
93
  }, z.core.$strip>;
92
94
  /** Main config.json structure. Uses partial object to allow partial tool configs. */
@@ -100,6 +102,10 @@ declare const configSchema: z.ZodObject<{
100
102
  enabled: z.ZodBoolean;
101
103
  versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
102
104
  }, z.core.$strip>>;
105
+ cursor: z.ZodOptional<z.ZodObject<{
106
+ enabled: z.ZodBoolean;
107
+ versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
108
+ }, z.core.$strip>>;
103
109
  }, z.core.$strip>>;
104
110
  }, z.core.$strip>;
105
111
  /** Skill frontmatter (name and description required) */
@@ -134,18 +140,11 @@ interface MarkdownFrontmatter {
134
140
  /** All parsed configuration from the .ai directory */
135
141
  interface UnifiedState {
136
142
  config: {
137
- tools?: Partial<Record<ToolId, {
138
- enabled: boolean;
139
- versionControl?: boolean;
140
- }>>;
143
+ tools?: Partial<Record<ToolId, ToolConfig>>;
141
144
  };
142
145
  settings: {
143
- permissions?: {
144
- allow?: string[];
145
- ask?: string[];
146
- deny?: string[];
147
- };
148
- mcpServers?: Record<string, unknown>;
146
+ permissions?: Permissions;
147
+ mcpServers?: Record<string, McpServer>;
149
148
  overrides?: Partial<Record<ToolId, Record<string, unknown>>>;
150
149
  } | null;
151
150
  agents: string | null;
@@ -302,12 +301,13 @@ interface InitOptions {
302
301
  rootDir: string;
303
302
  tools?: ToolId[];
304
303
  minimal?: boolean;
304
+ force?: boolean;
305
305
  }
306
306
  interface InitResult {
307
307
  created: string[];
308
308
  }
309
+ declare function initUnifiedConfig(options: InitOptions): Promise<InitResult>;
309
310
  declare function hasUnifiedConfig(rootDir: string): Promise<boolean>;
310
311
  declare function generateDefaultConfig(tools?: ToolId[]): Config;
311
- declare function initUnifiedConfig(options: InitOptions): Promise<InitResult>;
312
312
 
313
313
  export { CONFIG_DIRS, CONFIG_FILES, type ChangeResult, type Config, FileNotFoundError, type InitOptions, type InitResult, LnaiError, type MarkdownFile, type MarkdownFrontmatter, type McpServer, type OutputFile, ParseError, type PermissionLevel, type Permissions, type Plugin, PluginError, type RuleFrontmatter, type Settings, type SkillFrontmatter, type SkippedFeatureDetail, type SyncOptions, type SyncResult, TOOL_IDS, TOOL_OUTPUT_DIRS, type ToolConfig, type ToolId, UNIFIED_DIR, type UnifiedState, ValidationError, type ValidationErrorDetail, type ValidationResult, type ValidationWarningDetail, WriteError, type WriterOptions, claudeCodePlugin, computeHash, configSchema, generateDefaultConfig, hasUnifiedConfig, initUnifiedConfig, mcpServerSchema, opencodePlugin, parseFrontmatter, parseUnifiedConfig, permissionsSchema, pluginRegistry, ruleFrontmatterSchema, runSyncPipeline, settingsSchema, skillFrontmatterSchema, toolConfigSchema, toolIdSchema, updateGitignore, validateConfig, validateSettings, validateUnifiedState, writeFiles };
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import * as crypto from 'crypto';
7
7
 
8
8
  // src/constants.ts
9
9
  var UNIFIED_DIR = ".ai";
10
- var TOOL_IDS = ["claudeCode", "opencode"];
10
+ var TOOL_IDS = ["claudeCode", "opencode", "cursor"];
11
11
  var CONFIG_FILES = {
12
12
  config: "config.json",
13
13
  settings: "settings.json",
@@ -20,11 +20,13 @@ var CONFIG_DIRS = {
20
20
  };
21
21
  var TOOL_OUTPUT_DIRS = {
22
22
  claudeCode: ".claude",
23
- opencode: ".opencode"
23
+ opencode: ".opencode",
24
+ cursor: ".cursor"
24
25
  };
25
26
  var OVERRIDE_DIRS = {
26
27
  claudeCode: ".claude",
27
- opencode: ".opencode"
28
+ opencode: ".opencode",
29
+ cursor: ".cursor"
28
30
  };
29
31
 
30
32
  // src/errors.ts
@@ -50,10 +52,10 @@ var ParseError = class extends LnaiError {
50
52
  var ValidationError = class extends LnaiError {
51
53
  path;
52
54
  value;
53
- constructor(message, path7, value) {
55
+ constructor(message, path5, value) {
54
56
  super(message, "VALIDATION_ERROR");
55
57
  this.name = "ValidationError";
56
- this.path = path7;
58
+ this.path = path5;
57
59
  this.value = value;
58
60
  }
59
61
  };
@@ -104,19 +106,21 @@ var toolConfigSchema = z.object({
104
106
  enabled: z.boolean(),
105
107
  versionControl: z.boolean().optional().default(false)
106
108
  });
107
- var toolIdSchema = z.enum(["claudeCode", "opencode"]);
109
+ var toolIdSchema = z.enum(["claudeCode", "opencode", "cursor"]);
108
110
  var settingsSchema = z.object({
109
111
  permissions: permissionsSchema.optional(),
110
112
  mcpServers: z.record(z.string(), mcpServerSchema).optional(),
111
113
  overrides: z.object({
112
114
  claudeCode: z.record(z.string(), z.unknown()).optional(),
113
- opencode: z.record(z.string(), z.unknown()).optional()
115
+ opencode: z.record(z.string(), z.unknown()).optional(),
116
+ cursor: z.record(z.string(), z.unknown()).optional()
114
117
  }).optional()
115
118
  });
116
119
  var configSchema = z.object({
117
120
  tools: z.object({
118
121
  claudeCode: toolConfigSchema,
119
- opencode: toolConfigSchema
122
+ opencode: toolConfigSchema,
123
+ cursor: toolConfigSchema
120
124
  }).partial().optional()
121
125
  });
122
126
  var skillFrontmatterSchema = z.object({
@@ -356,6 +360,26 @@ function validateUnifiedState(state) {
356
360
  skipped: []
357
361
  };
358
362
  }
363
+ function validateToolIds(tools) {
364
+ const invalidTools = tools.filter(
365
+ (t) => !TOOL_IDS.includes(t)
366
+ );
367
+ if (invalidTools.length > 0) {
368
+ return {
369
+ valid: false,
370
+ errors: [
371
+ {
372
+ path: ["tools"],
373
+ message: `Invalid tool(s): ${invalidTools.join(", ")}. Valid tools: ${TOOL_IDS.join(", ")}`,
374
+ value: invalidTools
375
+ }
376
+ ],
377
+ warnings: [],
378
+ skipped: []
379
+ };
380
+ }
381
+ return { valid: true, errors: [], warnings: [], skipped: [] };
382
+ }
359
383
  async function scanOverrideDirectory(rootDir, toolId) {
360
384
  const overrideDir = path.join(rootDir, UNIFIED_DIR, OVERRIDE_DIRS[toolId]);
361
385
  try {
@@ -392,6 +416,26 @@ async function fileExists2(filePath) {
392
416
  return false;
393
417
  }
394
418
  }
419
+ async function getOverrideOutputFiles(rootDir, toolId) {
420
+ const outputDir = TOOL_OUTPUT_DIRS[toolId];
421
+ const overrideFiles = await scanOverrideDirectory(rootDir, toolId);
422
+ const result = [];
423
+ for (const overrideFile of overrideFiles) {
424
+ const targetPath = path.join(rootDir, outputDir, overrideFile.relativePath);
425
+ if (await fileExists2(targetPath)) {
426
+ continue;
427
+ }
428
+ const symlinkPath = `${outputDir}/${overrideFile.relativePath}`;
429
+ const symlinkDir = path.dirname(symlinkPath);
430
+ const sourcePath = `${UNIFIED_DIR}/${OVERRIDE_DIRS[toolId]}/${overrideFile.relativePath}`;
431
+ result.push({
432
+ path: symlinkPath,
433
+ type: "symlink",
434
+ target: path.relative(symlinkDir, sourcePath)
435
+ });
436
+ }
437
+ return result;
438
+ }
395
439
 
396
440
  // src/plugins/claude-code/index.ts
397
441
  var claudeCodePlugin = {
@@ -448,22 +492,236 @@ var claudeCodePlugin = {
448
492
  content: finalSettings
449
493
  });
450
494
  }
451
- const overrideFiles = await scanOverrideDirectory(rootDir, "claudeCode");
452
- for (const overrideFile of overrideFiles) {
453
- const targetPath = path.join(
454
- rootDir,
455
- outputDir,
456
- overrideFile.relativePath
457
- );
458
- if (await fileExists2(targetPath)) {
459
- continue;
495
+ const overrideFiles = await getOverrideOutputFiles(rootDir, "claudeCode");
496
+ files.push(...overrideFiles);
497
+ return files;
498
+ },
499
+ validate(state) {
500
+ const warnings = [];
501
+ if (!state.agents) {
502
+ warnings.push({
503
+ path: ["AGENTS.md"],
504
+ message: "No AGENTS.md found - .claude/CLAUDE.md will not be created"
505
+ });
506
+ }
507
+ return { valid: true, errors: [], warnings, skipped: [] };
508
+ }
509
+ };
510
+
511
+ // src/plugins/cursor/transforms.ts
512
+ function transformRuleToCursor(rule) {
513
+ const description = deriveDescription(rule.path, rule.content);
514
+ const globs = rule.frontmatter.paths || [];
515
+ const alwaysApply = globs.length === 0;
516
+ return {
517
+ frontmatter: {
518
+ description,
519
+ globs,
520
+ alwaysApply
521
+ },
522
+ content: rule.content
523
+ };
524
+ }
525
+ function deriveDescription(filename, content) {
526
+ const headingMatch = content.match(/^#\s+(.+)$/m);
527
+ if (headingMatch && headingMatch[1]) {
528
+ return headingMatch[1];
529
+ }
530
+ const baseName = filename.replace(/\.md$/, "");
531
+ return baseName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
532
+ }
533
+ function serializeCursorRule(frontmatter, content) {
534
+ const lines = [
535
+ "---",
536
+ `description: ${JSON.stringify(frontmatter.description)}`
537
+ ];
538
+ lines.push("globs:");
539
+ for (const glob of frontmatter.globs) {
540
+ lines.push(` - ${JSON.stringify(glob)}`);
541
+ }
542
+ lines.push(`alwaysApply: ${frontmatter.alwaysApply}`);
543
+ lines.push("---");
544
+ lines.push("");
545
+ lines.push(content);
546
+ return lines.join("\n");
547
+ }
548
+ function transformMcpToCursor(servers) {
549
+ if (!servers || Object.keys(servers).length === 0) {
550
+ return void 0;
551
+ }
552
+ const result = {};
553
+ for (const [name, server] of Object.entries(servers)) {
554
+ if (server.type === "http" || server.type === "sse") {
555
+ const cursorServer = {
556
+ url: server.url
557
+ };
558
+ if (server.headers) {
559
+ cursorServer.headers = transformEnvVarsToCursor(server.headers);
560
+ }
561
+ result[name] = cursorServer;
562
+ } else if (server.command) {
563
+ const cursorServer = {
564
+ command: server.command
565
+ };
566
+ if (server.args && server.args.length > 0) {
567
+ cursorServer.args = server.args;
568
+ }
569
+ if (server.env) {
570
+ cursorServer.env = transformEnvVarsToCursor(server.env);
460
571
  }
572
+ result[name] = cursorServer;
573
+ }
574
+ }
575
+ return Object.keys(result).length > 0 ? result : void 0;
576
+ }
577
+ function transformEnvVarToCursor(value) {
578
+ return value.replace(/\$\{([^}:]+)(:-[^}]*)?\}/g, "${env:$1}");
579
+ }
580
+ function transformEnvVarsToCursor(env) {
581
+ const result = {};
582
+ for (const [key, value] of Object.entries(env)) {
583
+ result[key] = transformEnvVarToCursor(value);
584
+ }
585
+ return result;
586
+ }
587
+ function transformPermissionsToCursor(permissions) {
588
+ if (!permissions) {
589
+ return { permissions: void 0, hasAskPermissions: false };
590
+ }
591
+ const allow = [];
592
+ const deny = [];
593
+ let hasAskPermissions = false;
594
+ if (permissions.allow) {
595
+ for (const rule of permissions.allow) {
596
+ const transformed = transformPermissionRule(rule);
597
+ if (transformed) {
598
+ allow.push(transformed);
599
+ }
600
+ }
601
+ }
602
+ if (permissions.ask && permissions.ask.length > 0) {
603
+ hasAskPermissions = true;
604
+ for (const rule of permissions.ask) {
605
+ const transformed = transformPermissionRule(rule);
606
+ if (transformed) {
607
+ allow.push(transformed);
608
+ }
609
+ }
610
+ }
611
+ if (permissions.deny) {
612
+ for (const rule of permissions.deny) {
613
+ const transformed = transformPermissionRule(rule);
614
+ if (transformed) {
615
+ deny.push(transformed);
616
+ }
617
+ }
618
+ }
619
+ if (allow.length === 0 && deny.length === 0) {
620
+ return { permissions: void 0, hasAskPermissions };
621
+ }
622
+ return {
623
+ permissions: {
624
+ allow,
625
+ deny
626
+ },
627
+ hasAskPermissions
628
+ };
629
+ }
630
+ function transformPermissionRule(rule) {
631
+ const match = rule.match(/^(\w+)\(([^)]+)\)$/);
632
+ if (!match) {
633
+ return null;
634
+ }
635
+ const tool = match[1];
636
+ let pattern = match[2];
637
+ const cursorTool = tool.toLowerCase() === "bash" ? "Shell" : tool;
638
+ if (pattern.endsWith(":*")) {
639
+ pattern = pattern.slice(0, -2);
640
+ }
641
+ return `${cursorTool}(${pattern})`;
642
+ }
643
+
644
+ // src/plugins/cursor/index.ts
645
+ var cursorPlugin = {
646
+ id: "cursor",
647
+ name: "Cursor",
648
+ async detect(_rootDir) {
649
+ return false;
650
+ },
651
+ async import(_rootDir) {
652
+ return null;
653
+ },
654
+ async export(state, rootDir) {
655
+ const files = [];
656
+ const outputDir = TOOL_OUTPUT_DIRS.cursor;
657
+ if (state.agents) {
658
+ files.push({
659
+ path: "AGENTS.md",
660
+ type: "symlink",
661
+ target: `${UNIFIED_DIR}/AGENTS.md`
662
+ });
663
+ }
664
+ for (const rule of state.rules) {
665
+ const transformed = transformRuleToCursor(rule);
666
+ const ruleContent = serializeCursorRule(
667
+ transformed.frontmatter,
668
+ transformed.content
669
+ );
670
+ const outputFilename = rule.path.replace(/\.md$/, ".mdc");
671
+ files.push({
672
+ path: `${outputDir}/rules/${outputFilename}`,
673
+ type: "text",
674
+ content: ruleContent
675
+ });
676
+ }
677
+ for (const skill of state.skills) {
461
678
  files.push({
462
- path: `${outputDir}/${overrideFile.relativePath}`,
679
+ path: `${outputDir}/skills/${skill.path}`,
463
680
  type: "symlink",
464
- target: `../${UNIFIED_DIR}/${OVERRIDE_DIRS.claudeCode}/${overrideFile.relativePath}`
681
+ target: `../../${UNIFIED_DIR}/skills/${skill.path}`
682
+ });
683
+ }
684
+ const mcpServers = transformMcpToCursor(state.settings?.mcpServers);
685
+ const cursorOverrides = state.settings?.overrides?.cursor;
686
+ const hasMcpOverrides = cursorOverrides?.["mcpServers"] !== void 0;
687
+ if (mcpServers || hasMcpOverrides) {
688
+ let mcpContent = mcpServers ? { mcpServers } : {};
689
+ if (cursorOverrides?.["mcpServers"]) {
690
+ mcpContent = deepMergeConfigs(mcpContent, {
691
+ mcpServers: cursorOverrides["mcpServers"]
692
+ });
693
+ }
694
+ files.push({
695
+ path: `${outputDir}/mcp.json`,
696
+ type: "json",
697
+ content: mcpContent
698
+ });
699
+ }
700
+ const permissionsResult = transformPermissionsToCursor(
701
+ state.settings?.permissions
702
+ );
703
+ const hasCliOverrides = cursorOverrides !== void 0 && Object.keys(cursorOverrides).some((key) => key !== "mcpServers");
704
+ if (permissionsResult.permissions || hasCliOverrides) {
705
+ let cliContent = permissionsResult.permissions ? { permissions: permissionsResult.permissions } : {};
706
+ if (cursorOverrides) {
707
+ const cliOverrides = {};
708
+ for (const [key, value] of Object.entries(cursorOverrides)) {
709
+ if (key !== "mcpServers") {
710
+ cliOverrides[key] = value;
711
+ }
712
+ }
713
+ if (Object.keys(cliOverrides).length > 0) {
714
+ cliContent = deepMergeConfigs(cliContent, cliOverrides);
715
+ }
716
+ }
717
+ files.push({
718
+ path: `${outputDir}/cli.json`,
719
+ type: "json",
720
+ content: cliContent
465
721
  });
466
722
  }
723
+ const overrideFiles = await getOverrideOutputFiles(rootDir, "cursor");
724
+ files.push(...overrideFiles);
467
725
  return files;
468
726
  },
469
727
  validate(state) {
@@ -471,9 +729,31 @@ var claudeCodePlugin = {
471
729
  if (!state.agents) {
472
730
  warnings.push({
473
731
  path: ["AGENTS.md"],
474
- message: "No AGENTS.md found - .claude/CLAUDE.md will not be created"
732
+ message: "No AGENTS.md found - root AGENTS.md will not be created"
733
+ });
734
+ }
735
+ const permissionsResult = transformPermissionsToCursor(
736
+ state.settings?.permissions
737
+ );
738
+ if (permissionsResult.hasAskPermissions) {
739
+ warnings.push({
740
+ path: ["settings", "permissions", "ask"],
741
+ message: 'Cursor does not support "ask" permission level - these rules will be treated as "allow"'
475
742
  });
476
743
  }
744
+ const mcpServers = state.settings?.mcpServers;
745
+ if (mcpServers) {
746
+ for (const [name, server] of Object.entries(mcpServers)) {
747
+ const isRemote = server.type === "http" || server.type === "sse";
748
+ const hasCommand = !!server.command;
749
+ if (!isRemote && !hasCommand) {
750
+ warnings.push({
751
+ path: ["settings", "mcpServers", name],
752
+ message: `MCP server "${name}" has no command or type - it will be skipped`
753
+ });
754
+ }
755
+ }
756
+ }
477
757
  return { valid: true, errors: [], warnings, skipped: [] };
478
758
  }
479
759
  };
@@ -484,8 +764,7 @@ function transformMcpToOpenCode(servers) {
484
764
  return void 0;
485
765
  }
486
766
  const result = {};
487
- for (const [name, serverRaw] of Object.entries(servers)) {
488
- const server = serverRaw;
767
+ for (const [name, server] of Object.entries(servers)) {
489
768
  if (server.type === "http" || server.type === "sse") {
490
769
  const openCodeServer = {
491
770
  type: "remote",
@@ -628,22 +907,8 @@ var opencodePlugin = {
628
907
  type: "json",
629
908
  content: finalConfig
630
909
  });
631
- const overrideFiles = await scanOverrideDirectory(rootDir, "opencode");
632
- for (const overrideFile of overrideFiles) {
633
- const targetPath = path.join(
634
- rootDir,
635
- outputDir,
636
- overrideFile.relativePath
637
- );
638
- if (await fileExists2(targetPath)) {
639
- continue;
640
- }
641
- files.push({
642
- path: `${outputDir}/${overrideFile.relativePath}`,
643
- type: "symlink",
644
- target: `../${UNIFIED_DIR}/${OVERRIDE_DIRS.opencode}/${overrideFile.relativePath}`
645
- });
646
- }
910
+ const overrideFiles = await getOverrideOutputFiles(rootDir, "opencode");
911
+ files.push(...overrideFiles);
647
912
  return files;
648
913
  },
649
914
  validate(state) {
@@ -681,6 +946,7 @@ var pluginRegistry = new PluginRegistry();
681
946
 
682
947
  // src/plugins/index.ts
683
948
  pluginRegistry.register(claudeCodePlugin);
949
+ pluginRegistry.register(cursorPlugin);
684
950
  pluginRegistry.register(opencodePlugin);
685
951
  function computeHash(content) {
686
952
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
@@ -821,16 +1087,26 @@ function getToolsToSync(config, requestedTools) {
821
1087
  }
822
1088
  async function runSyncPipeline(options) {
823
1089
  const { rootDir, dryRun = false, tools: requestedTools } = options;
1090
+ if (requestedTools && requestedTools.length > 0) {
1091
+ const toolValidation = validateToolIds(requestedTools);
1092
+ if (!toolValidation.valid) {
1093
+ const error = toolValidation.errors[0];
1094
+ throw new ValidationError(
1095
+ error?.message ?? "Invalid tools",
1096
+ error?.path ?? ["tools"],
1097
+ error?.value
1098
+ );
1099
+ }
1100
+ }
824
1101
  const state = await parseUnifiedConfig(rootDir);
825
1102
  const unifiedValidation = validateUnifiedState(state);
826
1103
  if (!unifiedValidation.valid) {
827
- return [
828
- {
829
- tool: "claudeCode",
830
- changes: [],
831
- validation: unifiedValidation
832
- }
833
- ];
1104
+ const errorMessages = unifiedValidation.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
1105
+ throw new ValidationError(
1106
+ `Unified config validation failed: ${errorMessages}`,
1107
+ ["config"],
1108
+ unifiedValidation.errors
1109
+ );
834
1110
  }
835
1111
  const toolsToSync = getToolsToSync(state.config, requestedTools);
836
1112
  if (toolsToSync.length === 0) {
@@ -861,35 +1137,24 @@ async function runSyncPipeline(options) {
861
1137
  }
862
1138
  return results;
863
1139
  }
864
- async function hasUnifiedConfig(rootDir) {
865
- const aiDir = path.join(rootDir, UNIFIED_DIR);
866
- try {
867
- const stats = await fs3.stat(aiDir);
868
- return stats.isDirectory();
869
- } catch {
870
- return false;
871
- }
872
- }
873
- function generateDefaultConfig(tools) {
874
- const enabledTools = tools ?? TOOL_IDS;
875
- const toolsConfig = {};
876
- for (const toolId of TOOL_IDS) {
877
- toolsConfig[toolId] = {
878
- enabled: enabledTools.includes(toolId),
879
- versionControl: false
880
- };
881
- }
882
- return {
883
- tools: toolsConfig
884
- };
885
- }
886
1140
  async function initUnifiedConfig(options) {
887
- const { rootDir, tools, minimal = false } = options;
1141
+ const { rootDir, tools, minimal = false, force = false } = options;
888
1142
  const aiDir = path.join(rootDir, UNIFIED_DIR);
889
1143
  const created = [];
1144
+ const config = generateDefaultConfig(tools);
1145
+ const exists = await hasUnifiedConfig(rootDir);
1146
+ if (exists && !force) {
1147
+ throw new ValidationError(
1148
+ `Directory ${UNIFIED_DIR}/ already exists. Use force option to overwrite.`,
1149
+ [UNIFIED_DIR],
1150
+ { exists: true }
1151
+ );
1152
+ }
1153
+ if (exists) {
1154
+ await fs3.rm(aiDir, { recursive: true, force: true });
1155
+ }
890
1156
  await fs3.mkdir(aiDir, { recursive: true });
891
1157
  created.push(UNIFIED_DIR);
892
- const config = generateDefaultConfig(tools);
893
1158
  const configPath = path.join(aiDir, CONFIG_FILES.config);
894
1159
  await fs3.writeFile(
895
1160
  configPath,
@@ -909,7 +1174,36 @@ async function initUnifiedConfig(options) {
909
1174
  }
910
1175
  return { created };
911
1176
  }
1177
+ async function hasUnifiedConfig(rootDir) {
1178
+ const aiDir = path.join(rootDir, UNIFIED_DIR);
1179
+ try {
1180
+ const stats = await fs3.stat(aiDir);
1181
+ return stats.isDirectory();
1182
+ } catch {
1183
+ return false;
1184
+ }
1185
+ }
1186
+ function generateDefaultConfig(tools) {
1187
+ if (tools) {
1188
+ const validation = validateToolIds(tools);
1189
+ if (!validation.valid) {
1190
+ const error = validation.errors[0];
1191
+ throw new ValidationError(
1192
+ error?.message ?? "Invalid tools",
1193
+ error?.path ?? ["tools"],
1194
+ error?.value
1195
+ );
1196
+ }
1197
+ }
1198
+ const enabledTools = tools ?? TOOL_IDS;
1199
+ const toolsConfig = {};
1200
+ for (const toolId of TOOL_IDS) {
1201
+ toolsConfig[toolId] = {
1202
+ enabled: enabledTools.includes(toolId),
1203
+ versionControl: false
1204
+ };
1205
+ }
1206
+ return { tools: toolsConfig };
1207
+ }
912
1208
 
913
1209
  export { CONFIG_DIRS, CONFIG_FILES, FileNotFoundError, LnaiError, ParseError, PluginError, TOOL_IDS, TOOL_OUTPUT_DIRS, UNIFIED_DIR, ValidationError, WriteError, claudeCodePlugin, computeHash, configSchema, generateDefaultConfig, hasUnifiedConfig, initUnifiedConfig, mcpServerSchema, opencodePlugin, parseFrontmatter, parseUnifiedConfig, permissionsSchema, pluginRegistry, ruleFrontmatterSchema, runSyncPipeline, settingsSchema, skillFrontmatterSchema, toolConfigSchema, toolIdSchema, updateGitignore, validateConfig, validateSettings, validateUnifiedState, writeFiles };
914
- //# sourceMappingURL=index.js.map
915
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lnai/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Core library for LNAI - unified AI config management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,6 +19,7 @@
19
19
  "config",
20
20
  "configuration",
21
21
  "claude",
22
+ "cursor",
22
23
  "opencode",
23
24
  "cli",
24
25
  "ai-tools"
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/schemas/index.ts","../src/parser/frontmatter.ts","../src/parser/index.ts","../src/validator/index.ts","../src/utils/overrides.ts","../src/plugins/claude-code/index.ts","../src/plugins/opencode/transforms.ts","../src/plugins/opencode/index.ts","../src/plugins/registry.ts","../src/plugins/index.ts","../src/writer/index.ts","../src/pipeline/index.ts","../src/init/index.ts"],"names":["path","fs","path2","fs2","fileExists","path3","path4","path5","path6","fs4"],"mappings":"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","file":"index.js","sourcesContent":["export const UNIFIED_DIR = \".ai\";\n\nexport const TOOL_IDS = [\"claudeCode\", \"opencode\"] as const;\n\nexport type ToolId = (typeof TOOL_IDS)[number];\n\nexport const CONFIG_FILES = {\n config: \"config.json\",\n settings: \"settings.json\",\n agents: \"AGENTS.md\",\n} as const;\n\nexport const CONFIG_DIRS = {\n rules: \"rules\",\n skills: \"skills\",\n subagents: \"subagents\",\n} as const;\n\nexport const TOOL_OUTPUT_DIRS: Record<ToolId, string> = {\n claudeCode: \".claude\",\n opencode: \".opencode\",\n};\n\n/** Tool-specific override directories within .ai/ */\nexport const OVERRIDE_DIRS: Record<ToolId, string> = {\n claudeCode: \".claude\",\n opencode: \".opencode\",\n};\n","export class LnaiError extends Error {\n public readonly code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.name = \"LnaiError\";\n this.code = code;\n }\n}\n\nexport class ParseError extends LnaiError {\n public readonly filePath: string;\n\n constructor(message: string, filePath: string, cause?: Error) {\n super(message, \"PARSE_ERROR\");\n this.name = \"ParseError\";\n this.filePath = filePath;\n if (cause) {\n this.cause = cause;\n }\n }\n}\n\nexport class ValidationError extends LnaiError {\n public readonly path: string[];\n public readonly value?: unknown;\n\n constructor(message: string, path: string[], value?: unknown) {\n super(message, \"VALIDATION_ERROR\");\n this.name = \"ValidationError\";\n this.path = path;\n this.value = value;\n }\n}\n\nexport class FileNotFoundError extends LnaiError {\n public readonly filePath: string;\n\n constructor(message: string, filePath: string) {\n super(message, \"FILE_NOT_FOUND\");\n this.name = \"FileNotFoundError\";\n this.filePath = filePath;\n }\n}\n\nexport class WriteError extends LnaiError {\n public readonly filePath: string;\n\n constructor(message: string, filePath: string, cause?: Error) {\n super(message, \"WRITE_ERROR\");\n this.name = \"WriteError\";\n this.filePath = filePath;\n if (cause) {\n this.cause = cause;\n }\n }\n}\n\nexport class PluginError extends LnaiError {\n public readonly pluginId: string;\n\n constructor(message: string, pluginId: string, cause?: Error) {\n super(message, \"PLUGIN_ERROR\");\n this.name = \"PluginError\";\n this.pluginId = pluginId;\n if (cause) {\n this.cause = cause;\n }\n }\n}\n","import { z } from \"zod\";\n\n/** MCP Server configuration (Claude format as source of truth) */\nexport const mcpServerSchema = z.object({\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n type: z.enum([\"http\", \"sse\"]).optional(),\n url: z.string().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n});\n\nexport const permissionsSchema = z.object({\n allow: z.array(z.string()).optional(),\n ask: z.array(z.string()).optional(),\n deny: z.array(z.string()).optional(),\n});\n\nexport const toolConfigSchema = z.object({\n enabled: z.boolean(),\n versionControl: z.boolean().optional().default(false),\n});\n\nexport const toolIdSchema = z.enum([\"claudeCode\", \"opencode\"]);\n\n/** Settings configuration (Claude format as source of truth) */\nexport const settingsSchema = z.object({\n permissions: permissionsSchema.optional(),\n mcpServers: z.record(z.string(), mcpServerSchema).optional(),\n overrides: z\n .object({\n claudeCode: z.record(z.string(), z.unknown()).optional(),\n opencode: z.record(z.string(), z.unknown()).optional(),\n })\n .optional(),\n});\n\n/** Main config.json structure. Uses partial object to allow partial tool configs. */\nexport const configSchema = z.object({\n tools: z\n .object({\n claudeCode: toolConfigSchema,\n opencode: toolConfigSchema,\n })\n .partial()\n .optional(),\n});\n\n/** Skill frontmatter (name and description required) */\nexport const skillFrontmatterSchema = z.object({\n name: z.string(),\n description: z.string(),\n});\n\n/** Rule frontmatter (paths required) */\nexport const ruleFrontmatterSchema = z.object({\n paths: z.array(z.string()).min(1),\n});\n\nexport type McpServer = z.infer<typeof mcpServerSchema>;\nexport type Permissions = z.infer<typeof permissionsSchema>;\nexport type ToolConfig = z.infer<typeof toolConfigSchema>;\nexport type Settings = z.infer<typeof settingsSchema>;\nexport type Config = z.infer<typeof configSchema>;\nexport type SkillFrontmatter = z.infer<typeof skillFrontmatterSchema>;\nexport type RuleFrontmatter = z.infer<typeof ruleFrontmatterSchema>;\n","import matter from \"gray-matter\";\n\nexport function parseFrontmatter(content: string): {\n frontmatter: Record<string, unknown>;\n content: string;\n} {\n const parsed = matter(content);\n return {\n frontmatter: parsed.data as Record<string, unknown>,\n content: parsed.content.trim(),\n };\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport { CONFIG_DIRS, CONFIG_FILES, UNIFIED_DIR } from \"../constants\";\nimport { FileNotFoundError, ParseError } from \"../errors\";\nimport type {\n MarkdownFile,\n RuleFrontmatter,\n SkillFrontmatter,\n UnifiedState,\n} from \"../types/index\";\nimport { parseFrontmatter } from \"./frontmatter\";\n\nexport { parseFrontmatter } from \"./frontmatter\";\n\n/**\n * Parse the unified .ai/ configuration directory.\n * Reads config.json, settings.json, AGENTS.md, rules, and skills.\n */\nexport async function parseUnifiedConfig(\n rootDir: string\n): Promise<UnifiedState> {\n const aiDir = path.join(rootDir, UNIFIED_DIR);\n\n if (!(await fileExists(aiDir))) {\n throw new FileNotFoundError(\n `Unified config directory not found: ${aiDir}`,\n aiDir\n );\n }\n\n const configPath = path.join(aiDir, CONFIG_FILES.config);\n let config: UnifiedState[\"config\"];\n try {\n config = await readJsonFile<UnifiedState[\"config\"]>(configPath);\n } catch (error) {\n if (error instanceof FileNotFoundError) {\n config = { tools: {} };\n } else {\n throw error;\n }\n }\n\n const settingsPath = path.join(aiDir, CONFIG_FILES.settings);\n let settings: UnifiedState[\"settings\"] = null;\n if (await fileExists(settingsPath)) {\n settings = await readJsonFile<UnifiedState[\"settings\"]>(settingsPath);\n }\n\n const agentsPath = path.join(aiDir, CONFIG_FILES.agents);\n let agents: string | null = null;\n if (await fileExists(agentsPath)) {\n agents = await readMarkdownFile(agentsPath);\n }\n\n const rulesDir = path.join(aiDir, CONFIG_DIRS.rules);\n const rules = await readMarkdownDirectory<RuleFrontmatter>(rulesDir);\n\n const skillsDir = path.join(aiDir, CONFIG_DIRS.skills);\n const skills = await readSkillsDirectory(skillsDir);\n\n return {\n config,\n settings,\n agents,\n rules,\n skills,\n };\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJsonFile<T>(filePath: string): Promise<T> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n } catch (error) {\n if ((error as { code?: string }).code === \"ENOENT\") {\n throw new FileNotFoundError(`File not found: ${filePath}`, filePath);\n }\n throw new ParseError(\n `Failed to parse JSON: ${filePath}`,\n filePath,\n error as Error\n );\n }\n}\n\nasync function readMarkdownFile(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch (error) {\n if ((error as { code?: string }).code === \"ENOENT\") {\n throw new FileNotFoundError(`File not found: ${filePath}`, filePath);\n }\n throw new ParseError(\n `Failed to read markdown: ${filePath}`,\n filePath,\n error as Error\n );\n }\n}\n\nasync function readMarkdownDirectory<T>(\n dirPath: string\n): Promise<MarkdownFile<T>[]> {\n const files: MarkdownFile<T>[] = [];\n\n if (!(await fileExists(dirPath))) {\n return files;\n }\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith(\".md\")) {\n const filePath = path.join(dirPath, entry.name);\n const content = await readMarkdownFile(filePath);\n const parsed = parseFrontmatter(content);\n\n files.push({\n path: entry.name,\n frontmatter: parsed.frontmatter as T,\n content: parsed.content,\n });\n }\n }\n\n return files;\n}\n\nasync function readSkillsDirectory(\n dirPath: string\n): Promise<MarkdownFile<SkillFrontmatter>[]> {\n const skills: MarkdownFile<SkillFrontmatter>[] = [];\n\n if (!(await fileExists(dirPath))) {\n return skills;\n }\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const skillFile = path.join(dirPath, entry.name, \"SKILL.md\");\n if (await fileExists(skillFile)) {\n const content = await readMarkdownFile(skillFile);\n const parsed = parseFrontmatter(content);\n\n skills.push({\n path: entry.name,\n frontmatter: parsed.frontmatter as SkillFrontmatter,\n content: parsed.content,\n });\n }\n }\n }\n\n return skills;\n}\n","import type { z } from \"zod\";\n\nimport {\n configSchema,\n ruleFrontmatterSchema,\n settingsSchema,\n skillFrontmatterSchema,\n} from \"../schemas/index\";\nimport type {\n UnifiedState,\n ValidationErrorDetail,\n ValidationResult,\n} from \"../types/index\";\n\n/**\n * Convert Zod issues to ValidationErrorDetail array\n */\nfunction zodIssuesToErrors(\n issues: z.core.$ZodIssue[],\n prefix: string[] = []\n): ValidationErrorDetail[] {\n return issues.map((issue) => ({\n path: [...prefix, ...issue.path.map(String)],\n message: issue.message,\n value: undefined,\n }));\n}\n\n/**\n * Validate config.json structure using Zod\n */\nexport function validateConfig(config: unknown): ValidationResult {\n const result = configSchema.safeParse(config);\n\n if (result.success) {\n return {\n valid: true,\n errors: [],\n warnings: [],\n skipped: [],\n };\n }\n\n return {\n valid: false,\n errors: zodIssuesToErrors(result.error.issues, [\"config\"]),\n warnings: [],\n skipped: [],\n };\n}\n\n/**\n * Validate settings.json structure using Zod\n */\nexport function validateSettings(settings: unknown): ValidationResult {\n if (settings === null || settings === undefined) {\n return {\n valid: true,\n errors: [],\n warnings: [],\n skipped: [],\n };\n }\n\n const result = settingsSchema.safeParse(settings);\n\n if (result.success) {\n return {\n valid: true,\n errors: [],\n warnings: [],\n skipped: [],\n };\n }\n\n return {\n valid: false,\n errors: zodIssuesToErrors(result.error.issues, [\"settings\"]),\n warnings: [],\n skipped: [],\n };\n}\n\n/**\n * Validate skill frontmatter\n */\nfunction validateSkillFrontmatter(\n frontmatter: unknown,\n skillPath: string\n): ValidationErrorDetail[] {\n const result = skillFrontmatterSchema.safeParse(frontmatter);\n\n if (result.success) {\n return [];\n }\n\n return zodIssuesToErrors(result.error.issues, [\n \"skills\",\n skillPath,\n \"frontmatter\",\n ]);\n}\n\n/**\n * Validate rule frontmatter\n */\nfunction validateRuleFrontmatter(\n frontmatter: unknown,\n rulePath: string\n): ValidationErrorDetail[] {\n const result = ruleFrontmatterSchema.safeParse(frontmatter);\n\n if (result.success) {\n return [];\n }\n\n return zodIssuesToErrors(result.error.issues, [\n \"rules\",\n rulePath,\n \"frontmatter\",\n ]);\n}\n\n/**\n * Validate the unified configuration state\n * @param state - Parsed unified state\n * @returns Validation result\n */\nexport function validateUnifiedState(state: UnifiedState): ValidationResult {\n const errors: ValidationErrorDetail[] = [];\n const warnings: ValidationErrorDetail[] = [];\n\n // 1. Validate config.json\n const configResult = validateConfig(state.config);\n errors.push(...configResult.errors);\n\n // 2. Validate settings.json\n const settingsResult = validateSettings(state.settings);\n errors.push(...settingsResult.errors);\n\n // 3. Validate each skill's frontmatter\n for (const skill of state.skills) {\n const skillErrors = validateSkillFrontmatter(skill.frontmatter, skill.path);\n errors.push(...skillErrors);\n }\n\n // 4. Validate each rule's frontmatter\n for (const rule of state.rules) {\n const ruleErrors = validateRuleFrontmatter(rule.frontmatter, rule.path);\n errors.push(...ruleErrors);\n }\n\n // 5. Add warnings for missing optional files\n if (!state.agents) {\n warnings.push({\n path: [\"AGENTS.md\"],\n message: \"AGENTS.md not found - no main instructions will be exported\",\n });\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n skipped: [],\n };\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport deepmerge from \"deepmerge\";\n\nimport type { ToolId } from \"../constants\";\nimport { OVERRIDE_DIRS, UNIFIED_DIR } from \"../constants\";\n\n/**\n * Information about a file in the override directory\n */\nexport interface OverrideFile {\n /** Relative path from the override directory (e.g., \"commands/custom.md\") */\n relativePath: string;\n /** Absolute path to the file */\n absolutePath: string;\n}\n\n/**\n * Recursively scan an override directory for all files.\n * Returns empty array if the directory doesn't exist.\n */\nexport async function scanOverrideDirectory(\n rootDir: string,\n toolId: ToolId\n): Promise<OverrideFile[]> {\n const overrideDir = path.join(rootDir, UNIFIED_DIR, OVERRIDE_DIRS[toolId]);\n\n try {\n await fs.access(overrideDir);\n } catch {\n return [];\n }\n\n const files: OverrideFile[] = [];\n await scanDir(overrideDir, overrideDir, files);\n return files;\n}\n\nasync function scanDir(\n baseDir: string,\n currentDir: string,\n files: OverrideFile[]\n): Promise<void> {\n const entries = await fs.readdir(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const absolutePath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await scanDir(baseDir, absolutePath, files);\n } else if (entry.isFile()) {\n const relativePath = path.relative(baseDir, absolutePath);\n files.push({ relativePath, absolutePath });\n }\n }\n}\n\nexport async function parseJsonFile(\n filePath: string\n): Promise<Record<string, unknown>> {\n const content = await fs.readFile(filePath, \"utf-8\");\n return JSON.parse(content) as Record<string, unknown>;\n}\n\n/**\n * Deep merge two config objects. Arrays are concatenated and deduplicated.\n */\nexport function deepMergeConfigs<T extends Record<string, unknown>>(\n base: T,\n override: Record<string, unknown>\n): T {\n return deepmerge(base, override, {\n arrayMerge: (target, source) => [...new Set([...target, ...source])],\n }) as T;\n}\n\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","import * as path from \"node:path\";\n\nimport { OVERRIDE_DIRS, TOOL_OUTPUT_DIRS, UNIFIED_DIR } from \"../../constants\";\nimport type {\n OutputFile,\n UnifiedState,\n ValidationResult,\n} from \"../../types/index\";\nimport {\n deepMergeConfigs,\n fileExists,\n scanOverrideDirectory,\n} from \"../../utils/overrides\";\nimport type { Plugin } from \"../types\";\n\n/**\n * Claude Code plugin for exporting to .claude/ format\n *\n * Output structure:\n * - .claude/CLAUDE.md (symlink -> ../.ai/AGENTS.md)\n * - .claude/rules/ (symlink -> ../.ai/rules)\n * - .claude/skills/<name>/ (symlink -> ../../.ai/skills/<name>)\n * - .claude/settings.json (generated settings merged with .ai/.claude/settings.json)\n * - .claude/<path> (symlink -> ../.ai/.claude/<path>) for other override files\n */\nexport const claudeCodePlugin: Plugin = {\n id: \"claudeCode\",\n name: \"Claude Code\",\n\n async detect(_rootDir: string): Promise<boolean> {\n // TODO: Implement in v0.2\n return false;\n },\n\n async import(_rootDir: string): Promise<Partial<UnifiedState> | null> {\n // TODO: Implement in v0.2\n return null;\n },\n\n async export(state: UnifiedState, rootDir: string): Promise<OutputFile[]> {\n const files: OutputFile[] = [];\n const outputDir = TOOL_OUTPUT_DIRS.claudeCode;\n\n if (state.agents) {\n files.push({\n path: `${outputDir}/CLAUDE.md`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/AGENTS.md`,\n });\n }\n\n if (state.rules.length > 0) {\n files.push({\n path: `${outputDir}/rules`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/rules`,\n });\n }\n\n for (const skill of state.skills) {\n files.push({\n path: `${outputDir}/skills/${skill.path}`,\n type: \"symlink\",\n target: `../../${UNIFIED_DIR}/skills/${skill.path}`,\n });\n }\n\n const baseSettings: Record<string, unknown> = {};\n if (state.settings?.permissions) {\n baseSettings[\"permissions\"] = state.settings.permissions;\n }\n if (state.settings?.mcpServers) {\n baseSettings[\"mcpServers\"] = state.settings.mcpServers;\n }\n\n let finalSettings = baseSettings;\n if (state.settings?.overrides?.claudeCode) {\n finalSettings = deepMergeConfigs(\n baseSettings,\n state.settings.overrides.claudeCode\n );\n }\n\n if (Object.keys(finalSettings).length > 0) {\n files.push({\n path: `${outputDir}/settings.json`,\n type: \"json\",\n content: finalSettings,\n });\n }\n\n const overrideFiles = await scanOverrideDirectory(rootDir, \"claudeCode\");\n for (const overrideFile of overrideFiles) {\n const targetPath = path.join(\n rootDir,\n outputDir,\n overrideFile.relativePath\n );\n if (await fileExists(targetPath)) {\n continue;\n }\n files.push({\n path: `${outputDir}/${overrideFile.relativePath}`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/${OVERRIDE_DIRS.claudeCode}/${overrideFile.relativePath}`,\n });\n }\n\n return files;\n },\n\n validate(state: UnifiedState): ValidationResult {\n const warnings: { path: string[]; message: string }[] = [];\n if (!state.agents) {\n warnings.push({\n path: [\"AGENTS.md\"],\n message: \"No AGENTS.md found - .claude/CLAUDE.md will not be created\",\n });\n }\n return { valid: true, errors: [], warnings, skipped: [] };\n },\n};\n","interface OpenCodeMcpServer {\n type: \"local\" | \"remote\";\n command?: string[];\n url?: string;\n environment?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\ntype OpenCodePermission = Record<\n string,\n Record<string, \"allow\" | \"ask\" | \"deny\">\n>;\n\ninterface ClaudeMcpServer {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n type?: \"http\" | \"sse\";\n url?: string;\n headers?: Record<string, string>;\n}\n\n/**\n * Transform MCP servers from Claude format to OpenCode format.\n *\n * Claude: { command: \"npx\", args: [\"-y\", \"@example/db\"], env: { \"DB_URL\": \"${DB_URL}\" } }\n * OpenCode: { type: \"local\", command: [\"npx\", \"-y\", \"@example/db\"], environment: { \"DB_URL\": \"{env:DB_URL}\" } }\n */\nexport function transformMcpToOpenCode(\n servers: Record<string, unknown> | undefined\n): Record<string, OpenCodeMcpServer> | undefined {\n if (!servers || Object.keys(servers).length === 0) {\n return undefined;\n }\n\n const result: Record<string, OpenCodeMcpServer> = {};\n\n for (const [name, serverRaw] of Object.entries(servers)) {\n const server = serverRaw as ClaudeMcpServer;\n\n if (server.type === \"http\" || server.type === \"sse\") {\n const openCodeServer: OpenCodeMcpServer = {\n type: \"remote\",\n url: server.url,\n };\n if (server.headers) {\n openCodeServer.headers = server.headers;\n }\n result[name] = openCodeServer;\n } else if (server.command) {\n const command = [server.command, ...(server.args || [])];\n const openCodeServer: OpenCodeMcpServer = {\n type: \"local\",\n command,\n };\n if (server.env) {\n openCodeServer.environment = transformEnvVars(server.env);\n }\n result[name] = openCodeServer;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Transform permissions from Claude format to OpenCode format.\n *\n * Claude: { allow: [\"Bash(git:*)\"], ask: [\"Bash(npm:*)\"], deny: [\"Read(.env)\"] }\n * OpenCode: { \"bash\": { \"git *\": \"allow\", \"npm *\": \"ask\" }, \"read\": { \".env\": \"deny\" } }\n */\nexport function transformPermissionsToOpenCode(\n permissions: { allow?: string[]; ask?: string[]; deny?: string[] } | undefined\n): OpenCodePermission | undefined {\n if (!permissions) {\n return undefined;\n }\n\n const result: OpenCodePermission = {};\n\n const processRules = (\n rules: string[] | undefined,\n level: \"allow\" | \"ask\" | \"deny\"\n ) => {\n if (!rules) {\n return;\n }\n\n for (const rule of rules) {\n const parsed = parsePermissionRule(rule);\n if (!parsed) {\n continue;\n }\n\n const { tool, pattern } = parsed;\n if (!result[tool]) {\n result[tool] = {};\n }\n\n result[tool]![pattern] = level;\n }\n };\n\n // Process in priority order: allow first, then ask, then deny (highest priority overwrites)\n processRules(permissions.allow, \"allow\");\n processRules(permissions.ask, \"ask\");\n processRules(permissions.deny, \"deny\");\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n// --- Helper functions ---\n\n/** Transform ${VAR} or ${VAR:-default} to {env:VAR} */\nfunction transformEnvVar(value: string): string {\n return value.replace(/\\$\\{([^}:]+)(:-[^}]*)?\\}/g, \"{env:$1}\");\n}\n\nfunction transformEnvVars(env: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n result[key] = transformEnvVar(value);\n }\n return result;\n}\n\n/** Parse \"Tool(pattern)\" format, returns { tool, pattern } or null */\nfunction parsePermissionRule(\n rule: string\n): { tool: string; pattern: string } | null {\n const match = rule.match(/^(\\w+)\\(([^)]+)\\)$/);\n if (!match) {\n return null;\n }\n\n const tool = match[1];\n const pattern = match[2];\n\n if (!tool || !pattern) {\n return null;\n }\n\n const normalizedTool = tool.toLowerCase();\n\n // Convert `:*` to ` *` (word boundary)\n let normalizedPattern = pattern;\n if (normalizedPattern.includes(\":*\")) {\n normalizedPattern = normalizedPattern.replace(/:(\\*)/g, \" $1\");\n }\n\n return {\n tool: normalizedTool,\n pattern: normalizedPattern,\n };\n}\n","import * as path from \"node:path\";\n\nimport { OVERRIDE_DIRS, TOOL_OUTPUT_DIRS, UNIFIED_DIR } from \"../../constants\";\nimport type {\n OutputFile,\n UnifiedState,\n ValidationResult,\n} from \"../../types/index\";\nimport {\n deepMergeConfigs,\n fileExists,\n scanOverrideDirectory,\n} from \"../../utils/overrides\";\nimport type { Plugin } from \"../types\";\nimport {\n transformMcpToOpenCode,\n transformPermissionsToOpenCode,\n} from \"./transforms\";\n\n/**\n * OpenCode plugin for exporting to opencode.json format\n *\n * Output structure:\n * - .opencode/AGENTS.md (symlink -> ../.ai/AGENTS.md)\n * - .opencode/rules/ (symlink -> ../.ai/rules)\n * - .opencode/skills/<name>/ (symlink -> ../../.ai/skills/<name>)\n * - opencode.json (generated config merged with .ai/.opencode/opencode.json)\n * - .opencode/<path> (symlink -> ../.ai/.opencode/<path>) for other override files\n */\nexport const opencodePlugin: Plugin = {\n id: \"opencode\",\n name: \"OpenCode\",\n\n async detect(_rootDir: string): Promise<boolean> {\n // TODO: Implement in v0.2\n return false;\n },\n\n async import(_rootDir: string): Promise<Partial<UnifiedState> | null> {\n // TODO: Implement in v0.2\n return null;\n },\n\n async export(state: UnifiedState, rootDir: string): Promise<OutputFile[]> {\n const files: OutputFile[] = [];\n const outputDir = TOOL_OUTPUT_DIRS.opencode;\n\n if (state.agents) {\n files.push({\n path: `${outputDir}/AGENTS.md`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/AGENTS.md`,\n });\n }\n\n if (state.rules.length > 0) {\n files.push({\n path: `${outputDir}/rules`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/rules`,\n });\n }\n\n for (const skill of state.skills) {\n files.push({\n path: `${outputDir}/skills/${skill.path}`,\n type: \"symlink\",\n target: `../../${UNIFIED_DIR}/skills/${skill.path}`,\n });\n }\n\n const baseConfig: Record<string, unknown> = {\n $schema: \"https://opencode.ai/config.json\",\n };\n if (state.rules.length > 0) {\n baseConfig[\"instructions\"] = [`${outputDir}/rules/*.md`];\n }\n\n const mcp = transformMcpToOpenCode(state.settings?.mcpServers);\n if (mcp) {\n baseConfig[\"mcp\"] = mcp;\n }\n\n const permission = transformPermissionsToOpenCode(\n state.settings?.permissions\n );\n if (permission) {\n baseConfig[\"permission\"] = permission;\n }\n\n let finalConfig = baseConfig;\n if (state.settings?.overrides?.opencode) {\n finalConfig = deepMergeConfigs(\n baseConfig,\n state.settings.overrides.opencode\n );\n }\n\n files.push({\n path: \"opencode.json\",\n type: \"json\",\n content: finalConfig,\n });\n\n const overrideFiles = await scanOverrideDirectory(rootDir, \"opencode\");\n for (const overrideFile of overrideFiles) {\n const targetPath = path.join(\n rootDir,\n outputDir,\n overrideFile.relativePath\n );\n if (await fileExists(targetPath)) {\n continue;\n }\n files.push({\n path: `${outputDir}/${overrideFile.relativePath}`,\n type: \"symlink\",\n target: `../${UNIFIED_DIR}/${OVERRIDE_DIRS.opencode}/${overrideFile.relativePath}`,\n });\n }\n\n return files;\n },\n\n validate(state: UnifiedState): ValidationResult {\n const warnings: { path: string[]; message: string }[] = [];\n if (!state.agents) {\n warnings.push({\n path: [\"AGENTS.md\"],\n message: \"No AGENTS.md found - .opencode/AGENTS.md will not be created\",\n });\n }\n return { valid: true, errors: [], warnings, skipped: [] };\n },\n};\n","import type { ToolId } from \"../constants\";\nimport type { Plugin } from \"./types\";\n\nclass PluginRegistry {\n private plugins: Map<ToolId, Plugin> = new Map();\n\n register(plugin: Plugin): void {\n this.plugins.set(plugin.id, plugin);\n }\n\n get(id: ToolId): Plugin | undefined {\n return this.plugins.get(id);\n }\n\n getAll(): Plugin[] {\n return Array.from(this.plugins.values());\n }\n\n getIds(): ToolId[] {\n return Array.from(this.plugins.keys());\n }\n\n has(id: ToolId): boolean {\n return this.plugins.has(id);\n }\n}\n\nexport const pluginRegistry = new PluginRegistry();\n","import { claudeCodePlugin } from \"./claude-code/index\";\nimport { opencodePlugin } from \"./opencode/index\";\nimport { pluginRegistry } from \"./registry\";\n\nexport { claudeCodePlugin, opencodePlugin, pluginRegistry };\nexport type { Plugin } from \"./types\";\n\npluginRegistry.register(claudeCodePlugin);\npluginRegistry.register(opencodePlugin);\n","import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport { WriteError } from \"../errors\";\nimport type { ChangeResult, OutputFile } from \"../types/index\";\n\n/**\n * Options for the file writer\n */\nexport interface WriterOptions {\n /** Root directory for output */\n rootDir: string;\n /** Preview changes without writing */\n dryRun?: boolean;\n}\n\nexport function computeHash(content: string): string {\n return crypto.createHash(\"sha256\").update(content, \"utf-8\").digest(\"hex\");\n}\n\nasync function readExistingFile(filePath: string): Promise<string | null> {\n try {\n return await fs.readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function getSymlinkTarget(filePath: string): Promise<string | null> {\n try {\n const stats = await fs.lstat(filePath);\n if (stats.isSymbolicLink()) {\n return await fs.readlink(filePath);\n }\n return null;\n } catch {\n return null;\n }\n}\n\nasync function ensureDir(dirPath: string): Promise<void> {\n await fs.mkdir(dirPath, { recursive: true });\n}\n\nasync function removeIfExists(filePath: string): Promise<void> {\n try {\n const stats = await fs.lstat(filePath);\n if (stats.isDirectory() && !stats.isSymbolicLink()) {\n await fs.rm(filePath, { recursive: true, force: true });\n } else {\n await fs.unlink(filePath);\n }\n } catch (error) {\n if ((error as { code?: string }).code !== \"ENOENT\") {\n throw error;\n }\n }\n}\n\nasync function writeSingleFile(\n file: OutputFile,\n rootDir: string,\n dryRun: boolean\n): Promise<ChangeResult> {\n const fullPath = path.join(rootDir, file.path);\n const dirPath = path.dirname(fullPath);\n\n if (file.type === \"symlink\") {\n const target = file.target!;\n const existingTarget = await getSymlinkTarget(fullPath);\n\n if (existingTarget === target) {\n return {\n path: file.path,\n action: \"unchanged\",\n };\n }\n\n if (!dryRun) {\n await ensureDir(dirPath);\n await removeIfExists(fullPath);\n await fs.symlink(target, fullPath);\n }\n\n return {\n path: file.path,\n action: existingTarget ? \"update\" : \"create\",\n };\n }\n\n const content =\n file.type === \"json\"\n ? JSON.stringify(file.content, null, 2) + \"\\n\"\n : String(file.content);\n\n const newHash = computeHash(content);\n const existingContent = await readExistingFile(fullPath);\n const oldHash = existingContent ? computeHash(existingContent) : undefined;\n\n if (oldHash === newHash) {\n return {\n path: file.path,\n action: \"unchanged\",\n oldHash,\n newHash,\n };\n }\n\n if (!dryRun) {\n await ensureDir(dirPath);\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n return {\n path: file.path,\n action: existingContent ? \"update\" : \"create\",\n oldHash,\n newHash,\n };\n}\n\nexport async function writeFiles(\n files: OutputFile[],\n options: WriterOptions\n): Promise<ChangeResult[]> {\n const { rootDir, dryRun = false } = options;\n const results: ChangeResult[] = [];\n\n for (const file of files) {\n try {\n const result = await writeSingleFile(file, rootDir, dryRun);\n results.push(result);\n } catch (error) {\n throw new WriteError(\n `Failed to write file: ${file.path}`,\n file.path,\n error as Error\n );\n }\n }\n\n return results;\n}\n\n/**\n * Update .gitignore with paths that should not be version controlled.\n * Manages a dedicated \"lnai-generated\" section to avoid conflicts with user entries.\n */\nexport async function updateGitignore(\n rootDir: string,\n paths: string[]\n): Promise<void> {\n const gitignorePath = path.join(rootDir, \".gitignore\");\n let content = \"\";\n\n try {\n content = await fs.readFile(gitignorePath, \"utf-8\");\n } catch {\n // File doesn't exist, start fresh\n }\n\n const marker = \"# lnai-generated\";\n const endMarker = \"# end lnai-generated\";\n\n const markerRegex = new RegExp(`${marker}[\\\\s\\\\S]*?${endMarker}\\\\n?`, \"g\");\n content = content.replace(markerRegex, \"\");\n content = content.trimEnd();\n\n const newSection = [\"\", marker, ...paths.map((p) => p), endMarker, \"\"].join(\n \"\\n\"\n );\n\n content = content + newSection;\n\n await fs.writeFile(gitignorePath, content, \"utf-8\");\n}\n","import type { ToolId } from \"../constants\";\nimport { parseUnifiedConfig } from \"../parser/index\";\nimport { pluginRegistry } from \"../plugins/index\";\nimport type { SyncResult } from \"../types/index\";\nimport { validateUnifiedState } from \"../validator/index\";\nimport { updateGitignore, writeFiles } from \"../writer/index\";\n\n/**\n * Options for the sync pipeline\n */\nexport interface SyncOptions {\n /** Root directory containing .ai/ config */\n rootDir: string;\n /** Only sync specific tools (default: all enabled) */\n tools?: ToolId[];\n /** Preview changes without writing files */\n dryRun?: boolean;\n /** Enable verbose output */\n verbose?: boolean;\n}\n\nfunction getToolsToSync(\n config: {\n tools?: Partial<\n Record<ToolId, { enabled: boolean; versionControl?: boolean }>\n >;\n },\n requestedTools?: ToolId[]\n): ToolId[] {\n if (requestedTools && requestedTools.length > 0) {\n return requestedTools.filter((tool) => pluginRegistry.has(tool));\n }\n\n const enabledTools: ToolId[] = [];\n\n if (config.tools) {\n for (const [toolId, toolConfig] of Object.entries(config.tools)) {\n if (toolConfig?.enabled && pluginRegistry.has(toolId as ToolId)) {\n enabledTools.push(toolId as ToolId);\n }\n }\n }\n\n if (enabledTools.length === 0) {\n return pluginRegistry.getIds();\n }\n\n return enabledTools;\n}\n\n/**\n * Run the sync pipeline to export .ai/ config to native tool formats.\n */\nexport async function runSyncPipeline(\n options: SyncOptions\n): Promise<SyncResult[]> {\n const { rootDir, dryRun = false, tools: requestedTools } = options;\n\n const state = await parseUnifiedConfig(rootDir);\n\n const unifiedValidation = validateUnifiedState(state);\n if (!unifiedValidation.valid) {\n return [\n {\n tool: \"claudeCode\" as ToolId,\n changes: [],\n validation: unifiedValidation,\n },\n ];\n }\n\n const toolsToSync = getToolsToSync(state.config, requestedTools);\n\n if (toolsToSync.length === 0) {\n return [];\n }\n\n const results: SyncResult[] = [];\n const pathsToIgnore: string[] = [];\n\n for (const toolId of toolsToSync) {\n const plugin = pluginRegistry.get(toolId);\n if (!plugin) {\n continue;\n }\n\n const validation = plugin.validate(state);\n const outputFiles = await plugin.export(state, rootDir);\n const changes = await writeFiles(outputFiles, { rootDir, dryRun });\n\n results.push({\n tool: toolId,\n changes,\n validation,\n });\n\n const toolConfig = state.config.tools?.[toolId];\n if (!toolConfig?.versionControl) {\n pathsToIgnore.push(...outputFiles.map((f) => f.path));\n }\n }\n\n if (pathsToIgnore.length > 0 && !dryRun) {\n await updateGitignore(rootDir, pathsToIgnore);\n }\n\n return results;\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nimport {\n CONFIG_DIRS,\n CONFIG_FILES,\n TOOL_IDS,\n type ToolId,\n UNIFIED_DIR,\n} from \"../constants\";\nimport type { Config } from \"../types/index\";\n\nexport interface InitOptions {\n rootDir: string;\n tools?: ToolId[];\n minimal?: boolean;\n}\n\nexport interface InitResult {\n created: string[];\n}\n\nexport async function hasUnifiedConfig(rootDir: string): Promise<boolean> {\n const aiDir = path.join(rootDir, UNIFIED_DIR);\n try {\n const stats = await fs.stat(aiDir);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function generateDefaultConfig(tools?: ToolId[]): Config {\n const enabledTools = tools ?? TOOL_IDS;\n\n const toolsConfig: Config[\"tools\"] = {};\n\n for (const toolId of TOOL_IDS) {\n toolsConfig[toolId] = {\n enabled: enabledTools.includes(toolId),\n versionControl: false,\n };\n }\n\n return {\n tools: toolsConfig,\n };\n}\n\nexport async function initUnifiedConfig(\n options: InitOptions\n): Promise<InitResult> {\n const { rootDir, tools, minimal = false } = options;\n const aiDir = path.join(rootDir, UNIFIED_DIR);\n const created: string[] = [];\n\n await fs.mkdir(aiDir, { recursive: true });\n created.push(UNIFIED_DIR);\n\n const config = generateDefaultConfig(tools);\n const configPath = path.join(aiDir, CONFIG_FILES.config);\n await fs.writeFile(\n configPath,\n JSON.stringify(config, null, 2) + \"\\n\",\n \"utf-8\"\n );\n created.push(path.join(UNIFIED_DIR, CONFIG_FILES.config));\n\n if (!minimal) {\n for (const dir of [CONFIG_DIRS.rules, CONFIG_DIRS.skills]) {\n const dirPath = path.join(aiDir, dir);\n await fs.mkdir(dirPath, { recursive: true });\n created.push(path.join(UNIFIED_DIR, dir));\n\n const gitkeepPath = path.join(dirPath, \".gitkeep\");\n await fs.writeFile(gitkeepPath, \"\", \"utf-8\");\n created.push(path.join(UNIFIED_DIR, dir, \".gitkeep\"));\n }\n }\n\n return { created };\n}\n"]}