@lnai/core 0.3.0 → 0.4.1

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"];
4
+ declare const TOOL_IDS: readonly ["claudeCode", "opencode", "cursor", "copilot", "windsurf"];
5
5
  type ToolId = (typeof TOOL_IDS)[number];
6
6
  declare const CONFIG_FILES: {
7
7
  readonly config: "config.json";
@@ -67,6 +67,7 @@ declare const toolIdSchema: z.ZodEnum<{
67
67
  opencode: "opencode";
68
68
  cursor: "cursor";
69
69
  copilot: "copilot";
70
+ windsurf: "windsurf";
70
71
  }>;
71
72
  /** Settings configuration (Claude format as source of truth) */
72
73
  declare const settingsSchema: z.ZodObject<{
@@ -106,6 +107,10 @@ declare const configSchema: z.ZodObject<{
106
107
  enabled: z.ZodBoolean;
107
108
  versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
108
109
  }, z.core.$strip>>;
110
+ windsurf: z.ZodOptional<z.ZodObject<{
111
+ enabled: z.ZodBoolean;
112
+ versionControl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
113
+ }, z.core.$strip>>;
109
114
  }, z.core.$strip>>;
110
115
  }, z.core.$strip>;
111
116
  /** Skill frontmatter (name and description required) */
@@ -243,7 +248,7 @@ declare const claudeCodePlugin: Plugin;
243
248
  * OpenCode plugin for exporting to opencode.json format
244
249
  *
245
250
  * Output structure:
246
- * - .opencode/AGENTS.md (symlink -> ../.ai/AGENTS.md)
251
+ * - AGENTS.md (symlink -> .ai/AGENTS.md) [at project root]
247
252
  * - .opencode/rules/ (symlink -> ../.ai/rules)
248
253
  * - .opencode/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
249
254
  * - opencode.json (generated config merged with .ai/.opencode/opencode.json)
@@ -301,12 +306,13 @@ interface InitOptions {
301
306
  tools?: ToolId[];
302
307
  minimal?: boolean;
303
308
  force?: boolean;
309
+ versionControl?: Record<ToolId, boolean>;
304
310
  }
305
311
  interface InitResult {
306
312
  created: string[];
307
313
  }
308
314
  declare function initUnifiedConfig(options: InitOptions): Promise<InitResult>;
309
315
  declare function hasUnifiedConfig(rootDir: string): Promise<boolean>;
310
- declare function generateDefaultConfig(tools?: ToolId[]): Config;
316
+ declare function generateDefaultConfig(tools?: ToolId[], versionControl?: Record<ToolId, boolean>): Config;
311
317
 
312
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 };
package/dist/index.js CHANGED
@@ -6,7 +6,13 @@ import * as crypto from 'crypto';
6
6
 
7
7
  // src/constants.ts
8
8
  var UNIFIED_DIR = ".ai";
9
- var TOOL_IDS = ["claudeCode", "opencode", "cursor", "copilot"];
9
+ var TOOL_IDS = [
10
+ "claudeCode",
11
+ "opencode",
12
+ "cursor",
13
+ "copilot",
14
+ "windsurf"
15
+ ];
10
16
  var CONFIG_FILES = {
11
17
  config: "config.json",
12
18
  settings: "settings.json",
@@ -21,13 +27,15 @@ var TOOL_OUTPUT_DIRS = {
21
27
  claudeCode: ".claude",
22
28
  opencode: ".opencode",
23
29
  cursor: ".cursor",
24
- copilot: ".github"
30
+ copilot: ".github",
31
+ windsurf: ".windsurf"
25
32
  };
26
33
  var OVERRIDE_DIRS = {
27
34
  claudeCode: ".claude",
28
35
  opencode: ".opencode",
29
36
  cursor: ".cursor",
30
- copilot: ".copilot"
37
+ copilot: ".copilot",
38
+ windsurf: ".windsurf"
31
39
  };
32
40
 
33
41
  // src/errors.ts
@@ -107,7 +115,13 @@ var toolConfigSchema = z.object({
107
115
  enabled: z.boolean(),
108
116
  versionControl: z.boolean().optional().default(false)
109
117
  });
110
- var toolIdSchema = z.enum(["claudeCode", "opencode", "cursor", "copilot"]);
118
+ var toolIdSchema = z.enum([
119
+ "claudeCode",
120
+ "opencode",
121
+ "cursor",
122
+ "copilot",
123
+ "windsurf"
124
+ ]);
111
125
  var settingsSchema = z.object({
112
126
  permissions: permissionsSchema.optional(),
113
127
  mcpServers: z.record(z.string(), mcpServerSchema).optional()
@@ -117,7 +131,8 @@ var configSchema = z.object({
117
131
  claudeCode: toolConfigSchema,
118
132
  opencode: toolConfigSchema,
119
133
  cursor: toolConfigSchema,
120
- copilot: toolConfigSchema
134
+ copilot: toolConfigSchema,
135
+ windsurf: toolConfigSchema
121
136
  }).partial().optional()
122
137
  });
123
138
  var skillFrontmatterSchema = z.object({
@@ -974,9 +989,9 @@ var opencodePlugin = {
974
989
  const outputDir = TOOL_OUTPUT_DIRS.opencode;
975
990
  if (state.agents) {
976
991
  files.push({
977
- path: `${outputDir}/AGENTS.md`,
992
+ path: "AGENTS.md",
978
993
  type: "symlink",
979
- target: `../${UNIFIED_DIR}/AGENTS.md`
994
+ target: `${UNIFIED_DIR}/AGENTS.md`
980
995
  });
981
996
  }
982
997
  if (state.rules.length > 0) {
@@ -1021,7 +1036,7 @@ var opencodePlugin = {
1021
1036
  if (!state.agents) {
1022
1037
  warnings.push({
1023
1038
  path: ["AGENTS.md"],
1024
- message: "No AGENTS.md found - .opencode/AGENTS.md will not be created"
1039
+ message: "No AGENTS.md found - root AGENTS.md will not be created"
1025
1040
  });
1026
1041
  }
1027
1042
  return { valid: true, errors: [], warnings, skipped: [] };
@@ -1049,11 +1064,116 @@ var PluginRegistry = class {
1049
1064
  };
1050
1065
  var pluginRegistry = new PluginRegistry();
1051
1066
 
1067
+ // src/plugins/windsurf/transforms.ts
1068
+ function transformRuleToWindsurf(rule) {
1069
+ const frontmatter = {
1070
+ trigger: "manual"
1071
+ };
1072
+ return {
1073
+ frontmatter,
1074
+ content: rule.content
1075
+ };
1076
+ }
1077
+ function serializeWindsurfRule(frontmatter, content) {
1078
+ const lines = ["---"];
1079
+ lines.push(`trigger: ${frontmatter.trigger}`);
1080
+ if (frontmatter.globs && frontmatter.globs.length > 0) {
1081
+ lines.push("globs:");
1082
+ for (const glob of frontmatter.globs) {
1083
+ lines.push(` - ${glob}`);
1084
+ }
1085
+ }
1086
+ if (frontmatter.description) {
1087
+ const escaped = frontmatter.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1088
+ lines.push(`description: "${escaped}"`);
1089
+ }
1090
+ lines.push("---");
1091
+ lines.push("");
1092
+ lines.push(content);
1093
+ return lines.join("\n");
1094
+ }
1095
+
1096
+ // src/plugins/windsurf/index.ts
1097
+ var windsurfPlugin = {
1098
+ id: "windsurf",
1099
+ name: "Windsurf",
1100
+ async detect(_rootDir) {
1101
+ return false;
1102
+ },
1103
+ async import(_rootDir) {
1104
+ return null;
1105
+ },
1106
+ async export(state, rootDir) {
1107
+ const files = [];
1108
+ const outputDir = TOOL_OUTPUT_DIRS.windsurf;
1109
+ if (state.agents) {
1110
+ files.push({
1111
+ path: "AGENTS.md",
1112
+ type: "symlink",
1113
+ target: `${UNIFIED_DIR}/AGENTS.md`
1114
+ });
1115
+ }
1116
+ for (const rule of state.rules) {
1117
+ const transformed = transformRuleToWindsurf(rule);
1118
+ const ruleContent = serializeWindsurfRule(
1119
+ transformed.frontmatter,
1120
+ transformed.content
1121
+ );
1122
+ files.push({
1123
+ path: `${outputDir}/rules/${rule.path}`,
1124
+ type: "text",
1125
+ content: ruleContent
1126
+ });
1127
+ }
1128
+ for (const skill of state.skills) {
1129
+ files.push({
1130
+ path: `${outputDir}/skills/${skill.path}`,
1131
+ type: "symlink",
1132
+ target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1133
+ });
1134
+ }
1135
+ return applyFileOverrides(files, rootDir, "windsurf");
1136
+ },
1137
+ validate(state) {
1138
+ const warnings = [];
1139
+ const skipped = [];
1140
+ if (!state.agents) {
1141
+ warnings.push({
1142
+ path: ["AGENTS.md"],
1143
+ message: "No AGENTS.md found - root AGENTS.md will not be created"
1144
+ });
1145
+ }
1146
+ if (state.rules.length > 0) {
1147
+ warnings.push({
1148
+ path: [".windsurf/rules"],
1149
+ message: "Rules are exported with 'trigger: manual' and require explicit @mention to invoke"
1150
+ });
1151
+ }
1152
+ if (state.settings?.mcpServers && Object.keys(state.settings.mcpServers).length > 0) {
1153
+ skipped.push({
1154
+ feature: "mcpServers",
1155
+ reason: "Windsurf uses global MCP config at ~/.codeium/windsurf/mcp_config.json - project-level MCP servers are not exported"
1156
+ });
1157
+ }
1158
+ if (state.settings?.permissions) {
1159
+ const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
1160
+ if (hasPermissions) {
1161
+ skipped.push({
1162
+ feature: "permissions",
1163
+ reason: "Windsurf does not support declarative permissions"
1164
+ });
1165
+ }
1166
+ }
1167
+ return { valid: true, errors: [], warnings, skipped };
1168
+ }
1169
+ };
1170
+
1052
1171
  // src/plugins/index.ts
1053
1172
  pluginRegistry.register(claudeCodePlugin);
1054
1173
  pluginRegistry.register(copilotPlugin);
1055
1174
  pluginRegistry.register(cursorPlugin);
1056
1175
  pluginRegistry.register(opencodePlugin);
1176
+ pluginRegistry.register(windsurfPlugin);
1057
1177
  function computeHash(content) {
1058
1178
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1059
1179
  }
@@ -1166,9 +1286,8 @@ async function updateGitignore(rootDir, paths) {
1166
1286
  const markerRegex = new RegExp(`${marker}[\\s\\S]*?${endMarker}\\n?`, "g");
1167
1287
  content = content.replace(markerRegex, "");
1168
1288
  content = content.trimEnd();
1169
- const newSection = ["", marker, ...paths.map((p) => p), endMarker, ""].join(
1170
- "\n"
1171
- );
1289
+ const uniquePaths = [...new Set(paths)];
1290
+ const newSection = ["", marker, ...uniquePaths, endMarker, ""].join("\n");
1172
1291
  content = content + newSection;
1173
1292
  await fs3.writeFile(gitignorePath, content, "utf-8");
1174
1293
  }
@@ -1244,10 +1363,16 @@ async function runSyncPipeline(options) {
1244
1363
  return results;
1245
1364
  }
1246
1365
  async function initUnifiedConfig(options) {
1247
- const { rootDir, tools, minimal = false, force = false } = options;
1366
+ const {
1367
+ rootDir,
1368
+ tools,
1369
+ minimal = false,
1370
+ force = false,
1371
+ versionControl
1372
+ } = options;
1248
1373
  const aiDir = path.join(rootDir, UNIFIED_DIR);
1249
1374
  const created = [];
1250
- const config = generateDefaultConfig(tools);
1375
+ const config = generateDefaultConfig(tools, versionControl);
1251
1376
  const exists = await hasUnifiedConfig(rootDir);
1252
1377
  if (exists && !force) {
1253
1378
  throw new ValidationError(
@@ -1289,7 +1414,7 @@ async function hasUnifiedConfig(rootDir) {
1289
1414
  return false;
1290
1415
  }
1291
1416
  }
1292
- function generateDefaultConfig(tools) {
1417
+ function generateDefaultConfig(tools, versionControl) {
1293
1418
  if (tools) {
1294
1419
  const validation = validateToolIds(tools);
1295
1420
  if (!validation.valid) {
@@ -1306,7 +1431,7 @@ function generateDefaultConfig(tools) {
1306
1431
  for (const toolId of TOOL_IDS) {
1307
1432
  toolsConfig[toolId] = {
1308
1433
  enabled: enabledTools.includes(toolId),
1309
- versionControl: false
1434
+ versionControl: versionControl?.[toolId] ?? false
1310
1435
  };
1311
1436
  }
1312
1437
  return { tools: toolsConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lnai/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Core library for LNAI - unified AI config management",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,7 @@
23
23
  "opencode",
24
24
  "copilot",
25
25
  "github-copilot",
26
+ "windsurf",
26
27
  "cli",
27
28
  "ai-tools"
28
29
  ],