@lnai/core 0.4.1 → 0.6.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", "copilot", "windsurf"];
4
+ declare const TOOL_IDS: readonly ["claudeCode", "opencode", "cursor", "copilot", "windsurf", "gemini", "codex"];
5
5
  type ToolId = (typeof TOOL_IDS)[number];
6
6
  declare const CONFIG_FILES: {
7
7
  readonly config: "config.json";
@@ -68,6 +68,8 @@ declare const toolIdSchema: z.ZodEnum<{
68
68
  cursor: "cursor";
69
69
  copilot: "copilot";
70
70
  windsurf: "windsurf";
71
+ gemini: "gemini";
72
+ codex: "codex";
71
73
  }>;
72
74
  /** Settings configuration (Claude format as source of truth) */
73
75
  declare const settingsSchema: z.ZodObject<{
@@ -111,6 +113,14 @@ declare const configSchema: z.ZodObject<{
111
113
  enabled: z.ZodBoolean;
112
114
  versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
113
115
  }, z.core.$strip>>;
116
+ gemini: z.ZodOptional<z.ZodObject<{
117
+ enabled: z.ZodBoolean;
118
+ versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
119
+ }, z.core.$strip>>;
120
+ codex: z.ZodOptional<z.ZodObject<{
121
+ enabled: z.ZodBoolean;
122
+ versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
123
+ }, z.core.$strip>>;
114
124
  }, z.core.$strip>>;
115
125
  }, z.core.$strip>;
116
126
  /** Skill frontmatter (name and description required) */
@@ -244,6 +254,18 @@ interface Plugin {
244
254
  */
245
255
  declare const claudeCodePlugin: Plugin;
246
256
 
257
+ /**
258
+ * Codex plugin for exporting to .codex/ format
259
+ *
260
+ * Output structure:
261
+ * - AGENTS.md (symlink -> .ai/AGENTS.md) [at project root]
262
+ * - <dir>/AGENTS.md (generated from .ai/rules/*.md, per glob directory)
263
+ * - .codex/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
264
+ * - .codex/config.toml (generated from settings.mcpServers)
265
+ * - .codex/<path> (symlink -> ../.ai/.codex/<path>) for override files
266
+ */
267
+ declare const codexPlugin: Plugin;
268
+
247
269
  /**
248
270
  * OpenCode plugin for exporting to opencode.json format
249
271
  *
@@ -315,4 +337,4 @@ declare function initUnifiedConfig(options: InitOptions): Promise<InitResult>;
315
337
  declare function hasUnifiedConfig(rootDir: string): Promise<boolean>;
316
338
  declare function generateDefaultConfig(tools?: ToolId[], versionControl?: Record<ToolId, boolean>): Config;
317
339
 
318
- 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 };
340
+ 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, codexPlugin, 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
@@ -11,7 +11,9 @@ var TOOL_IDS = [
11
11
  "opencode",
12
12
  "cursor",
13
13
  "copilot",
14
- "windsurf"
14
+ "windsurf",
15
+ "gemini",
16
+ "codex"
15
17
  ];
16
18
  var CONFIG_FILES = {
17
19
  config: "config.json",
@@ -28,14 +30,18 @@ var TOOL_OUTPUT_DIRS = {
28
30
  opencode: ".opencode",
29
31
  cursor: ".cursor",
30
32
  copilot: ".github",
31
- windsurf: ".windsurf"
33
+ windsurf: ".windsurf",
34
+ gemini: ".gemini",
35
+ codex: ".codex"
32
36
  };
33
37
  var OVERRIDE_DIRS = {
34
38
  claudeCode: ".claude",
35
39
  opencode: ".opencode",
36
40
  cursor: ".cursor",
37
41
  copilot: ".copilot",
38
- windsurf: ".windsurf"
42
+ windsurf: ".windsurf",
43
+ gemini: ".gemini",
44
+ codex: ".codex"
39
45
  };
40
46
 
41
47
  // src/errors.ts
@@ -61,10 +67,10 @@ var ParseError = class extends LnaiError {
61
67
  var ValidationError = class extends LnaiError {
62
68
  path;
63
69
  value;
64
- constructor(message, path5, value) {
70
+ constructor(message, path6, value) {
65
71
  super(message, "VALIDATION_ERROR");
66
72
  this.name = "ValidationError";
67
- this.path = path5;
73
+ this.path = path6;
68
74
  this.value = value;
69
75
  }
70
76
  };
@@ -120,7 +126,9 @@ var toolIdSchema = z.enum([
120
126
  "opencode",
121
127
  "cursor",
122
128
  "copilot",
123
- "windsurf"
129
+ "windsurf",
130
+ "gemini",
131
+ "codex"
124
132
  ]);
125
133
  var settingsSchema = z.object({
126
134
  permissions: permissionsSchema.optional(),
@@ -132,7 +140,9 @@ var configSchema = z.object({
132
140
  opencode: toolConfigSchema,
133
141
  cursor: toolConfigSchema,
134
142
  copilot: toolConfigSchema,
135
- windsurf: toolConfigSchema
143
+ windsurf: toolConfigSchema,
144
+ gemini: toolConfigSchema,
145
+ codex: toolConfigSchema
136
146
  }).partial().optional()
137
147
  });
138
148
  var skillFrontmatterSchema = z.object({
@@ -496,6 +506,183 @@ var claudeCodePlugin = {
496
506
  return { valid: true, errors: [], warnings, skipped: [] };
497
507
  }
498
508
  };
509
+ function getDirFromGlob(glob) {
510
+ const cleanPath = glob.replace(/(\*\*|\*|\{.*,.*\}).*$/, "");
511
+ const dir = cleanPath.replace(/\/$/, "");
512
+ if (dir === glob) {
513
+ const dirname4 = path.dirname(dir);
514
+ return dirname4 === "." && !dir.includes("/") ? "." : dirname4;
515
+ }
516
+ if (!dir) {
517
+ return ".";
518
+ }
519
+ return dir;
520
+ }
521
+ function groupRulesByDirectory(rules) {
522
+ const rulesMap = /* @__PURE__ */ new Map();
523
+ const addedRules = /* @__PURE__ */ new Map();
524
+ for (const rule of rules) {
525
+ for (const pathGlob of rule.frontmatter.paths) {
526
+ const dir = getDirFromGlob(pathGlob);
527
+ if (!rulesMap.has(dir)) {
528
+ rulesMap.set(dir, []);
529
+ addedRules.set(dir, /* @__PURE__ */ new Set());
530
+ }
531
+ if (addedRules.get(dir)?.has(rule.path)) {
532
+ continue;
533
+ }
534
+ addedRules.get(dir)?.add(rule.path);
535
+ const content = `## ${rule.path}
536
+
537
+ ${rule.content}
538
+ `;
539
+ rulesMap.get(dir)?.push(content);
540
+ }
541
+ }
542
+ return rulesMap;
543
+ }
544
+
545
+ // src/plugins/codex/index.ts
546
+ var codexPlugin = {
547
+ id: "codex",
548
+ name: "Codex",
549
+ async detect(_rootDir) {
550
+ return false;
551
+ },
552
+ async import(_rootDir) {
553
+ return null;
554
+ },
555
+ async export(state, rootDir) {
556
+ const files = [];
557
+ const outputDir = TOOL_OUTPUT_DIRS.codex;
558
+ if (state.agents) {
559
+ files.push({
560
+ path: "AGENTS.md",
561
+ type: "symlink",
562
+ target: `${UNIFIED_DIR}/AGENTS.md`
563
+ });
564
+ }
565
+ const rulesMap = groupRulesByDirectory(state.rules);
566
+ for (const [dir, contents] of rulesMap.entries()) {
567
+ if (dir === ".") {
568
+ continue;
569
+ }
570
+ const combinedContent = contents.join("\n---\n\n");
571
+ files.push({
572
+ path: `${dir}/AGENTS.md`,
573
+ type: "text",
574
+ content: combinedContent
575
+ });
576
+ }
577
+ for (const skill of state.skills) {
578
+ files.push({
579
+ path: `${outputDir}/skills/${skill.path}`,
580
+ type: "symlink",
581
+ target: `../../${UNIFIED_DIR}/skills/${skill.path}`
582
+ });
583
+ }
584
+ const configToml = buildCodexConfigToml(state.settings?.mcpServers);
585
+ if (configToml) {
586
+ files.push({
587
+ path: `${outputDir}/config.toml`,
588
+ type: "text",
589
+ content: configToml
590
+ });
591
+ }
592
+ return applyFileOverrides(files, rootDir, "codex");
593
+ },
594
+ validate(state) {
595
+ const warnings = [];
596
+ const skipped = [];
597
+ if (!state.agents) {
598
+ warnings.push({
599
+ path: ["AGENTS.md"],
600
+ message: "No AGENTS.md found - root AGENTS.md will not be created"
601
+ });
602
+ }
603
+ const rulesMap = groupRulesByDirectory(state.rules);
604
+ if (rulesMap.has(".")) {
605
+ warnings.push({
606
+ path: ["rules"],
607
+ message: "Rules with root globs are not exported - Codex only receives subdirectory AGENTS.md files"
608
+ });
609
+ }
610
+ const mcpServers = state.settings?.mcpServers;
611
+ if (mcpServers) {
612
+ for (const [name, server] of Object.entries(mcpServers)) {
613
+ if (!server.command && !server.url) {
614
+ warnings.push({
615
+ path: ["settings", "mcpServers", name],
616
+ message: `MCP server "${name}" has no command or url - it will be skipped`
617
+ });
618
+ }
619
+ }
620
+ }
621
+ if (state.settings?.permissions) {
622
+ const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
623
+ if (hasPermissions) {
624
+ skipped.push({
625
+ feature: "permissions",
626
+ reason: "Codex rules are not generated from LNAI permissions"
627
+ });
628
+ }
629
+ }
630
+ return { valid: true, errors: [], warnings, skipped };
631
+ }
632
+ };
633
+ function buildCodexConfigToml(mcpServers) {
634
+ if (!mcpServers || Object.keys(mcpServers).length === 0) {
635
+ return void 0;
636
+ }
637
+ const lines = [];
638
+ for (const [name, server] of Object.entries(mcpServers)) {
639
+ const hasCommand = !!server.command;
640
+ const hasUrl = !!server.url;
641
+ if (!hasCommand && !hasUrl) {
642
+ continue;
643
+ }
644
+ lines.push(`[mcp_servers.${formatTomlKey(name)}]`);
645
+ if (server.command) {
646
+ lines.push(`command = ${formatTomlString(server.command)}`);
647
+ if (server.args && server.args.length > 0) {
648
+ lines.push(`args = ${formatTomlArray(server.args)}`);
649
+ }
650
+ if (server.env && Object.keys(server.env).length > 0) {
651
+ lines.push(`env = ${formatTomlInlineTable(server.env)}`);
652
+ }
653
+ }
654
+ if (server.url) {
655
+ lines.push(`url = ${formatTomlString(server.url)}`);
656
+ if (server.headers && Object.keys(server.headers).length > 0) {
657
+ lines.push(`http_headers = ${formatTomlInlineTable(server.headers)}`);
658
+ }
659
+ }
660
+ lines.push("");
661
+ }
662
+ if (lines.length === 0) {
663
+ return void 0;
664
+ }
665
+ return `${lines.join("\n").trimEnd()}
666
+ `;
667
+ }
668
+ function formatTomlString(value) {
669
+ return JSON.stringify(value);
670
+ }
671
+ function formatTomlArray(values) {
672
+ return `[${values.map(formatTomlString).join(", ")}]`;
673
+ }
674
+ function formatTomlKey(key) {
675
+ if (/^[A-Za-z0-9_-]+$/.test(key)) {
676
+ return key;
677
+ }
678
+ return JSON.stringify(key);
679
+ }
680
+ function formatTomlInlineTable(values) {
681
+ const entries = Object.entries(values).map(
682
+ ([key, value]) => `${formatTomlKey(key)} = ${formatTomlString(value)}`
683
+ );
684
+ return `{ ${entries.join(", ")} }`;
685
+ }
499
686
 
500
687
  // src/utils/transforms.ts
501
688
  var ENV_VAR_PATTERN = /\$\{([^}:]+)(:-[^}]*)?\}/g;
@@ -652,6 +839,7 @@ var copilotPlugin = {
652
839
  },
653
840
  validate(state) {
654
841
  const warnings = [];
842
+ const skipped = [];
655
843
  if (!state.agents) {
656
844
  warnings.push({
657
845
  path: ["AGENTS.md"],
@@ -661,9 +849,9 @@ var copilotPlugin = {
661
849
  const permissions = state.settings?.permissions;
662
850
  const hasPermissions = permissions && (permissions.allow && permissions.allow.length > 0 || permissions.ask && permissions.ask.length > 0 || permissions.deny && permissions.deny.length > 0);
663
851
  if (hasPermissions) {
664
- warnings.push({
665
- path: ["settings", "permissions"],
666
- message: "GitHub Copilot does not support permissions - they will be ignored"
852
+ skipped.push({
853
+ feature: "permissions",
854
+ reason: "GitHub Copilot does not support declarative permissions"
667
855
  });
668
856
  }
669
857
  const mcpServers = state.settings?.mcpServers;
@@ -684,7 +872,7 @@ var copilotPlugin = {
684
872
  }
685
873
  }
686
874
  }
687
- return { valid: true, errors: [], warnings, skipped: [] };
875
+ return { valid: true, errors: [], warnings, skipped };
688
876
  }
689
877
  };
690
878
 
@@ -902,6 +1090,103 @@ function buildCliContent(permissions) {
902
1090
  return { permissions: permissionsResult.permissions };
903
1091
  }
904
1092
 
1093
+ // src/plugins/gemini/transforms.ts
1094
+ function transformMcpToGemini(mcpServers) {
1095
+ if (!mcpServers) {
1096
+ return void 0;
1097
+ }
1098
+ const geminiMcp = {};
1099
+ for (const [name, config] of Object.entries(mcpServers)) {
1100
+ if (!config.command && !config.url && !config.type) {
1101
+ continue;
1102
+ }
1103
+ geminiMcp[name] = {
1104
+ command: config.command,
1105
+ args: config.args,
1106
+ env: config.env
1107
+ };
1108
+ if (config.url) {
1109
+ geminiMcp[name].httpUrl = config.url;
1110
+ }
1111
+ }
1112
+ return Object.keys(geminiMcp).length > 0 ? geminiMcp : void 0;
1113
+ }
1114
+
1115
+ // src/plugins/gemini/index.ts
1116
+ var geminiPlugin = {
1117
+ id: "gemini",
1118
+ name: "Gemini CLI",
1119
+ async detect(_rootDir) {
1120
+ return false;
1121
+ },
1122
+ async import(_rootDir) {
1123
+ return null;
1124
+ },
1125
+ async export(state, rootDir) {
1126
+ const files = [];
1127
+ const outputDir = TOOL_OUTPUT_DIRS.gemini;
1128
+ if (state.agents) {
1129
+ files.push({
1130
+ path: `${outputDir}/GEMINI.md`,
1131
+ type: "symlink",
1132
+ target: `../${UNIFIED_DIR}/AGENTS.md`
1133
+ });
1134
+ }
1135
+ const rulesMap = groupRulesByDirectory(state.rules);
1136
+ for (const [dir, contents] of rulesMap.entries()) {
1137
+ const combinedContent = contents.join("\n---\n\n");
1138
+ const filePath = dir === "." ? "GEMINI.md" : `${dir}/GEMINI.md`;
1139
+ files.push({
1140
+ path: filePath,
1141
+ type: "text",
1142
+ content: combinedContent
1143
+ });
1144
+ }
1145
+ for (const skill of state.skills) {
1146
+ files.push({
1147
+ path: `${outputDir}/skills/${skill.path}`,
1148
+ type: "symlink",
1149
+ target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1150
+ });
1151
+ }
1152
+ const mcpServers = transformMcpToGemini(state.settings?.mcpServers);
1153
+ if (mcpServers) {
1154
+ files.push({
1155
+ path: `${outputDir}/settings.json`,
1156
+ type: "json",
1157
+ content: { mcpServers }
1158
+ });
1159
+ }
1160
+ return applyFileOverrides(files, rootDir, "gemini");
1161
+ },
1162
+ validate(state) {
1163
+ const warnings = [];
1164
+ const skipped = [];
1165
+ if (!state.agents) {
1166
+ warnings.push({
1167
+ path: ["AGENTS.md"],
1168
+ message: "No AGENTS.md found - GEMINI.md will not be created"
1169
+ });
1170
+ }
1171
+ if (state.settings?.permissions) {
1172
+ const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
1173
+ if (hasPermissions) {
1174
+ skipped.push({
1175
+ feature: "permissions",
1176
+ reason: "Gemini CLI does not support declarative permissions - permissions must be granted interactively"
1177
+ });
1178
+ }
1179
+ }
1180
+ if (state.rules.length > 0) {
1181
+ warnings.push({
1182
+ path: ["rules"],
1183
+ message: "Rules will be generated into GEMINI.md files in their respective subdirectories (e.g. apps/cli/GEMINI.md)."
1184
+ });
1185
+ }
1186
+ return { valid: true, errors: [], warnings, skipped };
1187
+ }
1188
+ };
1189
+
905
1190
  // src/plugins/opencode/transforms.ts
906
1191
  function transformMcpToOpenCode(servers) {
907
1192
  if (!servers || Object.keys(servers).length === 0) {
@@ -1172,8 +1457,10 @@ var windsurfPlugin = {
1172
1457
  pluginRegistry.register(claudeCodePlugin);
1173
1458
  pluginRegistry.register(copilotPlugin);
1174
1459
  pluginRegistry.register(cursorPlugin);
1460
+ pluginRegistry.register(codexPlugin);
1175
1461
  pluginRegistry.register(opencodePlugin);
1176
1462
  pluginRegistry.register(windsurfPlugin);
1463
+ pluginRegistry.register(geminiPlugin);
1177
1464
  function computeHash(content) {
1178
1465
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1179
1466
  }
@@ -1437,4 +1724,4 @@ function generateDefaultConfig(tools, versionControl) {
1437
1724
  return { tools: toolsConfig };
1438
1725
  }
1439
1726
 
1440
- 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 };
1727
+ export { CONFIG_DIRS, CONFIG_FILES, FileNotFoundError, LnaiError, ParseError, PluginError, TOOL_IDS, TOOL_OUTPUT_DIRS, UNIFIED_DIR, ValidationError, WriteError, claudeCodePlugin, codexPlugin, computeHash, configSchema, generateDefaultConfig, hasUnifiedConfig, initUnifiedConfig, mcpServerSchema, opencodePlugin, parseFrontmatter, parseUnifiedConfig, permissionsSchema, pluginRegistry, ruleFrontmatterSchema, runSyncPipeline, settingsSchema, skillFrontmatterSchema, toolConfigSchema, toolIdSchema, updateGitignore, validateConfig, validateSettings, validateUnifiedState, writeFiles };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lnai/core",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Core library for LNAI - unified AI config management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,8 @@
20
20
  "configuration",
21
21
  "claude",
22
22
  "cursor",
23
+ "gemini",
24
+ "antigravity",
23
25
  "opencode",
24
26
  "copilot",
25
27
  "github-copilot",