@lnai/core 0.2.0 → 0.3.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", "cursor"];
4
+ declare const TOOL_IDS: readonly ["claudeCode", "opencode", "cursor", "copilot"];
5
5
  type ToolId = (typeof TOOL_IDS)[number];
6
6
  declare const CONFIG_FILES: {
7
7
  readonly config: "config.json";
@@ -66,6 +66,7 @@ declare const toolIdSchema: z.ZodEnum<{
66
66
  claudeCode: "claudeCode";
67
67
  opencode: "opencode";
68
68
  cursor: "cursor";
69
+ copilot: "copilot";
69
70
  }>;
70
71
  /** Settings configuration (Claude format as source of truth) */
71
72
  declare const settingsSchema: z.ZodObject<{
@@ -85,11 +86,6 @@ declare const settingsSchema: z.ZodObject<{
85
86
  url: z.ZodOptional<z.ZodString>;
86
87
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
87
88
  }, z.core.$strip>>>;
88
- overrides: z.ZodOptional<z.ZodObject<{
89
- claudeCode: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
90
- opencode: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
91
- cursor: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
92
- }, z.core.$strip>>;
93
89
  }, z.core.$strip>;
94
90
  /** Main config.json structure. Uses partial object to allow partial tool configs. */
95
91
  declare const configSchema: z.ZodObject<{
@@ -106,6 +102,10 @@ declare const configSchema: z.ZodObject<{
106
102
  enabled: z.ZodBoolean;
107
103
  versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
108
104
  }, z.core.$strip>>;
105
+ copilot: z.ZodOptional<z.ZodObject<{
106
+ enabled: z.ZodBoolean;
107
+ versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
108
+ }, z.core.$strip>>;
109
109
  }, z.core.$strip>>;
110
110
  }, z.core.$strip>;
111
111
  /** Skill frontmatter (name and description required) */
@@ -145,7 +145,6 @@ interface UnifiedState {
145
145
  settings: {
146
146
  permissions?: Permissions;
147
147
  mcpServers?: Record<string, McpServer>;
148
- overrides?: Partial<Record<ToolId, Record<string, unknown>>>;
149
148
  } | null;
150
149
  agents: string | null;
151
150
  rules: MarkdownFile<RuleFrontmatter>[];
package/dist/index.js CHANGED
@@ -2,12 +2,11 @@ import { z } from 'zod';
2
2
  import * as fs3 from 'fs/promises';
3
3
  import * as path from 'path';
4
4
  import matter from 'gray-matter';
5
- import deepmerge from 'deepmerge';
6
5
  import * as crypto from 'crypto';
7
6
 
8
7
  // src/constants.ts
9
8
  var UNIFIED_DIR = ".ai";
10
- var TOOL_IDS = ["claudeCode", "opencode", "cursor"];
9
+ var TOOL_IDS = ["claudeCode", "opencode", "cursor", "copilot"];
11
10
  var CONFIG_FILES = {
12
11
  config: "config.json",
13
12
  settings: "settings.json",
@@ -21,12 +20,14 @@ var CONFIG_DIRS = {
21
20
  var TOOL_OUTPUT_DIRS = {
22
21
  claudeCode: ".claude",
23
22
  opencode: ".opencode",
24
- cursor: ".cursor"
23
+ cursor: ".cursor",
24
+ copilot: ".github"
25
25
  };
26
26
  var OVERRIDE_DIRS = {
27
27
  claudeCode: ".claude",
28
28
  opencode: ".opencode",
29
- cursor: ".cursor"
29
+ cursor: ".cursor",
30
+ copilot: ".copilot"
30
31
  };
31
32
 
32
33
  // src/errors.ts
@@ -106,21 +107,17 @@ var toolConfigSchema = z.object({
106
107
  enabled: z.boolean(),
107
108
  versionControl: z.boolean().optional().default(false)
108
109
  });
109
- var toolIdSchema = z.enum(["claudeCode", "opencode", "cursor"]);
110
+ var toolIdSchema = z.enum(["claudeCode", "opencode", "cursor", "copilot"]);
110
111
  var settingsSchema = z.object({
111
112
  permissions: permissionsSchema.optional(),
112
- mcpServers: z.record(z.string(), mcpServerSchema).optional(),
113
- overrides: z.object({
114
- claudeCode: 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()
117
- }).optional()
113
+ mcpServers: z.record(z.string(), mcpServerSchema).optional()
118
114
  });
119
115
  var configSchema = z.object({
120
116
  tools: z.object({
121
117
  claudeCode: toolConfigSchema,
122
118
  opencode: toolConfigSchema,
123
- cursor: toolConfigSchema
119
+ cursor: toolConfigSchema,
120
+ copilot: toolConfigSchema
124
121
  }).partial().optional()
125
122
  });
126
123
  var skillFrontmatterSchema = z.object({
@@ -403,38 +400,24 @@ async function scanDir(baseDir, currentDir, files) {
403
400
  }
404
401
  }
405
402
  }
406
- function deepMergeConfigs(base, override) {
407
- return deepmerge(base, override, {
408
- arrayMerge: (target, source) => [.../* @__PURE__ */ new Set([...target, ...source])]
409
- });
410
- }
411
- async function fileExists2(filePath) {
412
- try {
413
- await fs3.access(filePath);
414
- return true;
415
- } catch {
416
- return false;
417
- }
418
- }
419
- async function getOverrideOutputFiles(rootDir, toolId) {
403
+ async function applyFileOverrides(files, rootDir, toolId) {
420
404
  const outputDir = TOOL_OUTPUT_DIRS[toolId];
421
405
  const overrideFiles = await scanOverrideDirectory(rootDir, toolId);
422
- const result = [];
406
+ const overridePaths = /* @__PURE__ */ new Set();
407
+ const overrideOutputFiles = [];
423
408
  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);
409
+ const outputPath = `${outputDir}/${overrideFile.relativePath}`;
410
+ overridePaths.add(outputPath);
411
+ const symlinkDir = path.dirname(outputPath);
430
412
  const sourcePath = `${UNIFIED_DIR}/${OVERRIDE_DIRS[toolId]}/${overrideFile.relativePath}`;
431
- result.push({
432
- path: symlinkPath,
413
+ overrideOutputFiles.push({
414
+ path: outputPath,
433
415
  type: "symlink",
434
416
  target: path.relative(symlinkDir, sourcePath)
435
417
  });
436
418
  }
437
- return result;
419
+ const filteredFiles = files.filter((file) => !overridePaths.has(file.path));
420
+ return [...filteredFiles, ...overrideOutputFiles];
438
421
  }
439
422
 
440
423
  // src/plugins/claude-code/index.ts
@@ -471,30 +454,21 @@ var claudeCodePlugin = {
471
454
  target: `../../${UNIFIED_DIR}/skills/${skill.path}`
472
455
  });
473
456
  }
474
- const baseSettings = {};
457
+ const settings = {};
475
458
  if (state.settings?.permissions) {
476
- baseSettings["permissions"] = state.settings.permissions;
459
+ settings["permissions"] = state.settings.permissions;
477
460
  }
478
461
  if (state.settings?.mcpServers) {
479
- baseSettings["mcpServers"] = state.settings.mcpServers;
480
- }
481
- let finalSettings = baseSettings;
482
- if (state.settings?.overrides?.claudeCode) {
483
- finalSettings = deepMergeConfigs(
484
- baseSettings,
485
- state.settings.overrides.claudeCode
486
- );
462
+ settings["mcpServers"] = state.settings.mcpServers;
487
463
  }
488
- if (Object.keys(finalSettings).length > 0) {
464
+ if (Object.keys(settings).length > 0) {
489
465
  files.push({
490
466
  path: `${outputDir}/settings.json`,
491
467
  type: "json",
492
- content: finalSettings
468
+ content: settings
493
469
  });
494
470
  }
495
- const overrideFiles = await getOverrideOutputFiles(rootDir, "claudeCode");
496
- files.push(...overrideFiles);
497
- return files;
471
+ return applyFileOverrides(files, rootDir, "claudeCode");
498
472
  },
499
473
  validate(state) {
500
474
  const warnings = [];
@@ -508,6 +482,197 @@ var claudeCodePlugin = {
508
482
  }
509
483
  };
510
484
 
485
+ // src/utils/transforms.ts
486
+ var ENV_VAR_PATTERN = /\$\{([^}:]+)(:-[^}]*)?\}/g;
487
+ function transformEnvVar(value, format) {
488
+ if (format === "opencode") {
489
+ return value.replace(ENV_VAR_PATTERN, "{env:$1}");
490
+ }
491
+ return value.replace(ENV_VAR_PATTERN, "${env:$1}");
492
+ }
493
+ function transformEnvVars(env, format) {
494
+ const result = {};
495
+ for (const [key, value] of Object.entries(env)) {
496
+ result[key] = transformEnvVar(value, format);
497
+ }
498
+ return result;
499
+ }
500
+ function parsePermissionRule(rule) {
501
+ const match = rule.match(/^(\w+)\(([^)]+)\)$/);
502
+ if (!match) {
503
+ return null;
504
+ }
505
+ const tool = match[1];
506
+ const pattern = match[2];
507
+ if (!tool || !pattern) {
508
+ return null;
509
+ }
510
+ return { tool, pattern };
511
+ }
512
+ function deriveDescription(filename, content) {
513
+ const headingMatch = content.match(/^#\s+(.+)$/m);
514
+ if (headingMatch && headingMatch[1]) {
515
+ return headingMatch[1];
516
+ }
517
+ const baseName = filename.replace(/\.md$/, "");
518
+ return baseName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
519
+ }
520
+
521
+ // src/plugins/copilot/transforms.ts
522
+ function transformRuleToCopilot(rule) {
523
+ const description = deriveDescription(rule.path, rule.content);
524
+ const paths = rule.frontmatter.paths || [];
525
+ const frontmatter = {
526
+ description
527
+ };
528
+ if (paths.length > 0) {
529
+ frontmatter.applyTo = paths.join(",");
530
+ }
531
+ return {
532
+ frontmatter,
533
+ content: rule.content
534
+ };
535
+ }
536
+ function serializeCopilotInstruction(frontmatter, content) {
537
+ const lines = ["---"];
538
+ if (frontmatter.applyTo) {
539
+ lines.push(`applyTo: ${JSON.stringify(frontmatter.applyTo)}`);
540
+ }
541
+ lines.push(`description: ${JSON.stringify(frontmatter.description)}`);
542
+ lines.push("---");
543
+ lines.push("");
544
+ lines.push(content);
545
+ return lines.join("\n");
546
+ }
547
+ function transformMcpToCopilot(servers) {
548
+ if (!servers || Object.keys(servers).length === 0) {
549
+ return void 0;
550
+ }
551
+ const result = {};
552
+ for (const [name, server] of Object.entries(servers)) {
553
+ if (server.type === "http" || server.type === "sse") {
554
+ if (!server.url) {
555
+ continue;
556
+ }
557
+ const copilotServer = {
558
+ url: server.url
559
+ };
560
+ if (server.headers && Object.keys(server.headers).length > 0) {
561
+ copilotServer.requestInit = {
562
+ headers: transformEnvVars(server.headers, "copilot")
563
+ };
564
+ }
565
+ result[name] = copilotServer;
566
+ } else if (server.command) {
567
+ const copilotServer = {
568
+ type: "stdio",
569
+ command: server.command
570
+ };
571
+ if (server.args && server.args.length > 0) {
572
+ copilotServer.args = server.args;
573
+ }
574
+ if (server.env && Object.keys(server.env).length > 0) {
575
+ copilotServer.env = transformEnvVars(server.env, "copilot");
576
+ }
577
+ result[name] = copilotServer;
578
+ }
579
+ }
580
+ if (Object.keys(result).length === 0) {
581
+ return void 0;
582
+ }
583
+ return {
584
+ inputs: [],
585
+ servers: result
586
+ };
587
+ }
588
+
589
+ // src/plugins/copilot/index.ts
590
+ var copilotPlugin = {
591
+ id: "copilot",
592
+ name: "GitHub Copilot",
593
+ async detect(_rootDir) {
594
+ return false;
595
+ },
596
+ async import(_rootDir) {
597
+ return null;
598
+ },
599
+ async export(state, rootDir) {
600
+ const files = [];
601
+ if (state.agents) {
602
+ files.push({
603
+ path: ".github/copilot-instructions.md",
604
+ type: "symlink",
605
+ target: `../${UNIFIED_DIR}/AGENTS.md`
606
+ });
607
+ }
608
+ for (const rule of state.rules) {
609
+ const transformed = transformRuleToCopilot(rule);
610
+ const ruleContent = serializeCopilotInstruction(
611
+ transformed.frontmatter,
612
+ transformed.content
613
+ );
614
+ const outputFilename = rule.path.replace(/\.md$/, ".instructions.md");
615
+ files.push({
616
+ path: `.github/instructions/${outputFilename}`,
617
+ type: "text",
618
+ content: ruleContent
619
+ });
620
+ }
621
+ for (const skill of state.skills) {
622
+ files.push({
623
+ path: `.github/skills/${skill.path}`,
624
+ type: "symlink",
625
+ target: `../../${UNIFIED_DIR}/skills/${skill.path}`
626
+ });
627
+ }
628
+ const mcpConfig = transformMcpToCopilot(state.settings?.mcpServers);
629
+ if (mcpConfig) {
630
+ files.push({
631
+ path: ".vscode/mcp.json",
632
+ type: "json",
633
+ content: { inputs: mcpConfig.inputs, servers: mcpConfig.servers }
634
+ });
635
+ }
636
+ return applyFileOverrides(files, rootDir, "copilot");
637
+ },
638
+ validate(state) {
639
+ const warnings = [];
640
+ if (!state.agents) {
641
+ warnings.push({
642
+ path: ["AGENTS.md"],
643
+ message: "No AGENTS.md found - .github/copilot-instructions.md will not be created"
644
+ });
645
+ }
646
+ const permissions = state.settings?.permissions;
647
+ const hasPermissions = permissions && (permissions.allow && permissions.allow.length > 0 || permissions.ask && permissions.ask.length > 0 || permissions.deny && permissions.deny.length > 0);
648
+ if (hasPermissions) {
649
+ warnings.push({
650
+ path: ["settings", "permissions"],
651
+ message: "GitHub Copilot does not support permissions - they will be ignored"
652
+ });
653
+ }
654
+ const mcpServers = state.settings?.mcpServers;
655
+ if (mcpServers) {
656
+ for (const [name, server] of Object.entries(mcpServers)) {
657
+ const isRemote = server.type === "http" || server.type === "sse";
658
+ const hasCommand = !!server.command;
659
+ if (isRemote && !server.url) {
660
+ warnings.push({
661
+ path: ["settings", "mcpServers", name],
662
+ message: `MCP server "${name}" is type "${server.type}" but has no url - it will be skipped`
663
+ });
664
+ } else if (!isRemote && !hasCommand) {
665
+ warnings.push({
666
+ path: ["settings", "mcpServers", name],
667
+ message: `MCP server "${name}" has no command or type - it will be skipped`
668
+ });
669
+ }
670
+ }
671
+ }
672
+ return { valid: true, errors: [], warnings, skipped: [] };
673
+ }
674
+ };
675
+
511
676
  // src/plugins/cursor/transforms.ts
512
677
  function transformRuleToCursor(rule) {
513
678
  const description = deriveDescription(rule.path, rule.content);
@@ -522,14 +687,6 @@ function transformRuleToCursor(rule) {
522
687
  content: rule.content
523
688
  };
524
689
  }
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
690
  function serializeCursorRule(frontmatter, content) {
534
691
  const lines = [
535
692
  "---",
@@ -556,7 +713,7 @@ function transformMcpToCursor(servers) {
556
713
  url: server.url
557
714
  };
558
715
  if (server.headers) {
559
- cursorServer.headers = transformEnvVarsToCursor(server.headers);
716
+ cursorServer.headers = transformEnvVars(server.headers, "cursor");
560
717
  }
561
718
  result[name] = cursorServer;
562
719
  } else if (server.command) {
@@ -567,23 +724,13 @@ function transformMcpToCursor(servers) {
567
724
  cursorServer.args = server.args;
568
725
  }
569
726
  if (server.env) {
570
- cursorServer.env = transformEnvVarsToCursor(server.env);
727
+ cursorServer.env = transformEnvVars(server.env, "cursor");
571
728
  }
572
729
  result[name] = cursorServer;
573
730
  }
574
731
  }
575
732
  return Object.keys(result).length > 0 ? result : void 0;
576
733
  }
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
734
  function transformPermissionsToCursor(permissions) {
588
735
  if (!permissions) {
589
736
  return { permissions: void 0, hasAskPermissions: false };
@@ -682,47 +829,22 @@ var cursorPlugin = {
682
829
  });
683
830
  }
684
831
  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
- }
832
+ if (mcpServers) {
694
833
  files.push({
695
834
  path: `${outputDir}/mcp.json`,
696
835
  type: "json",
697
- content: mcpContent
836
+ content: { mcpServers }
698
837
  });
699
838
  }
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
- }
839
+ const cliContent = buildCliContent(state.settings?.permissions);
840
+ if (cliContent) {
717
841
  files.push({
718
842
  path: `${outputDir}/cli.json`,
719
843
  type: "json",
720
844
  content: cliContent
721
845
  });
722
846
  }
723
- const overrideFiles = await getOverrideOutputFiles(rootDir, "cursor");
724
- files.push(...overrideFiles);
725
- return files;
847
+ return applyFileOverrides(files, rootDir, "cursor");
726
848
  },
727
849
  validate(state) {
728
850
  const warnings = [];
@@ -757,6 +879,13 @@ var cursorPlugin = {
757
879
  return { valid: true, errors: [], warnings, skipped: [] };
758
880
  }
759
881
  };
882
+ function buildCliContent(permissions) {
883
+ const permissionsResult = transformPermissionsToCursor(permissions);
884
+ if (!permissionsResult.permissions) {
885
+ return void 0;
886
+ }
887
+ return { permissions: permissionsResult.permissions };
888
+ }
760
889
 
761
890
  // src/plugins/opencode/transforms.ts
762
891
  function transformMcpToOpenCode(servers) {
@@ -781,7 +910,7 @@ function transformMcpToOpenCode(servers) {
781
910
  command
782
911
  };
783
912
  if (server.env) {
784
- openCodeServer.environment = transformEnvVars(server.env);
913
+ openCodeServer.environment = transformEnvVars(server.env, "opencode");
785
914
  }
786
915
  result[name] = openCodeServer;
787
916
  }
@@ -798,7 +927,7 @@ function transformPermissionsToOpenCode(permissions) {
798
927
  return;
799
928
  }
800
929
  for (const rule of rules) {
801
- const parsed = parsePermissionRule(rule);
930
+ const parsed = parsePermissionRuleForOpenCode(rule);
802
931
  if (!parsed) {
803
932
  continue;
804
933
  }
@@ -814,28 +943,13 @@ function transformPermissionsToOpenCode(permissions) {
814
943
  processRules(permissions.deny, "deny");
815
944
  return Object.keys(result).length > 0 ? result : void 0;
816
945
  }
817
- function transformEnvVar(value) {
818
- return value.replace(/\$\{([^}:]+)(:-[^}]*)?\}/g, "{env:$1}");
819
- }
820
- function transformEnvVars(env) {
821
- const result = {};
822
- for (const [key, value] of Object.entries(env)) {
823
- result[key] = transformEnvVar(value);
824
- }
825
- return result;
826
- }
827
- function parsePermissionRule(rule) {
828
- const match = rule.match(/^(\w+)\(([^)]+)\)$/);
829
- if (!match) {
946
+ function parsePermissionRuleForOpenCode(rule) {
947
+ const parsed = parsePermissionRule(rule);
948
+ if (!parsed) {
830
949
  return null;
831
950
  }
832
- const tool = match[1];
833
- const pattern = match[2];
834
- if (!tool || !pattern) {
835
- return null;
836
- }
837
- const normalizedTool = tool.toLowerCase();
838
- let normalizedPattern = pattern;
951
+ const normalizedTool = parsed.tool.toLowerCase();
952
+ let normalizedPattern = parsed.pattern;
839
953
  if (normalizedPattern.includes(":*")) {
840
954
  normalizedPattern = normalizedPattern.replace(/:(\*)/g, " $1");
841
955
  }
@@ -879,37 +993,28 @@ var opencodePlugin = {
879
993
  target: `../../${UNIFIED_DIR}/skills/${skill.path}`
880
994
  });
881
995
  }
882
- const baseConfig = {
996
+ const config = {
883
997
  $schema: "https://opencode.ai/config.json"
884
998
  };
885
999
  if (state.rules.length > 0) {
886
- baseConfig["instructions"] = [`${outputDir}/rules/*.md`];
1000
+ config["instructions"] = [`${outputDir}/rules/*.md`];
887
1001
  }
888
1002
  const mcp = transformMcpToOpenCode(state.settings?.mcpServers);
889
1003
  if (mcp) {
890
- baseConfig["mcp"] = mcp;
1004
+ config["mcp"] = mcp;
891
1005
  }
892
1006
  const permission = transformPermissionsToOpenCode(
893
1007
  state.settings?.permissions
894
1008
  );
895
1009
  if (permission) {
896
- baseConfig["permission"] = permission;
897
- }
898
- let finalConfig = baseConfig;
899
- if (state.settings?.overrides?.opencode) {
900
- finalConfig = deepMergeConfigs(
901
- baseConfig,
902
- state.settings.overrides.opencode
903
- );
1010
+ config["permission"] = permission;
904
1011
  }
905
1012
  files.push({
906
1013
  path: "opencode.json",
907
1014
  type: "json",
908
- content: finalConfig
1015
+ content: config
909
1016
  });
910
- const overrideFiles = await getOverrideOutputFiles(rootDir, "opencode");
911
- files.push(...overrideFiles);
912
- return files;
1017
+ return applyFileOverrides(files, rootDir, "opencode");
913
1018
  },
914
1019
  validate(state) {
915
1020
  const warnings = [];
@@ -946,6 +1051,7 @@ var pluginRegistry = new PluginRegistry();
946
1051
 
947
1052
  // src/plugins/index.ts
948
1053
  pluginRegistry.register(claudeCodePlugin);
1054
+ pluginRegistry.register(copilotPlugin);
949
1055
  pluginRegistry.register(cursorPlugin);
950
1056
  pluginRegistry.register(opencodePlugin);
951
1057
  function computeHash(content) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lnai/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Core library for LNAI - unified AI config management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,6 +21,8 @@
21
21
  "claude",
22
22
  "cursor",
23
23
  "opencode",
24
+ "copilot",
25
+ "github-copilot",
24
26
  "cli",
25
27
  "ai-tools"
26
28
  ],
@@ -34,7 +36,6 @@
34
36
  "dist"
35
37
  ],
36
38
  "dependencies": {
37
- "deepmerge": "^4.3.1",
38
39
  "gray-matter": "^4.0.3",
39
40
  "zod": "^4.3.6"
40
41
  },