ai-dot 0.1.0-alpha.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.cjs ADDED
@@ -0,0 +1,3308 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ALL_TARGETS: () => ALL_TARGETS,
24
+ Decision: () => Decision,
25
+ HookEvent: () => HookEvent,
26
+ SCOPE_PRECEDENCE: () => SCOPE_PRECEDENCE,
27
+ Scope: () => Scope,
28
+ TEMPLATES: () => TEMPLATES,
29
+ TEMPLATE_NAMES: () => TEMPLATE_NAMES,
30
+ TargetTool: () => TargetTool,
31
+ Transport: () => Transport,
32
+ agentsEmitter: () => agentsEmitter,
33
+ contentHash: () => contentHash,
34
+ createAgent: () => createAgent,
35
+ createDirective: () => createDirective,
36
+ createHook: () => createHook,
37
+ createIgnorePattern: () => createIgnorePattern,
38
+ createPermission: () => createPermission,
39
+ createSetting: () => createSetting,
40
+ createSkill: () => createSkill,
41
+ createToolServer: () => createToolServer,
42
+ diffFiles: () => diffFiles,
43
+ directivesEmitter: () => directivesEmitter,
44
+ emptyConfig: () => emptyConfig,
45
+ getTemplate: () => getTemplate,
46
+ hooksEmitter: () => hooksEmitter,
47
+ isAgent: () => isAgent,
48
+ isDirective: () => isDirective,
49
+ isHook: () => isHook,
50
+ isIgnorePattern: () => isIgnorePattern,
51
+ isPermission: () => isPermission,
52
+ isSetting: () => isSetting,
53
+ isSkill: () => isSkill,
54
+ isToolServer: () => isToolServer,
55
+ loadMarkdownFile: () => loadMarkdownFile,
56
+ loadMergedConfig: () => loadMergedConfig,
57
+ loadProjectConfig: () => loadProjectConfig,
58
+ loadSkills: () => loadSkills,
59
+ loadState: () => loadState,
60
+ mcpEmitter: () => mcpEmitter,
61
+ mergeConfigs: () => mergeConfigs,
62
+ parseClaude: () => parseClaude,
63
+ parseCodex: () => parseCodex,
64
+ parseCursor: () => parseCursor,
65
+ parseMarkdownWithFrontmatter: () => parseMarkdownWithFrontmatter,
66
+ parseOpenCode: () => parseOpenCode,
67
+ permissionsEmitter: () => permissionsEmitter,
68
+ runCheck: () => runCheck,
69
+ runImport: () => runImport,
70
+ runImportCommand: () => runImportCommand,
71
+ runInit: () => runInit,
72
+ runStatus: () => runStatus,
73
+ runSync: () => runSync,
74
+ saveState: () => saveState,
75
+ scanForConfigs: () => scanForConfigs,
76
+ scopeOutranks: () => scopeOutranks,
77
+ skillsEmitter: () => skillsEmitter,
78
+ validateConfig: () => validateConfig,
79
+ writeProjectConfig: () => writeProjectConfig
80
+ });
81
+ module.exports = __toCommonJS(index_exports);
82
+
83
+ // src/commands/add.ts
84
+ var import_node_fs = require("fs");
85
+ var import_promises = require("fs/promises");
86
+ var import_node_path = require("path");
87
+ var import_yaml = require("yaml");
88
+
89
+ // src/tui.ts
90
+ var import_prompts = require("@clack/prompts");
91
+ function isTTY() {
92
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
93
+ }
94
+ function cancelGuard(value) {
95
+ if ((0, import_prompts.isCancel)(value)) {
96
+ (0, import_prompts.outro)("Cancelled.");
97
+ process.exit(0);
98
+ }
99
+ return value;
100
+ }
101
+
102
+ // src/commands/check.ts
103
+ var import_node_os2 = require("os");
104
+ var import_node_path4 = require("path");
105
+
106
+ // src/config/loader.ts
107
+ var import_promises4 = require("fs/promises");
108
+ var import_node_os = require("os");
109
+ var import_node_path3 = require("path");
110
+ var import_yaml2 = require("yaml");
111
+
112
+ // src/config/markdown-loader.ts
113
+ var import_promises2 = require("fs/promises");
114
+ function parseMarkdownWithFrontmatter(raw) {
115
+ const trimmed = raw.trimStart();
116
+ if (!trimmed.startsWith("---")) {
117
+ return { frontmatter: {}, body: raw.trim() };
118
+ }
119
+ const endIndex = trimmed.indexOf("---", 3);
120
+ if (endIndex === -1) {
121
+ return { frontmatter: {}, body: raw.trim() };
122
+ }
123
+ const frontmatterBlock = trimmed.slice(3, endIndex).trim();
124
+ const body = trimmed.slice(endIndex + 3).trim();
125
+ const frontmatter = {};
126
+ for (const line of frontmatterBlock.split("\n")) {
127
+ const colonIdx = line.indexOf(":");
128
+ if (colonIdx === -1) continue;
129
+ const key = line.slice(0, colonIdx).trim();
130
+ const rawValue = line.slice(colonIdx + 1).trim();
131
+ frontmatter[key] = parseFrontmatterValue(rawValue);
132
+ }
133
+ return { frontmatter, body };
134
+ }
135
+ function parseFrontmatterValue(raw) {
136
+ if (raw === "true") return true;
137
+ if (raw === "false") return false;
138
+ if (raw === "") return "";
139
+ if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
140
+ if (raw.startsWith("[") && raw.endsWith("]")) {
141
+ const inner = raw.slice(1, -1).trim();
142
+ if (!inner) return [];
143
+ return inner.split(",").map((s) => {
144
+ const v = s.trim();
145
+ if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
146
+ return v.slice(1, -1);
147
+ }
148
+ return v;
149
+ });
150
+ }
151
+ if (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) {
152
+ return raw.slice(1, -1);
153
+ }
154
+ return raw;
155
+ }
156
+ async function loadMarkdownFile(filePath) {
157
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
158
+ return parseMarkdownWithFrontmatter(content);
159
+ }
160
+
161
+ // src/config/schema.ts
162
+ function emptyConfig() {
163
+ return {
164
+ directives: [],
165
+ skills: [],
166
+ agents: [],
167
+ toolServers: [],
168
+ hooks: [],
169
+ permissions: [],
170
+ settings: [],
171
+ ignorePatterns: []
172
+ };
173
+ }
174
+ function mergeConfigs(base, override) {
175
+ const mergedSettings = [...base.settings];
176
+ for (const setting of override.settings) {
177
+ const idx = mergedSettings.findIndex((s) => s.key === setting.key);
178
+ if (idx >= 0) {
179
+ mergedSettings[idx] = setting;
180
+ } else {
181
+ mergedSettings.push(setting);
182
+ }
183
+ }
184
+ const mergedServers = [...base.toolServers];
185
+ for (const server of override.toolServers) {
186
+ const idx = mergedServers.findIndex((s) => s.name === server.name);
187
+ if (idx >= 0) {
188
+ mergedServers[idx] = server;
189
+ } else {
190
+ mergedServers.push(server);
191
+ }
192
+ }
193
+ return {
194
+ directives: [...base.directives, ...override.directives],
195
+ skills: [...base.skills, ...override.skills],
196
+ agents: [...base.agents, ...override.agents],
197
+ toolServers: mergedServers,
198
+ hooks: [...base.hooks, ...override.hooks],
199
+ permissions: [...base.permissions, ...override.permissions],
200
+ settings: mergedSettings,
201
+ ignorePatterns: [...base.ignorePatterns, ...override.ignorePatterns]
202
+ };
203
+ }
204
+ function validateConfig(config) {
205
+ const errors = [];
206
+ for (const d of config.directives) {
207
+ if (!d.content.trim()) {
208
+ errors.push({ file: "directives", message: "Directive has empty content" });
209
+ }
210
+ }
211
+ for (const s of config.skills) {
212
+ if (!s.name.trim()) {
213
+ errors.push({ file: "skills", message: "Skill has empty name" });
214
+ }
215
+ if (!s.content.trim()) {
216
+ errors.push({ file: `skills/${s.name}`, message: "Skill has empty content" });
217
+ }
218
+ }
219
+ for (const a of config.agents) {
220
+ if (!a.name.trim()) {
221
+ errors.push({ file: "agents", message: "Agent has empty name" });
222
+ }
223
+ if (!a.instructions.trim()) {
224
+ errors.push({ file: `agents/${a.name}`, message: "Agent has empty instructions" });
225
+ }
226
+ }
227
+ for (const ts of config.toolServers) {
228
+ if (!ts.name.trim()) {
229
+ errors.push({ file: "config.yaml", message: "ToolServer has empty name" });
230
+ }
231
+ if (ts.transport === "stdio" && !ts.command) {
232
+ errors.push({
233
+ file: "config.yaml",
234
+ message: `ToolServer "${ts.name}" uses stdio transport but has no command`
235
+ });
236
+ }
237
+ if ((ts.transport === "http" || ts.transport === "sse") && !ts.url) {
238
+ errors.push({
239
+ file: "config.yaml",
240
+ message: `ToolServer "${ts.name}" uses ${ts.transport} transport but has no url`
241
+ });
242
+ }
243
+ }
244
+ for (const p of config.permissions) {
245
+ if (!p.tool.trim()) {
246
+ errors.push({ file: "config.yaml", message: "Permission has empty tool" });
247
+ }
248
+ }
249
+ return { valid: errors.length === 0, errors };
250
+ }
251
+
252
+ // src/config/skill-loader.ts
253
+ var import_promises3 = require("fs/promises");
254
+ var import_node_path2 = require("path");
255
+ async function loadSkills(skillsDir) {
256
+ const entries = await (0, import_promises3.readdir)(skillsDir).catch(() => []);
257
+ const skills = [];
258
+ for (const entry of entries) {
259
+ const skillDir = (0, import_node_path2.join)(skillsDir, entry);
260
+ const skillStat = await (0, import_promises3.stat)(skillDir).catch(() => null);
261
+ if (!skillStat?.isDirectory()) continue;
262
+ const skillFile = (0, import_node_path2.join)(skillDir, "SKILL.md");
263
+ const content = await (0, import_promises3.readFile)(skillFile, "utf-8").catch(() => null);
264
+ if (content === null) continue;
265
+ skills.push({
266
+ name: entry,
267
+ description: extractDescription(content),
268
+ content,
269
+ disableAutoInvocation: false
270
+ });
271
+ }
272
+ return skills;
273
+ }
274
+ function extractDescription(content) {
275
+ const lines = content.split("\n");
276
+ for (const line of lines) {
277
+ const trimmed = line.trim();
278
+ if (!trimmed || trimmed.startsWith("#")) continue;
279
+ return trimmed.length > 100 ? `${trimmed.slice(0, 97)}...` : trimmed;
280
+ }
281
+ return "";
282
+ }
283
+
284
+ // src/config/loader.ts
285
+ async function loadProjectConfig(aiDir, scope = "project") {
286
+ const config = emptyConfig();
287
+ const errors = [];
288
+ const configPath = (0, import_node_path3.join)(aiDir, "config.yaml");
289
+ await loadConfigYaml(configPath, config, errors, scope);
290
+ const directivesDir = (0, import_node_path3.join)(aiDir, "directives");
291
+ await loadDirectives(directivesDir, config, errors, scope);
292
+ const skillsDir = (0, import_node_path3.join)(aiDir, "skills");
293
+ try {
294
+ config.skills = await loadSkills(skillsDir);
295
+ } catch {
296
+ }
297
+ const agentsDir = (0, import_node_path3.join)(aiDir, "agents");
298
+ await loadAgents(agentsDir, config, errors);
299
+ return { config, errors };
300
+ }
301
+ async function loadMergedConfig(projectDir) {
302
+ const userAiDir = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".ai");
303
+ const projectAiDir = (0, import_node_path3.join)(projectDir, ".ai");
304
+ const userResult = await loadProjectConfig(userAiDir, "user");
305
+ const projectResult = await loadProjectConfig(projectAiDir, "project");
306
+ const errors = [...userResult.errors, ...projectResult.errors];
307
+ const config = mergeConfigs(userResult.config, projectResult.config);
308
+ return { config, errors };
309
+ }
310
+ async function loadConfigYaml(filePath, config, errors, scope = "project") {
311
+ let raw;
312
+ try {
313
+ raw = await (0, import_promises4.readFile)(filePath, "utf-8");
314
+ } catch {
315
+ return;
316
+ }
317
+ let parsed;
318
+ try {
319
+ parsed = (0, import_yaml2.parse)(raw) ?? {};
320
+ } catch (e) {
321
+ errors.push({
322
+ file: filePath,
323
+ message: `Invalid YAML: ${e instanceof Error ? e.message : String(e)}`
324
+ });
325
+ return;
326
+ }
327
+ if (typeof parsed !== "object" || parsed === null) {
328
+ errors.push({ file: filePath, message: "config.yaml must be a YAML mapping" });
329
+ return;
330
+ }
331
+ if (parsed.mcpServers && typeof parsed.mcpServers === "object") {
332
+ for (const [name, raw2] of Object.entries(parsed.mcpServers)) {
333
+ const server = parseToolServer(name, raw2, filePath, errors, scope);
334
+ if (server) config.toolServers.push(server);
335
+ }
336
+ }
337
+ if (Array.isArray(parsed.permissions)) {
338
+ for (const raw2 of parsed.permissions) {
339
+ const perm = parsePermission(raw2, filePath, errors, scope);
340
+ if (perm) config.permissions.push(perm);
341
+ }
342
+ }
343
+ if (parsed.settings && typeof parsed.settings === "object") {
344
+ for (const [key, value] of Object.entries(parsed.settings)) {
345
+ config.settings.push({ key, value, scope });
346
+ }
347
+ }
348
+ if (Array.isArray(parsed.hooks)) {
349
+ for (const raw2 of parsed.hooks) {
350
+ const hook = parseHook(raw2, filePath, errors, scope);
351
+ if (hook) config.hooks.push(hook);
352
+ }
353
+ }
354
+ if (Array.isArray(parsed.ignore)) {
355
+ for (const raw2 of parsed.ignore) {
356
+ if (typeof raw2 === "string") {
357
+ config.ignorePatterns.push({ pattern: raw2, scope });
358
+ }
359
+ }
360
+ }
361
+ }
362
+ function parseToolServer(name, raw, file, errors, scope = "project") {
363
+ if (typeof raw !== "object" || raw === null) {
364
+ errors.push({ file, message: `mcpServers.${name} must be an object` });
365
+ return null;
366
+ }
367
+ const obj = raw;
368
+ const transport = typeof obj.transport === "string" ? obj.transport : "stdio";
369
+ return {
370
+ name,
371
+ transport,
372
+ command: typeof obj.command === "string" ? obj.command : void 0,
373
+ url: typeof obj.url === "string" ? obj.url : void 0,
374
+ args: Array.isArray(obj.args) ? obj.args.map(String) : void 0,
375
+ env: typeof obj.env === "object" && obj.env !== null ? Object.fromEntries(
376
+ Object.entries(obj.env).map(([k, v]) => [k, String(v)])
377
+ ) : void 0,
378
+ enabledTools: Array.isArray(obj.enabledTools) ? obj.enabledTools.map(String) : void 0,
379
+ disabledTools: Array.isArray(obj.disabledTools) ? obj.disabledTools.map(String) : void 0,
380
+ scope
381
+ };
382
+ }
383
+ function parsePermission(raw, file, errors, scope = "project") {
384
+ if (typeof raw !== "object" || raw === null) {
385
+ errors.push({ file, message: "Permission entry must be an object" });
386
+ return null;
387
+ }
388
+ const obj = raw;
389
+ if (typeof obj.tool !== "string" || typeof obj.decision !== "string") {
390
+ errors.push({ file, message: "Permission requires 'tool' and 'decision' fields" });
391
+ return null;
392
+ }
393
+ return {
394
+ tool: obj.tool,
395
+ pattern: typeof obj.pattern === "string" ? obj.pattern : void 0,
396
+ decision: obj.decision,
397
+ scope
398
+ };
399
+ }
400
+ function parseHook(raw, file, errors, scope = "project") {
401
+ if (typeof raw !== "object" || raw === null) {
402
+ errors.push({ file, message: "Hook entry must be an object" });
403
+ return null;
404
+ }
405
+ const obj = raw;
406
+ if (typeof obj.event !== "string" || typeof obj.handler !== "string") {
407
+ errors.push({ file, message: "Hook requires 'event' and 'handler' fields" });
408
+ return null;
409
+ }
410
+ return {
411
+ event: obj.event,
412
+ matcher: typeof obj.matcher === "string" ? obj.matcher : void 0,
413
+ handler: obj.handler,
414
+ scope
415
+ };
416
+ }
417
+ async function loadDirectives(directivesDir, config, errors, scope = "project") {
418
+ let files;
419
+ try {
420
+ files = await (0, import_promises4.readdir)(directivesDir);
421
+ } catch {
422
+ return;
423
+ }
424
+ for (const file of files.filter((f) => f.endsWith(".md"))) {
425
+ const filePath = (0, import_node_path3.join)(directivesDir, file);
426
+ try {
427
+ const { frontmatter, body } = await loadMarkdownFile(filePath);
428
+ const directive = {
429
+ content: body,
430
+ scope: typeof frontmatter.scope === "string" ? frontmatter.scope : scope,
431
+ alwaysApply: frontmatter.alwaysApply !== false,
432
+ appliesTo: Array.isArray(frontmatter.appliesTo) ? frontmatter.appliesTo.map(String) : typeof frontmatter.appliesTo === "string" ? [frontmatter.appliesTo] : void 0,
433
+ description: typeof frontmatter.description === "string" ? frontmatter.description : file.replace(/\.md$/, "")
434
+ };
435
+ config.directives.push(directive);
436
+ } catch (e) {
437
+ errors.push({
438
+ file: filePath,
439
+ message: `Failed to parse: ${e instanceof Error ? e.message : String(e)}`
440
+ });
441
+ }
442
+ }
443
+ }
444
+ async function loadAgents(agentsDir, config, errors) {
445
+ let files;
446
+ try {
447
+ files = await (0, import_promises4.readdir)(agentsDir);
448
+ } catch {
449
+ return;
450
+ }
451
+ for (const file of files.filter((f) => f.endsWith(".md"))) {
452
+ const filePath = (0, import_node_path3.join)(agentsDir, file);
453
+ try {
454
+ const { frontmatter, body } = await loadMarkdownFile(filePath);
455
+ const agent = {
456
+ name: file.replace(/\.md$/, ""),
457
+ description: typeof frontmatter.description === "string" ? frontmatter.description : "",
458
+ instructions: body,
459
+ model: typeof frontmatter.model === "string" ? frontmatter.model : void 0,
460
+ readonly: frontmatter.readonly === true ? true : void 0,
461
+ tools: Array.isArray(frontmatter.tools) ? frontmatter.tools.map(String) : void 0
462
+ };
463
+ config.agents.push(agent);
464
+ } catch (e) {
465
+ errors.push({
466
+ file: filePath,
467
+ message: `Failed to parse: ${e instanceof Error ? e.message : String(e)}`
468
+ });
469
+ }
470
+ }
471
+ }
472
+
473
+ // src/emitters/types.ts
474
+ var TargetTool = {
475
+ Claude: "claude",
476
+ Cursor: "cursor",
477
+ Codex: "codex",
478
+ OpenCode: "opencode",
479
+ Copilot: "copilot",
480
+ Antigravity: "antigravity"
481
+ };
482
+ var ALL_TARGETS = [
483
+ TargetTool.Claude,
484
+ TargetTool.Cursor,
485
+ TargetTool.Codex,
486
+ TargetTool.OpenCode,
487
+ TargetTool.Copilot,
488
+ TargetTool.Antigravity
489
+ ];
490
+
491
+ // src/commands/check.ts
492
+ async function runCheck(projectDir, scope = "project") {
493
+ const isUserScope = scope === "user";
494
+ const label = isUserScope ? "~/.ai/" : ".ai/";
495
+ console.log(`Checking ${label} configuration...
496
+ `);
497
+ const { config, errors: loadErrors } = isUserScope ? await loadProjectConfig((0, import_node_path4.join)((0, import_node_os2.homedir)(), ".ai"), "user") : await loadMergedConfig(projectDir);
498
+ if (loadErrors.length > 0) {
499
+ console.error("\x1B[31mLoad errors:\x1B[0m");
500
+ for (const err of loadErrors) {
501
+ console.error(` ${err.file}${err.line ? `:${err.line}` : ""}: ${err.message}`);
502
+ }
503
+ process.exitCode = 1;
504
+ return;
505
+ }
506
+ const { valid, errors: valErrors } = validateConfig(config);
507
+ if (!valid) {
508
+ console.error("\x1B[31mValidation errors:\x1B[0m");
509
+ for (const err of valErrors) {
510
+ console.error(` ${err.file}: ${err.message}`);
511
+ }
512
+ process.exitCode = 1;
513
+ return;
514
+ }
515
+ console.log("\x1B[32m\u2713\x1B[0m Config is valid\n");
516
+ printSummary(config);
517
+ console.log("\n\x1B[1mTarget compatibility:\x1B[0m\n");
518
+ for (const target of ALL_TARGETS) {
519
+ checkTarget(config, target);
520
+ }
521
+ }
522
+ function printSummary(config) {
523
+ const counts = [
524
+ ["Directives", config.directives.length],
525
+ ["Skills", config.skills.length],
526
+ ["Agents", config.agents.length],
527
+ ["MCP Servers", config.toolServers.length],
528
+ ["Permissions", config.permissions.length],
529
+ ["Settings", config.settings.length],
530
+ ["Hooks", config.hooks.length],
531
+ ["Ignore patterns", config.ignorePatterns.length]
532
+ ];
533
+ console.log("\x1B[1mConfig summary:\x1B[0m");
534
+ for (const [label, count] of counts) {
535
+ if (count > 0) {
536
+ console.log(` ${label}: ${count}`);
537
+ }
538
+ }
539
+ }
540
+ function checkTarget(config, target) {
541
+ const warnings = [];
542
+ if (target === "codex") {
543
+ if (config.hooks.length > 0) {
544
+ warnings.push("Hooks are not supported \u2014 will be skipped");
545
+ }
546
+ if (config.permissions.length > 0) {
547
+ warnings.push("Per-tool permissions are lossy \u2014 mapped to approval_policy");
548
+ }
549
+ if (config.directives.some((d) => d.appliesTo?.length)) {
550
+ warnings.push("File-scoped directives are not enforced \u2014 included as notes only");
551
+ }
552
+ if (config.agents.some((a) => a.tools?.length)) {
553
+ warnings.push("Per-agent tool restrictions are not supported");
554
+ }
555
+ }
556
+ if (target === "cursor") {
557
+ if (config.hooks.length > 0) {
558
+ warnings.push("Hook support is limited \u2014 hooks not directly emitted");
559
+ }
560
+ }
561
+ const icon = warnings.length > 0 ? "\x1B[33m\u26A0\x1B[0m" : "\x1B[32m\u2713\x1B[0m";
562
+ console.log(` ${icon} ${target}`);
563
+ for (const w of warnings) {
564
+ console.log(` - ${w}`);
565
+ }
566
+ }
567
+
568
+ // src/import/runner.ts
569
+ var import_node_path9 = require("path");
570
+
571
+ // src/import/parsers/claude.ts
572
+ var import_promises5 = require("fs/promises");
573
+ var import_node_path5 = require("path");
574
+ async function parseClaude(projectDir, files) {
575
+ const config = emptyConfig();
576
+ for (const file of files) {
577
+ switch (file.kind) {
578
+ case "directives":
579
+ if (file.relativePath === "CLAUDE.md") {
580
+ await parseClaudeMd(file.path, config);
581
+ } else {
582
+ await parseClaudeRule(file.path, config);
583
+ }
584
+ break;
585
+ case "mcp":
586
+ await parseMcpJson(file.path, config);
587
+ break;
588
+ case "settings":
589
+ await parseClaudeSettings(file.path, config);
590
+ break;
591
+ case "agents":
592
+ await parseClaudeAgent(file.path, config);
593
+ break;
594
+ case "skills": {
595
+ const skillsDir = (0, import_node_path5.join)(projectDir, ".claude", "skills");
596
+ try {
597
+ config.skills = await loadSkills(skillsDir);
598
+ } catch {
599
+ }
600
+ break;
601
+ }
602
+ }
603
+ }
604
+ return config;
605
+ }
606
+ async function parseClaudeMd(filePath, config) {
607
+ const raw = await (0, import_promises5.readFile)(filePath, "utf-8").catch(() => null);
608
+ if (!raw) return;
609
+ const sections = raw.split("\n\n---\n\n");
610
+ for (const section of sections) {
611
+ const content = section.trim();
612
+ if (!content) continue;
613
+ config.directives.push({
614
+ content,
615
+ scope: "project",
616
+ alwaysApply: true,
617
+ description: extractHeading(content)
618
+ });
619
+ }
620
+ }
621
+ async function parseClaudeRule(filePath, config) {
622
+ const raw = await (0, import_promises5.readFile)(filePath, "utf-8").catch(() => null);
623
+ if (!raw) return;
624
+ let content = raw.trim();
625
+ let appliesTo;
626
+ const match = content.match(/^<!--\s*applies to:\s*(.+?)\s*-->\s*\n*/);
627
+ if (match) {
628
+ appliesTo = match[1].split(",").map((s) => s.trim());
629
+ content = content.slice(match[0].length).trim();
630
+ }
631
+ config.directives.push({
632
+ content,
633
+ scope: "project",
634
+ alwaysApply: !appliesTo,
635
+ appliesTo,
636
+ description: extractHeading(content)
637
+ });
638
+ }
639
+ async function parseMcpJson(filePath, config) {
640
+ const raw = await (0, import_promises5.readFile)(filePath, "utf-8").catch(() => null);
641
+ if (!raw) return;
642
+ try {
643
+ const parsed = JSON.parse(raw);
644
+ const servers = parsed.mcpServers ?? parsed;
645
+ if (typeof servers !== "object" || servers === null) return;
646
+ for (const [name, entry] of Object.entries(servers)) {
647
+ const obj = entry;
648
+ config.toolServers.push({
649
+ name,
650
+ transport: typeof obj.type === "string" ? obj.type : "stdio",
651
+ command: typeof obj.command === "string" ? obj.command : void 0,
652
+ url: typeof obj.url === "string" ? obj.url : void 0,
653
+ args: Array.isArray(obj.args) ? obj.args.map(String) : void 0,
654
+ env: typeof obj.env === "object" && obj.env !== null ? Object.fromEntries(
655
+ Object.entries(obj.env).map(([k, v]) => [k, String(v)])
656
+ ) : void 0,
657
+ scope: "project"
658
+ });
659
+ }
660
+ } catch {
661
+ }
662
+ }
663
+ async function parseClaudeSettings(filePath, config) {
664
+ const raw = await (0, import_promises5.readFile)(filePath, "utf-8").catch(() => null);
665
+ if (!raw) return;
666
+ try {
667
+ const parsed = JSON.parse(raw);
668
+ if (parsed.permissions) {
669
+ const perms = parsed.permissions;
670
+ for (const rule of perms.allow ?? []) {
671
+ const p = parsePermissionRule(rule, "allow");
672
+ if (p) {
673
+ config.permissions.push(p);
674
+ }
675
+ }
676
+ for (const rule of perms.deny ?? []) {
677
+ const p = parsePermissionRule(rule, "deny");
678
+ if (p) {
679
+ if (p.tool === "Read" || p.tool === "Edit") {
680
+ const pattern = p.pattern;
681
+ if (pattern) {
682
+ const existing = config.ignorePatterns.find((ip) => ip.pattern === pattern);
683
+ if (!existing) {
684
+ config.ignorePatterns.push({ pattern, scope: "project" });
685
+ }
686
+ }
687
+ } else {
688
+ config.permissions.push(p);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ if (parsed.hooks && typeof parsed.hooks === "object") {
694
+ for (const [event, entries] of Object.entries(parsed.hooks)) {
695
+ if (!Array.isArray(entries)) continue;
696
+ for (const entry of entries) {
697
+ const obj = entry;
698
+ if (typeof obj.command !== "string") continue;
699
+ config.hooks.push({
700
+ event,
701
+ handler: obj.command,
702
+ matcher: typeof obj.matcher === "string" ? obj.matcher : void 0,
703
+ scope: "project"
704
+ });
705
+ }
706
+ }
707
+ }
708
+ const knownKeys = /* @__PURE__ */ new Set(["permissions", "hooks"]);
709
+ for (const [key, value] of Object.entries(parsed)) {
710
+ if (!knownKeys.has(key)) {
711
+ config.settings.push({ key, value, scope: "project" });
712
+ }
713
+ }
714
+ } catch {
715
+ }
716
+ }
717
+ function parsePermissionRule(rule, decision) {
718
+ const match = rule.match(/^(\w+)\((.+)\)$/);
719
+ if (match) {
720
+ return { tool: match[1], pattern: match[2], decision, scope: "project" };
721
+ }
722
+ return { tool: rule, decision, scope: "project" };
723
+ }
724
+ async function parseClaudeAgent(filePath, config) {
725
+ const raw = await (0, import_promises5.readFile)(filePath, "utf-8").catch(() => null);
726
+ if (!raw) return;
727
+ const name = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "agent";
728
+ config.agents.push({
729
+ name,
730
+ description: "",
731
+ instructions: raw.trim()
732
+ });
733
+ }
734
+ function extractHeading(content) {
735
+ const match = content.match(/^#\s+(.+)$/m);
736
+ if (match) return match[1].trim();
737
+ const firstLine = content.split("\n")[0]?.trim() ?? "";
738
+ return firstLine.length > 50 ? `${firstLine.slice(0, 47)}...` : firstLine;
739
+ }
740
+
741
+ // src/import/parsers/codex.ts
742
+ var import_promises6 = require("fs/promises");
743
+ var import_smol_toml = require("smol-toml");
744
+ async function parseCodex(_projectDir, files) {
745
+ const config = emptyConfig();
746
+ for (const file of files) {
747
+ switch (file.kind) {
748
+ case "settings":
749
+ await parseCodexToml(file.path, config);
750
+ break;
751
+ case "directives":
752
+ await parseAgentsMd(file.path, config);
753
+ break;
754
+ }
755
+ }
756
+ return config;
757
+ }
758
+ async function parseCodexToml(filePath, config) {
759
+ const raw = await (0, import_promises6.readFile)(filePath, "utf-8").catch(() => null);
760
+ if (!raw) return;
761
+ try {
762
+ const parsed = (0, import_smol_toml.parse)(raw);
763
+ if (parsed.mcp_servers && typeof parsed.mcp_servers === "object") {
764
+ for (const [name, entry] of Object.entries(parsed.mcp_servers)) {
765
+ const obj = entry;
766
+ config.toolServers.push({
767
+ name,
768
+ transport: typeof obj.type === "string" ? obj.type : "stdio",
769
+ command: typeof obj.command === "string" ? obj.command : void 0,
770
+ url: typeof obj.url === "string" ? obj.url : void 0,
771
+ args: Array.isArray(obj.args) ? obj.args.map(String) : void 0,
772
+ env: typeof obj.env === "object" && obj.env !== null ? Object.fromEntries(
773
+ Object.entries(obj.env).map(([k, v]) => [
774
+ k,
775
+ String(v)
776
+ ])
777
+ ) : void 0,
778
+ scope: "project"
779
+ });
780
+ }
781
+ }
782
+ if (typeof parsed.approval_policy === "string") {
783
+ if (parsed.approval_policy === "unless-allowed") {
784
+ config.permissions.push({
785
+ tool: "Bash",
786
+ decision: "allow",
787
+ scope: "project"
788
+ });
789
+ }
790
+ }
791
+ if (Array.isArray(parsed.protected_paths)) {
792
+ for (const p of parsed.protected_paths) {
793
+ if (typeof p === "string") {
794
+ config.ignorePatterns.push({ pattern: p, scope: "project" });
795
+ }
796
+ }
797
+ }
798
+ const knownKeys = /* @__PURE__ */ new Set(["mcp_servers", "approval_policy", "protected_paths"]);
799
+ for (const [key, value] of Object.entries(parsed)) {
800
+ if (!knownKeys.has(key)) {
801
+ config.settings.push({ key, value, scope: "project" });
802
+ }
803
+ }
804
+ } catch {
805
+ }
806
+ }
807
+ async function parseAgentsMd(filePath, config) {
808
+ const raw = await (0, import_promises6.readFile)(filePath, "utf-8").catch(() => null);
809
+ if (!raw) return;
810
+ const lines = raw.split("\n");
811
+ let currentName = null;
812
+ let currentType = "directive";
813
+ let currentLines = [];
814
+ const flush = () => {
815
+ const content = currentLines.join("\n").trim();
816
+ if (!content) return;
817
+ if (currentType === "agent" && currentName) {
818
+ config.agents.push({
819
+ name: currentName,
820
+ description: "",
821
+ instructions: content
822
+ });
823
+ } else if (currentType === "directive") {
824
+ config.directives.push({
825
+ content,
826
+ scope: "project",
827
+ alwaysApply: true,
828
+ description: currentName ?? "directive"
829
+ });
830
+ }
831
+ };
832
+ for (const line of lines) {
833
+ const agentMatch = line.match(/^##\s+Agent:\s+(.+)$/);
834
+ const sectionMatch = !agentMatch ? line.match(/^##\s+(.+)$/) : null;
835
+ if (agentMatch) {
836
+ flush();
837
+ currentName = agentMatch[1].trim();
838
+ currentType = "agent";
839
+ currentLines = [];
840
+ } else if (sectionMatch) {
841
+ flush();
842
+ currentName = sectionMatch[1].trim();
843
+ currentType = "directive";
844
+ currentLines = [];
845
+ } else if (currentName !== null || currentType === "directive") {
846
+ if (line.match(/^#\s/) && currentLines.length === 0 && currentName === null) continue;
847
+ currentLines.push(line);
848
+ }
849
+ }
850
+ flush();
851
+ }
852
+
853
+ // src/import/parsers/cursor.ts
854
+ var import_promises7 = require("fs/promises");
855
+ async function parseCursor(_projectDir, files) {
856
+ const config = emptyConfig();
857
+ for (const file of files) {
858
+ switch (file.kind) {
859
+ case "directives":
860
+ await parseCursorRule(file.path, config);
861
+ break;
862
+ case "mcp":
863
+ await parseCursorMcp(file.path, config);
864
+ break;
865
+ case "agents":
866
+ await parseCursorAgent(file.path, config);
867
+ break;
868
+ }
869
+ }
870
+ return config;
871
+ }
872
+ async function parseCursorRule(filePath, config) {
873
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8").catch(() => null);
874
+ if (!raw) return;
875
+ const { frontmatter, body } = parseMarkdownWithFrontmatter(raw);
876
+ let appliesTo;
877
+ if (typeof frontmatter.globs === "string") {
878
+ appliesTo = frontmatter.globs.split(",").map((s) => s.trim());
879
+ } else if (Array.isArray(frontmatter.globs)) {
880
+ appliesTo = frontmatter.globs.map(String);
881
+ }
882
+ config.directives.push({
883
+ content: body,
884
+ scope: "project",
885
+ alwaysApply: frontmatter.alwaysApply !== false,
886
+ appliesTo,
887
+ description: typeof frontmatter.description === "string" ? frontmatter.description : filePath.split("/").pop()?.replace(/\.mdc?$/, "") ?? "rule"
888
+ });
889
+ }
890
+ async function parseCursorMcp(filePath, config) {
891
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8").catch(() => null);
892
+ if (!raw) return;
893
+ try {
894
+ const parsed = JSON.parse(raw);
895
+ const servers = parsed.mcpServers ?? parsed;
896
+ if (typeof servers !== "object" || servers === null) return;
897
+ for (const [name, entry] of Object.entries(servers)) {
898
+ const obj = entry;
899
+ config.toolServers.push({
900
+ name,
901
+ transport: typeof obj.type === "string" ? obj.type : "stdio",
902
+ command: typeof obj.command === "string" ? obj.command : void 0,
903
+ url: typeof obj.url === "string" ? obj.url : void 0,
904
+ args: Array.isArray(obj.args) ? obj.args.map(String) : void 0,
905
+ env: typeof obj.env === "object" && obj.env !== null ? Object.fromEntries(
906
+ Object.entries(obj.env).map(([k, v]) => [k, String(v)])
907
+ ) : void 0,
908
+ scope: "project"
909
+ });
910
+ }
911
+ } catch {
912
+ }
913
+ }
914
+ async function parseCursorAgent(filePath, config) {
915
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8").catch(() => null);
916
+ if (!raw) return;
917
+ const { frontmatter, body } = parseMarkdownWithFrontmatter(raw);
918
+ const name = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "agent";
919
+ config.agents.push({
920
+ name,
921
+ description: typeof frontmatter.description === "string" ? frontmatter.description : "",
922
+ instructions: body,
923
+ model: typeof frontmatter.model === "string" ? frontmatter.model : void 0,
924
+ readonly: frontmatter.readonly === true ? true : void 0,
925
+ tools: Array.isArray(frontmatter.tools) ? frontmatter.tools.map(String) : void 0
926
+ });
927
+ }
928
+
929
+ // src/import/parsers/opencode.ts
930
+ var import_promises8 = require("fs/promises");
931
+ var import_node_path6 = require("path");
932
+ async function parseOpenCode(projectDir, files) {
933
+ const config = emptyConfig();
934
+ for (const file of files) {
935
+ switch (file.kind) {
936
+ case "settings":
937
+ await parseOpenCodeJson(file.path, config);
938
+ break;
939
+ case "agents":
940
+ await parseOpenCodeAgent(file.path, config);
941
+ break;
942
+ case "skills": {
943
+ const skillsDir = (0, import_node_path6.join)(projectDir, ".opencode", "skills");
944
+ try {
945
+ config.skills = await loadSkills(skillsDir);
946
+ } catch {
947
+ }
948
+ break;
949
+ }
950
+ }
951
+ }
952
+ return config;
953
+ }
954
+ var SETTINGS_KEYS = /* @__PURE__ */ new Set(["model", "theme", "provider", "maxTokens"]);
955
+ async function parseOpenCodeJson(filePath, config) {
956
+ const raw = await (0, import_promises8.readFile)(filePath, "utf-8").catch(() => null);
957
+ if (!raw) return;
958
+ try {
959
+ const parsed = JSON.parse(raw);
960
+ if (parsed.mcp && typeof parsed.mcp === "object") {
961
+ for (const [name, entry] of Object.entries(parsed.mcp)) {
962
+ const obj = entry;
963
+ const rawType = typeof obj.type === "string" ? obj.type : "stdio";
964
+ const transport = rawType === "remote" ? "http" : rawType;
965
+ config.toolServers.push({
966
+ name,
967
+ transport,
968
+ command: typeof obj.command === "string" ? obj.command : void 0,
969
+ url: typeof obj.url === "string" ? obj.url : void 0,
970
+ args: Array.isArray(obj.args) ? obj.args.map(String) : void 0,
971
+ env: typeof obj.env === "object" && obj.env !== null ? Object.fromEntries(
972
+ Object.entries(obj.env).map(([k, v]) => [
973
+ k,
974
+ String(v)
975
+ ])
976
+ ) : void 0,
977
+ scope: "project"
978
+ });
979
+ }
980
+ }
981
+ if (parsed.permission && typeof parsed.permission === "object") {
982
+ for (const [tool, value] of Object.entries(parsed.permission)) {
983
+ if (typeof value === "string") {
984
+ config.permissions.push({
985
+ tool,
986
+ decision: value,
987
+ scope: "project"
988
+ });
989
+ } else if (typeof value === "object" && value !== null) {
990
+ for (const [pattern, decision] of Object.entries(value)) {
991
+ config.permissions.push({
992
+ tool,
993
+ pattern,
994
+ decision,
995
+ scope: "project"
996
+ });
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ if (Array.isArray(parsed.instructions)) {
1002
+ for (const instrPath of parsed.instructions) {
1003
+ if (typeof instrPath !== "string") continue;
1004
+ const absPath = (0, import_node_path6.join)(filePath, "..", instrPath);
1005
+ const content = await (0, import_promises8.readFile)(absPath, "utf-8").catch(() => null);
1006
+ if (!content) continue;
1007
+ let body = content.trim();
1008
+ let appliesTo;
1009
+ const match = body.match(/^<!--\s*applies to:\s*(.+?)\s*-->\s*\n*/);
1010
+ if (match) {
1011
+ appliesTo = match[1].split(",").map((s) => s.trim());
1012
+ body = body.slice(match[0].length).trim();
1013
+ }
1014
+ config.directives.push({
1015
+ content: body,
1016
+ scope: "project",
1017
+ alwaysApply: !appliesTo,
1018
+ appliesTo,
1019
+ description: extractHeading2(body)
1020
+ });
1021
+ }
1022
+ }
1023
+ if (parsed.watcher?.ignore && Array.isArray(parsed.watcher.ignore)) {
1024
+ for (const pattern of parsed.watcher.ignore) {
1025
+ if (typeof pattern === "string") {
1026
+ config.ignorePatterns.push({ pattern, scope: "project" });
1027
+ }
1028
+ }
1029
+ }
1030
+ for (const [key, value] of Object.entries(parsed)) {
1031
+ if (SETTINGS_KEYS.has(key)) {
1032
+ config.settings.push({ key, value, scope: "project" });
1033
+ }
1034
+ }
1035
+ } catch {
1036
+ }
1037
+ }
1038
+ async function parseOpenCodeAgent(filePath, config) {
1039
+ const raw = await (0, import_promises8.readFile)(filePath, "utf-8").catch(() => null);
1040
+ if (!raw) return;
1041
+ const { frontmatter, body } = parseMarkdownWithFrontmatter(raw);
1042
+ const name = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "agent";
1043
+ config.agents.push({
1044
+ name,
1045
+ description: typeof frontmatter.description === "string" ? frontmatter.description : "",
1046
+ instructions: body,
1047
+ model: typeof frontmatter.model === "string" ? frontmatter.model : void 0,
1048
+ mode: typeof frontmatter.mode === "string" ? frontmatter.mode : void 0,
1049
+ temperature: typeof frontmatter.temperature === "number" ? frontmatter.temperature : void 0,
1050
+ topP: typeof frontmatter.top_p === "number" ? frontmatter.top_p : void 0,
1051
+ steps: typeof frontmatter.steps === "number" ? frontmatter.steps : void 0,
1052
+ color: typeof frontmatter.color === "string" ? frontmatter.color : void 0,
1053
+ hidden: frontmatter.hidden === true ? true : void 0,
1054
+ disabled: frontmatter.disable === true ? true : void 0
1055
+ });
1056
+ }
1057
+ function extractHeading2(content) {
1058
+ const match = content.match(/^#\s+(.+)$/m);
1059
+ if (match) return match[1].trim();
1060
+ const firstLine = content.split("\n")[0]?.trim() ?? "";
1061
+ return firstLine.length > 50 ? `${firstLine.slice(0, 47)}...` : firstLine;
1062
+ }
1063
+
1064
+ // src/import/scanner.ts
1065
+ var import_promises9 = require("fs/promises");
1066
+ var import_node_path7 = require("path");
1067
+ async function exists(p) {
1068
+ try {
1069
+ await (0, import_promises9.stat)(p);
1070
+ return true;
1071
+ } catch {
1072
+ return false;
1073
+ }
1074
+ }
1075
+ async function listMdFiles(dir) {
1076
+ try {
1077
+ const entries = await (0, import_promises9.readdir)(dir);
1078
+ return entries.filter((f) => f.endsWith(".md"));
1079
+ } catch {
1080
+ return [];
1081
+ }
1082
+ }
1083
+ async function listMdcFiles(dir) {
1084
+ try {
1085
+ const entries = await (0, import_promises9.readdir)(dir);
1086
+ return entries.filter((f) => f.endsWith(".mdc"));
1087
+ } catch {
1088
+ return [];
1089
+ }
1090
+ }
1091
+ async function listSkillDirs(dir) {
1092
+ try {
1093
+ const entries = await (0, import_promises9.readdir)(dir);
1094
+ const dirs = [];
1095
+ for (const entry of entries) {
1096
+ const p = (0, import_node_path7.join)(dir, entry);
1097
+ const s = await (0, import_promises9.stat)(p).catch(() => null);
1098
+ if (s?.isDirectory()) {
1099
+ const skillFile = (0, import_node_path7.join)(p, "SKILL.md");
1100
+ if (await exists(skillFile)) dirs.push(entry);
1101
+ }
1102
+ }
1103
+ return dirs;
1104
+ } catch {
1105
+ return [];
1106
+ }
1107
+ }
1108
+ async function scanForConfigs(projectDir) {
1109
+ const detected = [];
1110
+ const add = (absPath, source, kind, label) => {
1111
+ detected.push({
1112
+ path: absPath,
1113
+ relativePath: (0, import_node_path7.relative)(projectDir, absPath),
1114
+ source,
1115
+ kind,
1116
+ label
1117
+ });
1118
+ };
1119
+ const claudeMd = (0, import_node_path7.join)(projectDir, "CLAUDE.md");
1120
+ if (await exists(claudeMd)) add(claudeMd, "claude", "directives", "CLAUDE.md (directives)");
1121
+ const mcpJson = (0, import_node_path7.join)(projectDir, ".mcp.json");
1122
+ if (await exists(mcpJson)) add(mcpJson, "claude", "mcp", ".mcp.json (MCP servers)");
1123
+ const claudeSettings = (0, import_node_path7.join)(projectDir, ".claude", "settings.json");
1124
+ if (await exists(claudeSettings))
1125
+ add(claudeSettings, "claude", "settings", ".claude/settings.json (permissions + settings)");
1126
+ const claudeRulesDir = (0, import_node_path7.join)(projectDir, ".claude", "rules");
1127
+ for (const f of await listMdFiles(claudeRulesDir)) {
1128
+ const p = (0, import_node_path7.join)(claudeRulesDir, f);
1129
+ add(p, "claude", "directives", `.claude/rules/${f} (directive)`);
1130
+ }
1131
+ const claudeAgentsDir = (0, import_node_path7.join)(projectDir, ".claude", "agents");
1132
+ for (const f of await listMdFiles(claudeAgentsDir)) {
1133
+ const p = (0, import_node_path7.join)(claudeAgentsDir, f);
1134
+ add(p, "claude", "agents", `.claude/agents/${f} (agent)`);
1135
+ }
1136
+ const claudeSkillsDir = (0, import_node_path7.join)(projectDir, ".claude", "skills");
1137
+ for (const s of await listSkillDirs(claudeSkillsDir)) {
1138
+ const p = (0, import_node_path7.join)(claudeSkillsDir, s, "SKILL.md");
1139
+ add(p, "claude", "skills", `.claude/skills/${s}/SKILL.md (skill)`);
1140
+ }
1141
+ const cursorRulesDir = (0, import_node_path7.join)(projectDir, ".cursor", "rules");
1142
+ for (const f of await listMdcFiles(cursorRulesDir)) {
1143
+ const p = (0, import_node_path7.join)(cursorRulesDir, f);
1144
+ add(p, "cursor", "directives", `.cursor/rules/${f} (directive)`);
1145
+ }
1146
+ const cursorMcp = (0, import_node_path7.join)(projectDir, ".cursor", "mcp.json");
1147
+ if (await exists(cursorMcp)) add(cursorMcp, "cursor", "mcp", ".cursor/mcp.json (MCP servers)");
1148
+ const cursorAgentsDir = (0, import_node_path7.join)(projectDir, ".cursor", "agents");
1149
+ for (const f of await listMdFiles(cursorAgentsDir)) {
1150
+ const p = (0, import_node_path7.join)(cursorAgentsDir, f);
1151
+ add(p, "cursor", "agents", `.cursor/agents/${f} (agent)`);
1152
+ }
1153
+ const codexToml = (0, import_node_path7.join)(projectDir, ".codex", "config.toml");
1154
+ if (await exists(codexToml)) add(codexToml, "codex", "settings", ".codex/config.toml (settings)");
1155
+ const agentsMd = (0, import_node_path7.join)(projectDir, "AGENTS.md");
1156
+ if (await exists(agentsMd)) add(agentsMd, "codex", "directives", "AGENTS.md (directives)");
1157
+ const opencodeJson = (0, import_node_path7.join)(projectDir, "opencode.json");
1158
+ if (await exists(opencodeJson))
1159
+ add(opencodeJson, "opencode", "settings", "opencode.json (config)");
1160
+ const opencodeAgentsDir = (0, import_node_path7.join)(projectDir, ".opencode", "agents");
1161
+ for (const f of await listMdFiles(opencodeAgentsDir)) {
1162
+ const p = (0, import_node_path7.join)(opencodeAgentsDir, f);
1163
+ add(p, "opencode", "agents", `.opencode/agents/${f} (agent)`);
1164
+ }
1165
+ const opencodeSkillsDir = (0, import_node_path7.join)(projectDir, ".opencode", "skills");
1166
+ for (const s of await listSkillDirs(opencodeSkillsDir)) {
1167
+ const p = (0, import_node_path7.join)(opencodeSkillsDir, s, "SKILL.md");
1168
+ add(p, "opencode", "skills", `.opencode/skills/${s}/SKILL.md (skill)`);
1169
+ }
1170
+ return detected;
1171
+ }
1172
+
1173
+ // src/import/writer.ts
1174
+ var import_promises10 = require("fs/promises");
1175
+ var import_node_path8 = require("path");
1176
+ var import_yaml3 = require("yaml");
1177
+ function slugify(str) {
1178
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1179
+ }
1180
+ async function writeProjectConfig(aiDir, config) {
1181
+ const written = [];
1182
+ await (0, import_promises10.mkdir)(aiDir, { recursive: true });
1183
+ await (0, import_promises10.mkdir)((0, import_node_path8.join)(aiDir, "directives"), { recursive: true });
1184
+ await (0, import_promises10.mkdir)((0, import_node_path8.join)(aiDir, "skills"), { recursive: true });
1185
+ await (0, import_promises10.mkdir)((0, import_node_path8.join)(aiDir, "agents"), { recursive: true });
1186
+ const yamlObj = {};
1187
+ if (config.toolServers.length > 0) {
1188
+ const servers = {};
1189
+ for (const s of config.toolServers) {
1190
+ const entry = { transport: s.transport };
1191
+ if (s.command) entry.command = s.command;
1192
+ if (s.url) entry.url = s.url;
1193
+ if (s.args?.length) entry.args = s.args;
1194
+ if (s.env && Object.keys(s.env).length > 0) entry.env = s.env;
1195
+ if (s.enabledTools?.length) entry.enabledTools = s.enabledTools;
1196
+ if (s.disabledTools?.length) entry.disabledTools = s.disabledTools;
1197
+ servers[s.name] = entry;
1198
+ }
1199
+ yamlObj.mcpServers = servers;
1200
+ } else {
1201
+ yamlObj.mcpServers = {};
1202
+ }
1203
+ if (config.permissions.length > 0) {
1204
+ yamlObj.permissions = config.permissions.map((p) => {
1205
+ const entry = { tool: p.tool, decision: p.decision };
1206
+ if (p.pattern) entry.pattern = p.pattern;
1207
+ return entry;
1208
+ });
1209
+ } else {
1210
+ yamlObj.permissions = [];
1211
+ }
1212
+ if (config.settings.length > 0) {
1213
+ const settingsObj = {};
1214
+ for (const s of config.settings) {
1215
+ settingsObj[s.key] = s.value;
1216
+ }
1217
+ yamlObj.settings = settingsObj;
1218
+ } else {
1219
+ yamlObj.settings = {};
1220
+ }
1221
+ if (config.hooks.length > 0) {
1222
+ yamlObj.hooks = config.hooks.map((h) => {
1223
+ const entry = { event: h.event, handler: h.handler };
1224
+ if (h.matcher) entry.matcher = h.matcher;
1225
+ return entry;
1226
+ });
1227
+ } else {
1228
+ yamlObj.hooks = [];
1229
+ }
1230
+ if (config.ignorePatterns.length > 0) {
1231
+ yamlObj.ignore = config.ignorePatterns.map((p) => p.pattern);
1232
+ } else {
1233
+ yamlObj.ignore = [];
1234
+ }
1235
+ const configPath = (0, import_node_path8.join)(aiDir, "config.yaml");
1236
+ await (0, import_promises10.writeFile)(configPath, (0, import_yaml3.stringify)(yamlObj), "utf-8");
1237
+ written.push(configPath);
1238
+ for (const d of config.directives) {
1239
+ const name = slugify(d.description || "rule");
1240
+ const fm = [];
1241
+ fm.push(`scope: ${d.scope}`);
1242
+ fm.push(`alwaysApply: ${d.alwaysApply}`);
1243
+ if (d.appliesTo?.length) {
1244
+ fm.push(`appliesTo: [${d.appliesTo.join(", ")}]`);
1245
+ }
1246
+ if (d.description) {
1247
+ fm.push(`description: ${d.description}`);
1248
+ }
1249
+ const header = `---
1250
+ ${fm.join("\n")}
1251
+ ---`;
1252
+ const filePath = (0, import_node_path8.join)(aiDir, "directives", `${name}.md`);
1253
+ await (0, import_promises10.writeFile)(filePath, `${header}
1254
+
1255
+ ${d.content}
1256
+ `, "utf-8");
1257
+ written.push(filePath);
1258
+ }
1259
+ for (const a of config.agents) {
1260
+ const fm = [];
1261
+ if (a.description) fm.push(`description: ${a.description}`);
1262
+ if (a.model) fm.push(`model: ${a.model}`);
1263
+ if (a.readonly) fm.push("readonly: true");
1264
+ if (a.tools?.length) fm.push(`tools: [${a.tools.join(", ")}]`);
1265
+ const header = fm.length > 0 ? `---
1266
+ ${fm.join("\n")}
1267
+ ---
1268
+
1269
+ ` : "";
1270
+ const filePath = (0, import_node_path8.join)(aiDir, "agents", `${a.name}.md`);
1271
+ await (0, import_promises10.writeFile)(filePath, `${header}${a.instructions}
1272
+ `, "utf-8");
1273
+ written.push(filePath);
1274
+ }
1275
+ for (const s of config.skills) {
1276
+ const skillDir = (0, import_node_path8.join)(aiDir, "skills", s.name);
1277
+ await (0, import_promises10.mkdir)(skillDir, { recursive: true });
1278
+ const filePath = (0, import_node_path8.join)(skillDir, "SKILL.md");
1279
+ await (0, import_promises10.writeFile)(filePath, s.content, "utf-8");
1280
+ written.push(filePath);
1281
+ }
1282
+ return written;
1283
+ }
1284
+
1285
+ // src/import/runner.ts
1286
+ async function runImport(projectDir, options) {
1287
+ const detected = await scanForConfigs(projectDir);
1288
+ const filtered = options.sourceFilter ? detected.filter((f) => f.source === options.sourceFilter) : detected;
1289
+ if (filtered.length === 0) {
1290
+ return { detected, imported: [], config: emptyConfig(), filesWritten: [] };
1291
+ }
1292
+ const selected = options.selectFiles ? await options.selectFiles(filtered) : filtered;
1293
+ if (selected.length === 0) {
1294
+ return { detected, imported: [], config: emptyConfig(), filesWritten: [] };
1295
+ }
1296
+ const bySource = groupBySource(selected);
1297
+ let importedConfig = emptyConfig();
1298
+ if (bySource.claude.length > 0) {
1299
+ const partial = await parseClaude(projectDir, bySource.claude);
1300
+ importedConfig = mergeConfigs(importedConfig, toFull(partial));
1301
+ }
1302
+ if (bySource.cursor.length > 0) {
1303
+ const partial = await parseCursor(projectDir, bySource.cursor);
1304
+ importedConfig = mergeConfigs(importedConfig, toFull(partial));
1305
+ }
1306
+ if (bySource.codex.length > 0) {
1307
+ const partial = await parseCodex(projectDir, bySource.codex);
1308
+ importedConfig = mergeConfigs(importedConfig, toFull(partial));
1309
+ }
1310
+ if (bySource.opencode.length > 0) {
1311
+ const partial = await parseOpenCode(projectDir, bySource.opencode);
1312
+ importedConfig = mergeConfigs(importedConfig, toFull(partial));
1313
+ }
1314
+ importedConfig.toolServers = deduplicateByName(importedConfig.toolServers);
1315
+ const aiDir = (0, import_node_path9.join)(projectDir, ".ai");
1316
+ try {
1317
+ const existing = await loadProjectConfig(aiDir, "project");
1318
+ if (hasContent(existing.config)) {
1319
+ importedConfig = mergeConfigs(importedConfig, existing.config);
1320
+ }
1321
+ } catch {
1322
+ }
1323
+ const filesWritten = await writeProjectConfig(aiDir, importedConfig);
1324
+ return {
1325
+ detected,
1326
+ imported: selected,
1327
+ config: importedConfig,
1328
+ filesWritten
1329
+ };
1330
+ }
1331
+ function groupBySource(files) {
1332
+ const result = {
1333
+ claude: [],
1334
+ cursor: [],
1335
+ codex: [],
1336
+ opencode: []
1337
+ };
1338
+ for (const f of files) {
1339
+ result[f.source].push(f);
1340
+ }
1341
+ return result;
1342
+ }
1343
+ function toFull(partial) {
1344
+ return { ...emptyConfig(), ...partial };
1345
+ }
1346
+ function deduplicateByName(items) {
1347
+ const seen = /* @__PURE__ */ new Set();
1348
+ return items.filter((item) => {
1349
+ if (seen.has(item.name)) return false;
1350
+ seen.add(item.name);
1351
+ return true;
1352
+ });
1353
+ }
1354
+ function hasContent(config) {
1355
+ return config.directives.length > 0 || config.skills.length > 0 || config.agents.length > 0 || config.toolServers.length > 0 || config.hooks.length > 0 || config.permissions.length > 0 || config.settings.length > 0 || config.ignorePatterns.length > 0;
1356
+ }
1357
+
1358
+ // src/commands/import.ts
1359
+ async function runImportCommand(projectDir, options) {
1360
+ if (isTTY()) {
1361
+ await interactiveImport(projectDir, options);
1362
+ } else {
1363
+ await nonInteractiveImport(projectDir, options);
1364
+ }
1365
+ }
1366
+ async function interactiveImport(projectDir, options) {
1367
+ (0, import_prompts.intro)("ai-dot import \u2014 detect and import existing configs");
1368
+ const detected = await scanForConfigs(projectDir);
1369
+ const filtered = options?.source ? detected.filter((f) => f.source === options.source) : detected;
1370
+ if (filtered.length === 0) {
1371
+ (0, import_prompts.outro)("No existing config files found.");
1372
+ return;
1373
+ }
1374
+ const result = await runImport(projectDir, {
1375
+ interactive: true,
1376
+ sourceFilter: options?.source,
1377
+ selectFiles: async (files) => {
1378
+ const selected = cancelGuard(
1379
+ await (0, import_prompts.multiselect)({
1380
+ message: "Select files to import:",
1381
+ options: files.map((f) => ({
1382
+ value: f.relativePath,
1383
+ label: f.label,
1384
+ hint: f.source
1385
+ })),
1386
+ required: true
1387
+ })
1388
+ );
1389
+ return files.filter((f) => selected.includes(f.relativePath));
1390
+ }
1391
+ });
1392
+ if (result.imported.length === 0) {
1393
+ (0, import_prompts.outro)("Nothing imported.");
1394
+ return;
1395
+ }
1396
+ console.log(`
1397
+ \x1B[32m\u2713\x1B[0m Imported ${result.imported.length} file(s):`);
1398
+ for (const f of result.imported) {
1399
+ console.log(` ${f.label}`);
1400
+ }
1401
+ console.log(`
1402
+ Wrote ${result.filesWritten.length} file(s) to .ai/`);
1403
+ (0, import_prompts.outro)("Run ai-dot sync to generate tool configs from imported settings.");
1404
+ }
1405
+ async function nonInteractiveImport(projectDir, options) {
1406
+ const result = await runImport(projectDir, {
1407
+ interactive: false,
1408
+ sourceFilter: options?.source
1409
+ });
1410
+ if (result.detected.length === 0) {
1411
+ console.log("No existing config files found.");
1412
+ return;
1413
+ }
1414
+ if (result.imported.length === 0) {
1415
+ console.log("No files to import.");
1416
+ return;
1417
+ }
1418
+ console.log(`\x1B[32m\u2713\x1B[0m Imported ${result.imported.length} file(s):`);
1419
+ for (const f of result.imported) {
1420
+ console.log(` ${f.label}`);
1421
+ }
1422
+ console.log(`
1423
+ Wrote ${result.filesWritten.length} file(s) to .ai/`);
1424
+ console.log("\nNext: run \x1B[1mai-dot sync\x1B[0m to generate tool configs.");
1425
+ }
1426
+
1427
+ // src/commands/init.ts
1428
+ var import_node_fs2 = require("fs");
1429
+ var import_node_path12 = require("path");
1430
+
1431
+ // src/templates/blank.ts
1432
+ function blankTemplate() {
1433
+ return emptyConfig();
1434
+ }
1435
+
1436
+ // src/templates/minimal.ts
1437
+ function minimalTemplate() {
1438
+ return {
1439
+ directives: [
1440
+ {
1441
+ content: "# Project Conventions\n\n- Follow existing code style and patterns\n- Write clear, descriptive commit messages\n- Keep functions focused and small\n- Add comments only where the logic isn't self-evident",
1442
+ scope: "project",
1443
+ alwaysApply: true,
1444
+ description: "conventions"
1445
+ }
1446
+ ],
1447
+ skills: [],
1448
+ agents: [],
1449
+ toolServers: [],
1450
+ hooks: [],
1451
+ permissions: [],
1452
+ settings: [],
1453
+ ignorePatterns: [
1454
+ { pattern: "node_modules/**", scope: "project" },
1455
+ { pattern: "dist/**", scope: "project" },
1456
+ { pattern: ".env", scope: "project" },
1457
+ { pattern: ".env.*", scope: "project" }
1458
+ ]
1459
+ };
1460
+ }
1461
+
1462
+ // src/templates/monorepo.ts
1463
+ function monorepoTemplate() {
1464
+ return {
1465
+ directives: [
1466
+ {
1467
+ content: "# Monorepo Conventions\n\n- Each package lives in its own directory under `packages/` or `apps/`\n- Changes to shared packages require careful review of downstream consumers\n- Prefer workspace-level dependencies; hoist shared deps to the root\n- Each package must have its own README, tests, and build script\n- Cross-package imports must go through published package names, not relative paths",
1468
+ scope: "project",
1469
+ alwaysApply: true,
1470
+ description: "monorepo-conventions"
1471
+ },
1472
+ {
1473
+ content: "# Code Review Guidelines\n\n- All changes need at least one approval before merging\n- Review for correctness, readability, and maintainability\n- Check that changes don't break other packages in the workspace\n- Verify that new dependencies are justified and properly scoped",
1474
+ scope: "project",
1475
+ alwaysApply: true,
1476
+ description: "code-review"
1477
+ }
1478
+ ],
1479
+ skills: [],
1480
+ agents: [
1481
+ {
1482
+ name: "reviewer",
1483
+ description: "Reviews code changes for quality and cross-package impact",
1484
+ instructions: "You are a code reviewer for a monorepo. When reviewing changes:\n\n1. Check if the change affects shared packages and identify downstream consumers\n2. Verify that cross-package dependencies are properly declared\n3. Ensure tests cover the change adequately\n4. Flag any breaking changes to package APIs\n5. Check for consistent coding patterns across the monorepo",
1485
+ readonly: true
1486
+ }
1487
+ ],
1488
+ toolServers: [],
1489
+ hooks: [],
1490
+ permissions: [
1491
+ { tool: "Bash", pattern: "npm *", decision: "allow", scope: "project" },
1492
+ { tool: "Bash", pattern: "pnpm *", decision: "allow", scope: "project" },
1493
+ { tool: "Bash", pattern: "yarn *", decision: "allow", scope: "project" },
1494
+ { tool: "Bash", pattern: "turbo *", decision: "allow", scope: "project" }
1495
+ ],
1496
+ settings: [],
1497
+ ignorePatterns: [
1498
+ { pattern: "node_modules/**", scope: "project" },
1499
+ { pattern: "dist/**", scope: "project" },
1500
+ { pattern: "build/**", scope: "project" },
1501
+ { pattern: ".env", scope: "project" },
1502
+ { pattern: ".env.*", scope: "project" },
1503
+ { pattern: ".turbo/**", scope: "project" }
1504
+ ]
1505
+ };
1506
+ }
1507
+
1508
+ // src/templates/python.ts
1509
+ function pythonTemplate() {
1510
+ return {
1511
+ directives: [
1512
+ {
1513
+ content: "# Python Conventions\n\n- Use type hints for all function signatures\n- Follow PEP 8 style guidelines\n- Use `ruff` or `black` for formatting\n- Prefer f-strings over `.format()` or `%` formatting\n- Use `pathlib.Path` instead of `os.path` for file operations\n- Use dataclasses or Pydantic models for structured data",
1514
+ scope: "project",
1515
+ alwaysApply: true,
1516
+ description: "python-conventions"
1517
+ },
1518
+ {
1519
+ content: "# Docstring Conventions\n\n- Write docstrings for all public modules, classes, and functions\n- Use Google-style docstrings\n- Include Args, Returns, and Raises sections as applicable\n- Keep the first line as a concise summary",
1520
+ scope: "project",
1521
+ alwaysApply: true,
1522
+ description: "docstrings"
1523
+ },
1524
+ {
1525
+ content: "# Testing Rules\n\n- Write tests using pytest\n- Use descriptive test names: `test_<what>_<condition>_<expected>`\n- Use fixtures for shared setup\n- Mock external services, not internal logic\n- Aim for high coverage on business logic",
1526
+ scope: "project",
1527
+ alwaysApply: true,
1528
+ description: "testing"
1529
+ }
1530
+ ],
1531
+ skills: [],
1532
+ agents: [],
1533
+ toolServers: [],
1534
+ hooks: [],
1535
+ permissions: [
1536
+ { tool: "Bash", pattern: "python *", decision: "allow", scope: "project" },
1537
+ { tool: "Bash", pattern: "pip *", decision: "allow", scope: "project" },
1538
+ { tool: "Bash", pattern: "pytest *", decision: "allow", scope: "project" }
1539
+ ],
1540
+ settings: [],
1541
+ ignorePatterns: [
1542
+ { pattern: "__pycache__/**", scope: "project" },
1543
+ { pattern: ".venv/**", scope: "project" },
1544
+ { pattern: "*.pyc", scope: "project" },
1545
+ { pattern: ".env", scope: "project" },
1546
+ { pattern: "dist/**", scope: "project" },
1547
+ { pattern: "*.egg-info/**", scope: "project" }
1548
+ ]
1549
+ };
1550
+ }
1551
+
1552
+ // src/templates/web.ts
1553
+ function webTemplate() {
1554
+ return {
1555
+ directives: [
1556
+ {
1557
+ content: "# TypeScript Conventions\n\n- Use strict TypeScript \u2014 avoid `any`, prefer explicit types\n- Prefer `const` over `let`; never use `var`\n- Use ES modules (`import`/`export`), not CommonJS\n- Prefer async/await over raw promises\n- Use template literals over string concatenation\n- Destructure objects and arrays where it improves readability",
1558
+ scope: "project",
1559
+ alwaysApply: true,
1560
+ description: "typescript-conventions"
1561
+ },
1562
+ {
1563
+ content: "# Testing Rules\n\n- Write tests for all new functionality\n- Use descriptive test names that explain the expected behavior\n- Prefer unit tests; use integration tests for cross-boundary flows\n- Mock external dependencies, not internal modules\n- Each test should test one thing",
1564
+ scope: "project",
1565
+ alwaysApply: true,
1566
+ description: "testing"
1567
+ },
1568
+ {
1569
+ content: "# Security Guidelines\n\n- Never hardcode secrets, tokens, or credentials\n- Validate and sanitize all user input\n- Use parameterized queries for database operations\n- Escape output to prevent XSS\n- Follow the principle of least privilege for permissions and access",
1570
+ scope: "project",
1571
+ alwaysApply: true,
1572
+ description: "security"
1573
+ }
1574
+ ],
1575
+ skills: [],
1576
+ agents: [],
1577
+ toolServers: [
1578
+ {
1579
+ name: "filesystem",
1580
+ transport: "stdio",
1581
+ command: "npx",
1582
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
1583
+ scope: "project"
1584
+ }
1585
+ ],
1586
+ hooks: [],
1587
+ permissions: [
1588
+ { tool: "Bash", pattern: "npm *", decision: "allow", scope: "project" },
1589
+ { tool: "Bash", pattern: "npx *", decision: "allow", scope: "project" }
1590
+ ],
1591
+ settings: [],
1592
+ ignorePatterns: [
1593
+ { pattern: "node_modules/**", scope: "project" },
1594
+ { pattern: "dist/**", scope: "project" },
1595
+ { pattern: "build/**", scope: "project" },
1596
+ { pattern: ".env", scope: "project" },
1597
+ { pattern: ".env.*", scope: "project" },
1598
+ { pattern: "*.min.js", scope: "project" }
1599
+ ]
1600
+ };
1601
+ }
1602
+
1603
+ // src/templates/index.ts
1604
+ var TEMPLATES = [
1605
+ {
1606
+ name: "minimal",
1607
+ label: "Minimal",
1608
+ description: "Conventions + common ignore patterns",
1609
+ factory: minimalTemplate
1610
+ },
1611
+ {
1612
+ name: "web",
1613
+ label: "Web / TypeScript",
1614
+ description: "TypeScript conventions, testing, security, MCP server",
1615
+ factory: webTemplate
1616
+ },
1617
+ {
1618
+ name: "python",
1619
+ label: "Python",
1620
+ description: "Formatting, typing, docstrings, pytest conventions",
1621
+ factory: pythonTemplate
1622
+ },
1623
+ {
1624
+ name: "monorepo",
1625
+ label: "Monorepo",
1626
+ description: "Workspace-aware conventions, reviewer agent",
1627
+ factory: monorepoTemplate
1628
+ },
1629
+ {
1630
+ name: "blank",
1631
+ label: "Blank",
1632
+ description: "Empty config skeleton",
1633
+ factory: blankTemplate
1634
+ }
1635
+ ];
1636
+ function getTemplate(name) {
1637
+ const template = TEMPLATES.find((t) => t.name === name);
1638
+ if (!template) {
1639
+ throw new Error(`Unknown template: ${name}`);
1640
+ }
1641
+ return template.factory();
1642
+ }
1643
+ var TEMPLATE_NAMES = TEMPLATES.map((t) => t.name);
1644
+
1645
+ // src/commands/sync.ts
1646
+ var import_promises12 = require("fs/promises");
1647
+ var import_node_os3 = require("os");
1648
+ var import_node_path11 = require("path");
1649
+
1650
+ // src/emitters/agents.ts
1651
+ var agentsEmitter = {
1652
+ emit(config, target) {
1653
+ switch (target) {
1654
+ case "claude":
1655
+ return emitClaudeAgents(config.agents);
1656
+ case "cursor":
1657
+ return emitCursorAgents(config.agents);
1658
+ case "codex":
1659
+ return emitCodexAgents(config.agents);
1660
+ case "opencode":
1661
+ return emitOpenCodeAgents(config.agents);
1662
+ case "copilot":
1663
+ return emitCopilotAgents(config.agents);
1664
+ case "antigravity":
1665
+ return emitAntigravityAgents(config.agents);
1666
+ }
1667
+ }
1668
+ };
1669
+ function emitClaudeAgents(agents) {
1670
+ const files = [];
1671
+ for (const agent of agents) {
1672
+ files.push({
1673
+ path: `.claude/agents/${agent.name}.md`,
1674
+ content: `${agent.instructions}
1675
+ `
1676
+ });
1677
+ }
1678
+ return { files, warnings: [] };
1679
+ }
1680
+ function emitCursorAgents(agents) {
1681
+ const files = [];
1682
+ for (const agent of agents) {
1683
+ const frontmatter = [];
1684
+ if (agent.description) frontmatter.push(`description: ${agent.description}`);
1685
+ if (agent.model) frontmatter.push(`model: ${agent.model}`);
1686
+ if (agent.readonly) frontmatter.push(`readonly: true`);
1687
+ if (agent.tools?.length) frontmatter.push(`tools: [${agent.tools.join(", ")}]`);
1688
+ const header = frontmatter.length > 0 ? `---
1689
+ ${frontmatter.join("\n")}
1690
+ ---
1691
+
1692
+ ` : "";
1693
+ files.push({
1694
+ path: `.cursor/agents/${agent.name}.md`,
1695
+ content: `${header}${agent.instructions}
1696
+ `
1697
+ });
1698
+ }
1699
+ return { files, warnings: [] };
1700
+ }
1701
+ function emitCodexAgents(agents) {
1702
+ const files = [];
1703
+ const warnings = [];
1704
+ if (agents.length === 0) return { files, warnings };
1705
+ const sections = agents.map((a) => {
1706
+ const meta = [];
1707
+ if (a.model) meta.push(`Model: ${a.model}`);
1708
+ if (a.readonly) meta.push("Read-only access");
1709
+ if (a.tools?.length) meta.push(`Tools: ${a.tools.join(", ")}`);
1710
+ const metaBlock = meta.length > 0 ? `
1711
+
1712
+ > ${meta.join(" | ")}` : "";
1713
+ return `## Agent: ${a.name}
1714
+
1715
+ ${a.description ? `${a.description}
1716
+
1717
+ ` : ""}${a.instructions}${metaBlock}`;
1718
+ });
1719
+ files.push({
1720
+ path: "AGENTS.md",
1721
+ content: `# Agents
1722
+
1723
+ ${sections.join("\n\n---\n\n")}
1724
+ `
1725
+ });
1726
+ if (agents.some((a) => a.tools?.length)) {
1727
+ warnings.push(
1728
+ "Codex does not support per-agent tool restrictions \u2014 tools listed as notes only."
1729
+ );
1730
+ }
1731
+ return { files, warnings };
1732
+ }
1733
+ var COPILOT_TOOL_MAP = {
1734
+ Read: "read",
1735
+ Write: "edit",
1736
+ Edit: "edit",
1737
+ Bash: "execute",
1738
+ Shell: "execute",
1739
+ Search: "search",
1740
+ WebSearch: "web",
1741
+ WebFetch: "web"
1742
+ };
1743
+ function emitCopilotAgents(agents) {
1744
+ const files = [];
1745
+ const warnings = [];
1746
+ for (const agent of agents) {
1747
+ const frontmatter = [];
1748
+ frontmatter.push(`description: ${agent.description || agent.name}`);
1749
+ if (agent.readonly) {
1750
+ frontmatter.push(`tools: [read, search]`);
1751
+ } else if (agent.tools?.length) {
1752
+ const mapped = [
1753
+ ...new Set(
1754
+ agent.tools.map((t) => {
1755
+ const alias = COPILOT_TOOL_MAP[t];
1756
+ if (!alias) {
1757
+ warnings.push(
1758
+ `Unknown tool "${t}" for Copilot agent "${agent.name}" \u2014 passed through as-is.`
1759
+ );
1760
+ return t.toLowerCase();
1761
+ }
1762
+ return alias;
1763
+ })
1764
+ )
1765
+ ];
1766
+ frontmatter.push(`tools: [${mapped.join(", ")}]`);
1767
+ }
1768
+ if (agent.model) {
1769
+ warnings.push(
1770
+ `Copilot agents do not support model override in file config \u2014 model "${agent.model}" on agent "${agent.name}" is ignored.`
1771
+ );
1772
+ }
1773
+ const header = `---
1774
+ ${frontmatter.join("\n")}
1775
+ ---
1776
+
1777
+ `;
1778
+ files.push({
1779
+ path: `.github/agents/${agent.name}.agent.md`,
1780
+ content: `${header}${agent.instructions}
1781
+ `
1782
+ });
1783
+ }
1784
+ return { files, warnings };
1785
+ }
1786
+ function emitOpenCodeAgents(agents) {
1787
+ const files = [];
1788
+ for (const agent of agents) {
1789
+ const frontmatter = [];
1790
+ if (agent.description) frontmatter.push(`description: ${agent.description}`);
1791
+ if (agent.mode) frontmatter.push(`mode: ${agent.mode}`);
1792
+ if (agent.model) frontmatter.push(`model: ${agent.model}`);
1793
+ if (agent.temperature !== void 0) frontmatter.push(`temperature: ${agent.temperature}`);
1794
+ if (agent.topP !== void 0) frontmatter.push(`top_p: ${agent.topP}`);
1795
+ if (agent.steps !== void 0) frontmatter.push(`steps: ${agent.steps}`);
1796
+ if (agent.color) frontmatter.push(`color: ${agent.color}`);
1797
+ if (agent.hidden) frontmatter.push(`hidden: true`);
1798
+ if (agent.disabled) frontmatter.push(`disable: true`);
1799
+ if (agent.readonly) {
1800
+ frontmatter.push(`tools:`);
1801
+ frontmatter.push(` write: false`);
1802
+ frontmatter.push(` bash: false`);
1803
+ }
1804
+ const header = frontmatter.length > 0 ? `---
1805
+ ${frontmatter.join("\n")}
1806
+ ---
1807
+
1808
+ ` : "";
1809
+ files.push({
1810
+ path: `.opencode/agents/${agent.name}.md`,
1811
+ content: `${header}${agent.instructions}
1812
+ `
1813
+ });
1814
+ }
1815
+ return { files, warnings: [] };
1816
+ }
1817
+ function emitAntigravityAgents(agents) {
1818
+ const warnings = [];
1819
+ if (agents.length > 0) {
1820
+ warnings.push(
1821
+ "Antigravity does not support file-based agent configuration \u2014 agents are skipped."
1822
+ );
1823
+ }
1824
+ return { files: [], warnings };
1825
+ }
1826
+
1827
+ // src/emitters/directives.ts
1828
+ var directivesEmitter = {
1829
+ emit(config, target) {
1830
+ switch (target) {
1831
+ case "claude":
1832
+ return emitClaude(config.directives);
1833
+ case "cursor":
1834
+ return emitCursor(config.directives);
1835
+ case "codex":
1836
+ return emitCodex(config.directives);
1837
+ case "opencode":
1838
+ return emitOpenCode(config.directives);
1839
+ case "copilot":
1840
+ return emitCopilot(config.directives);
1841
+ case "antigravity":
1842
+ return emitAntigravity(config.directives);
1843
+ }
1844
+ }
1845
+ };
1846
+ function emitClaude(directives) {
1847
+ const files = [];
1848
+ const warnings = [];
1849
+ const alwaysApply = directives.filter((d) => d.alwaysApply && !d.appliesTo?.length);
1850
+ const scoped = directives.filter((d) => !d.alwaysApply || d.appliesTo?.length);
1851
+ if (alwaysApply.length > 0) {
1852
+ const sections = alwaysApply.map((d) => d.content);
1853
+ files.push({
1854
+ path: "CLAUDE.md",
1855
+ content: `${sections.join("\n\n---\n\n")}
1856
+ `
1857
+ });
1858
+ }
1859
+ for (const directive of scoped) {
1860
+ const name = slugify2(directive.description || "rule");
1861
+ let content = directive.content;
1862
+ if (directive.appliesTo?.length) {
1863
+ const globs = directive.appliesTo.join(", ");
1864
+ content = `<!-- applies to: ${globs} -->
1865
+
1866
+ ${content}`;
1867
+ }
1868
+ files.push({
1869
+ path: `.claude/rules/${name}.md`,
1870
+ content: `${content}
1871
+ `
1872
+ });
1873
+ }
1874
+ return { files, warnings };
1875
+ }
1876
+ function emitCursor(directives) {
1877
+ const files = [];
1878
+ const warnings = [];
1879
+ for (const directive of directives) {
1880
+ const name = slugify2(directive.description || "rule");
1881
+ const frontmatter = [];
1882
+ if (directive.description) {
1883
+ frontmatter.push(`description: ${directive.description}`);
1884
+ }
1885
+ if (directive.appliesTo?.length) {
1886
+ frontmatter.push(`globs: ${directive.appliesTo.join(", ")}`);
1887
+ }
1888
+ frontmatter.push(`alwaysApply: ${directive.alwaysApply}`);
1889
+ const header = `---
1890
+ ${frontmatter.join("\n")}
1891
+ ---`;
1892
+ files.push({
1893
+ path: `.cursor/rules/${name}.mdc`,
1894
+ content: `${header}
1895
+
1896
+ ${directive.content}
1897
+ `
1898
+ });
1899
+ }
1900
+ return { files, warnings };
1901
+ }
1902
+ function emitCodex(directives) {
1903
+ const files = [];
1904
+ const warnings = [];
1905
+ if (directives.length === 0) return { files, warnings };
1906
+ const sections = directives.map((d) => {
1907
+ const header = d.description ? `## ${d.description}` : "## Directive";
1908
+ const scopeNote = d.appliesTo?.length ? `
1909
+
1910
+ > Applies to: ${d.appliesTo.join(", ")}` : "";
1911
+ return `${header}${scopeNote}
1912
+
1913
+ ${d.content}`;
1914
+ });
1915
+ files.push({
1916
+ path: "AGENTS.md",
1917
+ content: `# Project Instructions
1918
+
1919
+ ${sections.join("\n\n---\n\n")}
1920
+ `
1921
+ });
1922
+ if (directives.some((d) => d.appliesTo?.length)) {
1923
+ warnings.push(
1924
+ "Codex AGENTS.md does not support file-scoped directives \u2014 appliesTo patterns are included as notes but not enforced."
1925
+ );
1926
+ }
1927
+ return { files, warnings };
1928
+ }
1929
+ function emitOpenCode(directives) {
1930
+ const files = [];
1931
+ const warnings = [];
1932
+ if (directives.length === 0) return { files, warnings };
1933
+ const paths = [];
1934
+ for (const directive of directives) {
1935
+ const name = slugify2(directive.description || "rule");
1936
+ let content = directive.content;
1937
+ if (directive.appliesTo?.length) {
1938
+ content = `<!-- applies to: ${directive.appliesTo.join(", ")} -->
1939
+
1940
+ ${content}`;
1941
+ }
1942
+ const relPath = `.opencode/instructions/${name}.md`;
1943
+ files.push({ path: relPath, content: `${content}
1944
+ ` });
1945
+ paths.push(relPath);
1946
+ }
1947
+ files.push({
1948
+ path: "opencode.json",
1949
+ content: `${JSON.stringify({ instructions: paths }, null, 2)}
1950
+ `
1951
+ });
1952
+ if (directives.some((d) => d.appliesTo?.length)) {
1953
+ warnings.push(
1954
+ "OpenCode does not support file-scoped directives \u2014 appliesTo patterns are included as comments but not enforced."
1955
+ );
1956
+ }
1957
+ return { files, warnings };
1958
+ }
1959
+ function emitCopilot(directives) {
1960
+ const files = [];
1961
+ const warnings = [];
1962
+ const repoWide = directives.filter((d) => d.alwaysApply && !d.appliesTo?.length);
1963
+ const scoped = directives.filter((d) => !d.alwaysApply || (d.appliesTo?.length ?? 0) > 0);
1964
+ if (repoWide.length > 0) {
1965
+ const sections = repoWide.map((d) => d.content);
1966
+ files.push({
1967
+ path: ".github/copilot-instructions.md",
1968
+ content: `${sections.join("\n\n---\n\n")}
1969
+ `
1970
+ });
1971
+ }
1972
+ for (const directive of scoped) {
1973
+ const name = slugify2(directive.description || "rule");
1974
+ let content = directive.content;
1975
+ if (directive.appliesTo?.length) {
1976
+ const applyTo = directive.appliesTo.join(",");
1977
+ content = `---
1978
+ applyTo: "${applyTo}"
1979
+ ---
1980
+
1981
+ ${content}`;
1982
+ }
1983
+ files.push({
1984
+ path: `.github/instructions/${name}.instructions.md`,
1985
+ content: `${content}
1986
+ `
1987
+ });
1988
+ }
1989
+ return { files, warnings };
1990
+ }
1991
+ function emitAntigravity(directives) {
1992
+ const files = [];
1993
+ const warnings = [];
1994
+ for (const directive of directives) {
1995
+ const name = slugify2(directive.description || "rule");
1996
+ const frontmatter = [];
1997
+ if (directive.description) {
1998
+ frontmatter.push(`description: ${directive.description}`);
1999
+ }
2000
+ if (directive.alwaysApply && directive.appliesTo?.length) {
2001
+ frontmatter.push(`globs:`);
2002
+ for (const glob of directive.appliesTo) {
2003
+ frontmatter.push(` - "${glob}"`);
2004
+ }
2005
+ } else if (directive.alwaysApply) {
2006
+ frontmatter.push(`alwaysApply: true`);
2007
+ } else if (!directive.appliesTo?.length) {
2008
+ frontmatter.push(`alwaysApply: false`);
2009
+ } else {
2010
+ frontmatter.push(`globs:`);
2011
+ for (const glob of directive.appliesTo) {
2012
+ frontmatter.push(` - "${glob}"`);
2013
+ }
2014
+ }
2015
+ const header = `---
2016
+ ${frontmatter.join("\n")}
2017
+ ---`;
2018
+ files.push({
2019
+ path: `.agent/rules/${name}.md`,
2020
+ content: `${header}
2021
+
2022
+ ${directive.content}
2023
+ `
2024
+ });
2025
+ }
2026
+ return { files, warnings };
2027
+ }
2028
+ function slugify2(str) {
2029
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2030
+ }
2031
+
2032
+ // src/emitters/hooks.ts
2033
+ var hooksEmitter = {
2034
+ emit(config, target) {
2035
+ switch (target) {
2036
+ case "claude":
2037
+ return emitClaude2(config.hooks, config.ignorePatterns);
2038
+ case "cursor":
2039
+ return emitCursor2(config.hooks, config.ignorePatterns);
2040
+ case "codex":
2041
+ return emitCodex2(config.hooks, config.ignorePatterns);
2042
+ case "opencode":
2043
+ return emitOpenCode2(config.hooks, config.ignorePatterns);
2044
+ case "copilot":
2045
+ return emitCopilot2(config.hooks, config.ignorePatterns);
2046
+ case "antigravity":
2047
+ return emitAntigravity2(config.hooks, config.ignorePatterns);
2048
+ }
2049
+ }
2050
+ };
2051
+ function emitClaude2(hooks, ignorePatterns) {
2052
+ const files = [];
2053
+ const warnings = [];
2054
+ if (hooks.length === 0 && ignorePatterns.length === 0) return { files, warnings };
2055
+ const settings = {};
2056
+ if (hooks.length > 0) {
2057
+ const hooksObj = {};
2058
+ for (const hook of hooks) {
2059
+ if (!hooksObj[hook.event]) hooksObj[hook.event] = [];
2060
+ const entry = { command: hook.handler };
2061
+ if (hook.matcher) entry.matcher = hook.matcher;
2062
+ hooksObj[hook.event].push(entry);
2063
+ }
2064
+ settings.hooks = hooksObj;
2065
+ }
2066
+ if (ignorePatterns.length > 0) {
2067
+ const deny = ignorePatterns.flatMap((p) => [`Read(${p.pattern})`, `Edit(${p.pattern})`]);
2068
+ settings.permissions = { deny };
2069
+ }
2070
+ files.push({
2071
+ path: ".claude/settings.json",
2072
+ content: `${JSON.stringify(settings, null, 2)}
2073
+ `
2074
+ });
2075
+ return { files, warnings };
2076
+ }
2077
+ function emitCursor2(hooks, ignorePatterns) {
2078
+ const files = [];
2079
+ const warnings = [];
2080
+ if (ignorePatterns.length > 0) {
2081
+ const lines = [
2082
+ "# Generated by ai-dot",
2083
+ "# File exclusion rules for Cursor AI",
2084
+ "",
2085
+ ...ignorePatterns.map((p) => p.pattern),
2086
+ ""
2087
+ ];
2088
+ files.push({
2089
+ path: ".cursorignore",
2090
+ content: lines.join("\n")
2091
+ });
2092
+ }
2093
+ if (hooks.length > 0) {
2094
+ warnings.push(
2095
+ "Cursor hook support is limited \u2014 hooks are not directly emitted to Cursor config."
2096
+ );
2097
+ }
2098
+ return { files, warnings };
2099
+ }
2100
+ function emitCodex2(hooks, ignorePatterns) {
2101
+ const files = [];
2102
+ const warnings = [];
2103
+ if (ignorePatterns.length > 0) {
2104
+ const paths = ignorePatterns.map((p) => `"${p.pattern}"`).join(", ");
2105
+ files.push({
2106
+ path: ".codex/config.toml",
2107
+ content: `protected_paths = [${paths}]
2108
+ `
2109
+ });
2110
+ }
2111
+ if (hooks.length > 0) {
2112
+ warnings.push("Codex does not support hooks \u2014 hook configuration is skipped.");
2113
+ }
2114
+ return { files, warnings };
2115
+ }
2116
+ function emitOpenCode2(hooks, ignorePatterns) {
2117
+ const files = [];
2118
+ const warnings = [];
2119
+ if (ignorePatterns.length > 0) {
2120
+ const ignore = ignorePatterns.map((p) => p.pattern);
2121
+ files.push({
2122
+ path: "opencode.json",
2123
+ content: `${JSON.stringify({ watcher: { ignore } }, null, 2)}
2124
+ `
2125
+ });
2126
+ }
2127
+ if (hooks.length > 0) {
2128
+ warnings.push("OpenCode does not support lifecycle hooks \u2014 hook configuration is skipped.");
2129
+ }
2130
+ return { files, warnings };
2131
+ }
2132
+ var COPILOT_HOOK_EVENTS = /* @__PURE__ */ new Set([
2133
+ "preToolUse",
2134
+ "postToolUse",
2135
+ "sessionStart",
2136
+ "sessionEnd",
2137
+ "userPromptSubmitted",
2138
+ "agentStop",
2139
+ "subagentStop",
2140
+ "errorOccurred"
2141
+ ]);
2142
+ function emitCopilot2(hooks, ignorePatterns) {
2143
+ const files = [];
2144
+ const warnings = [];
2145
+ if (hooks.length > 0) {
2146
+ const hooksObj = {};
2147
+ for (const hook of hooks) {
2148
+ if (!COPILOT_HOOK_EVENTS.has(hook.event)) {
2149
+ warnings.push(`Hook event "${hook.event}" is not supported by Copilot \u2014 skipped.`);
2150
+ continue;
2151
+ }
2152
+ if (!hooksObj[hook.event]) hooksObj[hook.event] = [];
2153
+ const entry = { command: hook.handler };
2154
+ if (hook.matcher) entry.matcher = hook.matcher;
2155
+ hooksObj[hook.event].push(entry);
2156
+ }
2157
+ if (Object.keys(hooksObj).length > 0) {
2158
+ files.push({
2159
+ path: ".github/hooks/ai-dot.hooks.json",
2160
+ content: `${JSON.stringify({ hooks: hooksObj }, null, 2)}
2161
+ `
2162
+ });
2163
+ }
2164
+ }
2165
+ if (ignorePatterns.length > 0) {
2166
+ warnings.push(
2167
+ "Copilot does not support file-based ignore patterns \u2014 ignore configuration is skipped."
2168
+ );
2169
+ }
2170
+ return { files, warnings };
2171
+ }
2172
+ function emitAntigravity2(hooks, ignorePatterns) {
2173
+ const warnings = [];
2174
+ if (hooks.length > 0) {
2175
+ warnings.push("Antigravity does not support hooks \u2014 hook configuration is skipped.");
2176
+ }
2177
+ if (ignorePatterns.length > 0) {
2178
+ warnings.push(
2179
+ "Antigravity does not support file-based ignore patterns \u2014 ignore configuration is skipped."
2180
+ );
2181
+ }
2182
+ return { files: [], warnings };
2183
+ }
2184
+
2185
+ // src/emitters/mcp.ts
2186
+ var mcpEmitter = {
2187
+ emit(config, target) {
2188
+ switch (target) {
2189
+ case "claude":
2190
+ return emitClaudeMcp(config.toolServers);
2191
+ case "cursor":
2192
+ return emitCursorMcp(config.toolServers);
2193
+ case "codex":
2194
+ return emitCodexMcp(config.toolServers);
2195
+ case "opencode":
2196
+ return emitOpenCodeMcp(config.toolServers);
2197
+ case "copilot":
2198
+ return emitCopilotMcp(config.toolServers);
2199
+ case "antigravity":
2200
+ return emitAntigravityMcp(config.toolServers);
2201
+ }
2202
+ }
2203
+ };
2204
+ function emitClaudeMcp(servers) {
2205
+ const files = [];
2206
+ const warnings = [];
2207
+ if (servers.length === 0) return { files, warnings };
2208
+ const mcpServers = {};
2209
+ for (const server of servers) {
2210
+ mcpServers[server.name] = buildMcpEntry(server);
2211
+ }
2212
+ files.push({
2213
+ path: ".mcp.json",
2214
+ content: `${JSON.stringify({ mcpServers }, null, 2)}
2215
+ `
2216
+ });
2217
+ return { files, warnings };
2218
+ }
2219
+ function emitCursorMcp(servers) {
2220
+ const files = [];
2221
+ const warnings = [];
2222
+ if (servers.length === 0) return { files, warnings };
2223
+ const mcpServers = {};
2224
+ for (const server of servers) {
2225
+ mcpServers[server.name] = buildMcpEntry(server);
2226
+ }
2227
+ files.push({
2228
+ path: ".cursor/mcp.json",
2229
+ content: `${JSON.stringify({ mcpServers }, null, 2)}
2230
+ `
2231
+ });
2232
+ return { files, warnings };
2233
+ }
2234
+ function emitCodexMcp(servers) {
2235
+ const files = [];
2236
+ const warnings = [];
2237
+ if (servers.length === 0) return { files, warnings };
2238
+ const sections = [];
2239
+ for (const server of servers) {
2240
+ sections.push(buildTomlSection(server));
2241
+ }
2242
+ files.push({
2243
+ path: ".codex/config.toml",
2244
+ content: `${sections.join("\n\n")}
2245
+ `
2246
+ });
2247
+ if (servers.some((s) => s.enabledTools?.length || s.disabledTools?.length)) {
2248
+ warnings.push(
2249
+ "Codex config.toml does not support enabledTools/disabledTools filtering for MCP servers."
2250
+ );
2251
+ }
2252
+ return { files, warnings };
2253
+ }
2254
+ function emitCopilotMcp(servers) {
2255
+ const files = [];
2256
+ const warnings = [];
2257
+ if (servers.length === 0) return { files, warnings };
2258
+ const mcpServers = {};
2259
+ for (const server of servers) {
2260
+ const entry = buildMcpEntry(server);
2261
+ if (!entry.type) {
2262
+ entry.type = "stdio";
2263
+ }
2264
+ mcpServers[server.name] = entry;
2265
+ }
2266
+ files.push({
2267
+ path: ".vscode/mcp.json",
2268
+ content: `${JSON.stringify({ mcpServers }, null, 2)}
2269
+ `
2270
+ });
2271
+ warnings.push(
2272
+ "Copilot coding agent MCP must be configured in GitHub repo settings separately \u2014 .vscode/mcp.json is for VS Code Copilot Chat only."
2273
+ );
2274
+ return { files, warnings };
2275
+ }
2276
+ function buildMcpEntry(server) {
2277
+ const entry = {};
2278
+ if (server.transport === "stdio") {
2279
+ entry.command = server.command;
2280
+ if (server.args?.length) entry.args = server.args;
2281
+ } else {
2282
+ entry.type = server.transport;
2283
+ entry.url = server.url;
2284
+ }
2285
+ if (server.env && Object.keys(server.env).length > 0) {
2286
+ entry.env = server.env;
2287
+ }
2288
+ return entry;
2289
+ }
2290
+ function buildTomlSection(server) {
2291
+ const lines = [`[mcp_servers.${server.name}]`];
2292
+ if (server.transport === "stdio") {
2293
+ lines.push(`type = "stdio"`);
2294
+ if (server.command) lines.push(`command = ${tomlString(server.command)}`);
2295
+ if (server.args?.length) {
2296
+ lines.push(`args = [${server.args.map(tomlString).join(", ")}]`);
2297
+ }
2298
+ } else {
2299
+ lines.push(`type = ${tomlString(server.transport)}`);
2300
+ if (server.url) lines.push(`url = ${tomlString(server.url)}`);
2301
+ }
2302
+ if (server.env && Object.keys(server.env).length > 0) {
2303
+ lines.push("");
2304
+ lines.push(`[mcp_servers.${server.name}.env]`);
2305
+ for (const [key, value] of Object.entries(server.env)) {
2306
+ lines.push(`${key} = ${tomlString(value)}`);
2307
+ }
2308
+ }
2309
+ return lines.join("\n");
2310
+ }
2311
+ function emitOpenCodeMcp(servers) {
2312
+ const files = [];
2313
+ const warnings = [];
2314
+ if (servers.length === 0) return { files, warnings };
2315
+ const mcp = {};
2316
+ for (const server of servers) {
2317
+ const entry = {};
2318
+ if (server.transport === "stdio") {
2319
+ entry.type = "stdio";
2320
+ entry.command = server.command;
2321
+ if (server.args?.length) entry.args = server.args;
2322
+ } else {
2323
+ entry.type = "remote";
2324
+ entry.url = server.url;
2325
+ }
2326
+ if (server.env && Object.keys(server.env).length > 0) {
2327
+ entry.env = server.env;
2328
+ }
2329
+ mcp[server.name] = entry;
2330
+ }
2331
+ files.push({
2332
+ path: "opencode.json",
2333
+ content: `${JSON.stringify({ mcp }, null, 2)}
2334
+ `
2335
+ });
2336
+ if (servers.some((s) => s.enabledTools?.length || s.disabledTools?.length)) {
2337
+ warnings.push(
2338
+ "OpenCode does not support enabledTools/disabledTools filtering for MCP servers."
2339
+ );
2340
+ }
2341
+ return { files, warnings };
2342
+ }
2343
+ function emitAntigravityMcp(servers) {
2344
+ const files = [];
2345
+ const warnings = [];
2346
+ if (servers.length === 0) return { files, warnings };
2347
+ const mcpServers = {};
2348
+ for (const server of servers) {
2349
+ mcpServers[server.name] = buildMcpEntry(server);
2350
+ }
2351
+ files.push({
2352
+ path: "mcp_config.json",
2353
+ content: `${JSON.stringify({ mcpServers }, null, 2)}
2354
+ `
2355
+ });
2356
+ if (servers.some((s) => s.enabledTools?.length || s.disabledTools?.length)) {
2357
+ warnings.push(
2358
+ "Antigravity does not support enabledTools/disabledTools filtering for MCP servers."
2359
+ );
2360
+ }
2361
+ return { files, warnings };
2362
+ }
2363
+ function tomlString(s) {
2364
+ return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
2365
+ }
2366
+
2367
+ // src/emitters/permissions.ts
2368
+ var permissionsEmitter = {
2369
+ emit(config, target) {
2370
+ switch (target) {
2371
+ case "claude":
2372
+ return emitClaude3(config.permissions, config.settings);
2373
+ case "cursor":
2374
+ return emitCursor3(config.permissions, config.settings);
2375
+ case "codex":
2376
+ return emitCodex3(config.permissions, config.settings);
2377
+ case "opencode":
2378
+ return emitOpenCode3(config.permissions, config.settings);
2379
+ case "copilot":
2380
+ return emitCopilot3(config.permissions, config.settings);
2381
+ case "antigravity":
2382
+ return emitAntigravity3(config.permissions, config.settings);
2383
+ }
2384
+ }
2385
+ };
2386
+ function emitClaude3(permissions, settings) {
2387
+ const files = [];
2388
+ const warnings = [];
2389
+ if (permissions.length === 0 && settings.length === 0) return { files, warnings };
2390
+ const settingsObj = {};
2391
+ if (permissions.length > 0) {
2392
+ const allow = [];
2393
+ const deny = [];
2394
+ for (const perm of permissions) {
2395
+ const rule = perm.pattern ? `${perm.tool}(${perm.pattern})` : perm.tool;
2396
+ if (perm.decision === "allow") allow.push(rule);
2397
+ else if (perm.decision === "deny") deny.push(rule);
2398
+ }
2399
+ const permsObj = {};
2400
+ if (allow.length > 0) permsObj.allow = allow;
2401
+ if (deny.length > 0) permsObj.deny = deny;
2402
+ settingsObj.permissions = permsObj;
2403
+ if (permissions.some((p) => p.decision === "ask")) {
2404
+ warnings.push(
2405
+ 'Claude Code does not support "ask" permission decision \u2014 these rules are omitted.'
2406
+ );
2407
+ }
2408
+ }
2409
+ for (const setting of settings) {
2410
+ settingsObj[setting.key] = setting.value;
2411
+ }
2412
+ files.push({
2413
+ path: ".claude/settings.json",
2414
+ content: `${JSON.stringify(settingsObj, null, 2)}
2415
+ `
2416
+ });
2417
+ return { files, warnings };
2418
+ }
2419
+ function emitCursor3(permissions, settings) {
2420
+ const files = [];
2421
+ const warnings = [];
2422
+ if (permissions.length === 0 && settings.length === 0) return { files, warnings };
2423
+ const cliConfig = {};
2424
+ if (permissions.length > 0) {
2425
+ const allow = [];
2426
+ const deny = [];
2427
+ for (const perm of permissions) {
2428
+ const rule = perm.pattern ? `${perm.tool}(${perm.pattern})` : perm.tool;
2429
+ if (perm.decision === "allow") allow.push(rule);
2430
+ else if (perm.decision === "deny") deny.push(rule);
2431
+ }
2432
+ const permsObj = {};
2433
+ if (allow.length > 0) permsObj.allow = allow;
2434
+ if (deny.length > 0) permsObj.deny = deny;
2435
+ cliConfig.permissions = permsObj;
2436
+ }
2437
+ for (const setting of settings) {
2438
+ cliConfig[setting.key] = setting.value;
2439
+ }
2440
+ files.push({
2441
+ path: ".cursor/cli.json",
2442
+ content: `${JSON.stringify(cliConfig, null, 2)}
2443
+ `
2444
+ });
2445
+ return { files, warnings };
2446
+ }
2447
+ function emitCodex3(permissions, settings) {
2448
+ const files = [];
2449
+ const warnings = [];
2450
+ if (permissions.length === 0 && settings.length === 0) return { files, warnings };
2451
+ const lines = [];
2452
+ if (permissions.length > 0) {
2453
+ const hasAllow = permissions.some((p) => p.decision === "allow");
2454
+ const hasDeny = permissions.some((p) => p.decision === "deny");
2455
+ if (hasAllow && !hasDeny) {
2456
+ lines.push(`approval_policy = "unless-allowed"`);
2457
+ } else if (hasDeny) {
2458
+ lines.push(`approval_policy = "unless-allowed"`);
2459
+ } else {
2460
+ lines.push(`approval_policy = "on-failure"`);
2461
+ }
2462
+ warnings.push(
2463
+ "Codex permissions are lossy \u2014 per-tool/pattern permissions are mapped to a single approval_policy."
2464
+ );
2465
+ }
2466
+ for (const setting of settings) {
2467
+ const value = typeof setting.value === "string" ? `"${setting.value}"` : String(setting.value);
2468
+ lines.push(`${setting.key} = ${value}`);
2469
+ }
2470
+ files.push({
2471
+ path: ".codex/config.toml",
2472
+ content: `${lines.join("\n")}
2473
+ `
2474
+ });
2475
+ return { files, warnings };
2476
+ }
2477
+ function emitOpenCode3(permissions, settings) {
2478
+ const files = [];
2479
+ const warnings = [];
2480
+ if (permissions.length === 0 && settings.length === 0) return { files, warnings };
2481
+ const config = {};
2482
+ if (permissions.length > 0) {
2483
+ const permission = {};
2484
+ for (const perm of permissions) {
2485
+ if (perm.pattern) {
2486
+ const toolKey = perm.tool.toLowerCase();
2487
+ if (!permission[toolKey] || typeof permission[toolKey] !== "object") {
2488
+ permission[toolKey] = {};
2489
+ }
2490
+ permission[toolKey][perm.pattern] = perm.decision;
2491
+ } else {
2492
+ permission[perm.tool.toLowerCase()] = perm.decision;
2493
+ }
2494
+ }
2495
+ config.permission = permission;
2496
+ }
2497
+ for (const setting of settings) {
2498
+ config[setting.key] = setting.value;
2499
+ }
2500
+ files.push({
2501
+ path: "opencode.json",
2502
+ content: `${JSON.stringify(config, null, 2)}
2503
+ `
2504
+ });
2505
+ return { files, warnings };
2506
+ }
2507
+ function emitCopilot3(permissions, settings) {
2508
+ const warnings = [];
2509
+ if (permissions.length > 0) {
2510
+ warnings.push(
2511
+ "Copilot does not support file-based permission configuration \u2014 permissions are skipped."
2512
+ );
2513
+ }
2514
+ if (settings.length > 0) {
2515
+ warnings.push(
2516
+ "Copilot does not support file-based settings configuration \u2014 settings are skipped."
2517
+ );
2518
+ }
2519
+ return { files: [], warnings };
2520
+ }
2521
+ function emitAntigravity3(permissions, settings) {
2522
+ const warnings = [];
2523
+ if (permissions.length > 0) {
2524
+ warnings.push(
2525
+ "Antigravity does not support file-based permission configuration \u2014 permissions are skipped."
2526
+ );
2527
+ }
2528
+ if (settings.length > 0) {
2529
+ warnings.push(
2530
+ "Antigravity does not support file-based settings configuration \u2014 settings are skipped."
2531
+ );
2532
+ }
2533
+ return { files: [], warnings };
2534
+ }
2535
+
2536
+ // src/emitters/skills.ts
2537
+ var SKILLS_DIRS = {
2538
+ claude: ".claude/skills",
2539
+ cursor: ".cursor/skills",
2540
+ codex: ".codex/skills",
2541
+ opencode: ".opencode/skills",
2542
+ copilot: ".github/skills",
2543
+ antigravity: ".agent/skills"
2544
+ };
2545
+ var skillsEmitter = {
2546
+ emit(config, target) {
2547
+ const files = [];
2548
+ const warnings = [];
2549
+ const baseDir = SKILLS_DIRS[target];
2550
+ for (const skill of config.skills) {
2551
+ files.push({
2552
+ path: `${baseDir}/${skill.name}/SKILL.md`,
2553
+ content: skill.content
2554
+ });
2555
+ }
2556
+ return { files, warnings };
2557
+ }
2558
+ };
2559
+
2560
+ // src/state.ts
2561
+ var import_node_crypto = require("crypto");
2562
+ var import_promises11 = require("fs/promises");
2563
+ var import_node_path10 = require("path");
2564
+ var STATE_FILE = ".ai/.state.json";
2565
+ function contentHash(content) {
2566
+ return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").slice(0, 16);
2567
+ }
2568
+ async function loadState(projectDir) {
2569
+ try {
2570
+ const raw = await (0, import_promises11.readFile)((0, import_node_path10.join)(projectDir, STATE_FILE), "utf-8");
2571
+ return JSON.parse(raw);
2572
+ } catch {
2573
+ return null;
2574
+ }
2575
+ }
2576
+ async function saveState(projectDir, files) {
2577
+ const state = {
2578
+ lastSync: (/* @__PURE__ */ new Date()).toISOString(),
2579
+ files: {}
2580
+ };
2581
+ for (const file of files) {
2582
+ state.files[file.path] = contentHash(file.content);
2583
+ }
2584
+ await (0, import_promises11.writeFile)((0, import_node_path10.join)(projectDir, STATE_FILE), `${JSON.stringify(state, null, 2)}
2585
+ `, "utf-8");
2586
+ }
2587
+ async function diffFiles(projectDir, generated, state) {
2588
+ const entries = [];
2589
+ const seenPaths = /* @__PURE__ */ new Set();
2590
+ for (const file of generated) {
2591
+ seenPaths.add(file.path);
2592
+ const generatedHash = contentHash(file.content);
2593
+ let diskContent = null;
2594
+ try {
2595
+ diskContent = await (0, import_promises11.readFile)((0, import_node_path10.join)(projectDir, file.path), "utf-8");
2596
+ } catch {
2597
+ }
2598
+ if (diskContent === null) {
2599
+ entries.push({ path: file.path, status: "new" });
2600
+ continue;
2601
+ }
2602
+ const diskHash = contentHash(diskContent);
2603
+ const lastKnownHash = state?.files[file.path];
2604
+ if (diskHash === generatedHash) {
2605
+ entries.push({ path: file.path, status: "up-to-date" });
2606
+ } else if (!lastKnownHash) {
2607
+ entries.push({ path: file.path, status: "conflict" });
2608
+ } else if (diskHash === lastKnownHash) {
2609
+ entries.push({ path: file.path, status: "modified" });
2610
+ } else {
2611
+ entries.push({ path: file.path, status: "conflict" });
2612
+ }
2613
+ }
2614
+ if (state) {
2615
+ for (const path of Object.keys(state.files)) {
2616
+ if (!seenPaths.has(path)) {
2617
+ entries.push({ path, status: "orphaned" });
2618
+ }
2619
+ }
2620
+ }
2621
+ return entries;
2622
+ }
2623
+
2624
+ // src/commands/sync.ts
2625
+ var USER_OUTPUT_DIRS = {
2626
+ claude: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".claude"),
2627
+ cursor: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".cursor"),
2628
+ codex: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".codex"),
2629
+ opencode: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".config", "opencode"),
2630
+ copilot: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".copilot"),
2631
+ antigravity: (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".agent")
2632
+ };
2633
+ var EMITTERS = [
2634
+ skillsEmitter,
2635
+ directivesEmitter,
2636
+ mcpEmitter,
2637
+ agentsEmitter,
2638
+ permissionsEmitter,
2639
+ hooksEmitter
2640
+ ];
2641
+ async function runSync(projectDir, options) {
2642
+ const isUserScope = options.scope === "user";
2643
+ const label = isUserScope ? "~/.ai/" : ".ai/";
2644
+ console.log(`Loading config from ${label} ...`);
2645
+ const { config, errors: loadErrors } = isUserScope ? await loadProjectConfig((0, import_node_path11.join)((0, import_node_os3.homedir)(), ".ai"), "user") : await loadMergedConfig(projectDir);
2646
+ if (loadErrors.length > 0) {
2647
+ console.error("\x1B[31mConfig errors:\x1B[0m");
2648
+ for (const err of loadErrors) {
2649
+ console.error(` ${err.file}${err.line ? `:${err.line}` : ""}: ${err.message}`);
2650
+ }
2651
+ process.exitCode = 1;
2652
+ return;
2653
+ }
2654
+ const { errors: valErrors } = validateConfig(config);
2655
+ if (valErrors.length > 0) {
2656
+ console.error("\x1B[31mValidation errors:\x1B[0m");
2657
+ for (const err of valErrors) {
2658
+ console.error(` ${err.file}: ${err.message}`);
2659
+ }
2660
+ process.exitCode = 1;
2661
+ return;
2662
+ }
2663
+ const targets = options.targets.length > 0 ? options.targets : [...ALL_TARGETS];
2664
+ const perTarget = /* @__PURE__ */ new Map();
2665
+ for (const target of targets) {
2666
+ const targetFiles = [];
2667
+ const targetWarnings = [];
2668
+ for (const emitter of EMITTERS) {
2669
+ const result = emitter.emit(config, target);
2670
+ targetFiles.push(...result.files);
2671
+ targetWarnings.push(...result.warnings);
2672
+ }
2673
+ perTarget.set(target, { files: targetFiles, warnings: targetWarnings });
2674
+ }
2675
+ const allFiles = [...perTarget.values()].flatMap((r) => r.files);
2676
+ const allWarnings = [...perTarget.values()].flatMap((r) => r.warnings);
2677
+ const mergedFiles = mergeFiles(allFiles);
2678
+ if (allWarnings.length > 0) {
2679
+ console.log("\x1B[33mWarnings:\x1B[0m");
2680
+ for (const w of allWarnings) {
2681
+ console.log(` \u26A0 ${w}`);
2682
+ }
2683
+ console.log("");
2684
+ }
2685
+ if (!isUserScope && !options.force && !options.dryRun) {
2686
+ const state = await loadState(projectDir);
2687
+ const statuses = await diffFiles(projectDir, mergedFiles, state);
2688
+ const conflicts = statuses.filter((s) => s.status === "conflict");
2689
+ if (conflicts.length > 0) {
2690
+ console.error("\x1B[31mConflicts detected (files manually edited since last sync):\x1B[0m");
2691
+ for (const c of conflicts) {
2692
+ console.error(` ! ${c.path}`);
2693
+ }
2694
+ console.error("\nUse \x1B[1m--force\x1B[0m to overwrite, or manually resolve.");
2695
+ process.exitCode = 1;
2696
+ return;
2697
+ }
2698
+ }
2699
+ if (options.dryRun) {
2700
+ console.log("\x1B[36mDry run \u2014 files that would be written:\x1B[0m");
2701
+ for (const file of mergedFiles) {
2702
+ const outputPath = resolveOutputPath(file.path, isUserScope, projectDir, targets);
2703
+ console.log(` ${outputPath}`);
2704
+ }
2705
+ } else {
2706
+ let filesToWrite = mergedFiles;
2707
+ if (!options.yes && isTTY() && !isUserScope) {
2708
+ const state = await loadState(projectDir);
2709
+ const statuses = await diffFiles(projectDir, mergedFiles, state);
2710
+ const changeCount = statuses.filter(
2711
+ (s) => s.status !== "up-to-date" && s.status !== "orphaned"
2712
+ ).length;
2713
+ if (changeCount === 0) {
2714
+ console.log("\x1B[32m\u2713\x1B[0m Everything is up-to-date.");
2715
+ return;
2716
+ }
2717
+ printStatusTable(statuses, projectDir, isUserScope, targets);
2718
+ const proceed = cancelGuard(
2719
+ await (0, import_prompts.confirm)({
2720
+ message: `Apply ${changeCount} change${changeCount === 1 ? "" : "s"}?`,
2721
+ initialValue: true
2722
+ })
2723
+ );
2724
+ if (!proceed) {
2725
+ console.log("Sync cancelled.");
2726
+ return;
2727
+ }
2728
+ const changePaths = new Set(
2729
+ statuses.filter((s) => s.status !== "up-to-date").map((s) => s.path)
2730
+ );
2731
+ filesToWrite = mergedFiles.filter((f) => changePaths.has(f.path));
2732
+ }
2733
+ for (const file of filesToWrite) {
2734
+ const fullPath = resolveOutputPath(file.path, isUserScope, projectDir, targets);
2735
+ await (0, import_promises12.mkdir)((0, import_node_path11.dirname)(fullPath), { recursive: true });
2736
+ await (0, import_promises12.writeFile)(fullPath, file.content, "utf-8");
2737
+ }
2738
+ if (!isUserScope) {
2739
+ await saveState(projectDir, mergedFiles);
2740
+ }
2741
+ printSyncSummary(config, targets, perTarget, filesToWrite.length);
2742
+ }
2743
+ }
2744
+ var TARGET_LABELS = {
2745
+ claude: "Claude Code",
2746
+ cursor: "Cursor",
2747
+ codex: "Codex",
2748
+ opencode: "OpenCode",
2749
+ copilot: "GitHub Copilot",
2750
+ antigravity: "Antigravity"
2751
+ };
2752
+ function countEntities(config) {
2753
+ const counts = [];
2754
+ if (config.directives.length > 0)
2755
+ counts.push({ label: "directive", count: config.directives.length });
2756
+ if (config.skills.length > 0) counts.push({ label: "skill", count: config.skills.length });
2757
+ if (config.agents.length > 0) counts.push({ label: "agent", count: config.agents.length });
2758
+ if (config.toolServers.length > 0)
2759
+ counts.push({ label: "MCP server", count: config.toolServers.length });
2760
+ if (config.permissions.length > 0)
2761
+ counts.push({ label: "permission", count: config.permissions.length });
2762
+ if (config.hooks.length > 0) counts.push({ label: "hook", count: config.hooks.length });
2763
+ return counts;
2764
+ }
2765
+ function pluralize(count, singular) {
2766
+ return `${count} ${singular}${count === 1 ? "" : "s"}`;
2767
+ }
2768
+ function printSyncSummary(config, targets, perTarget, filesWritten) {
2769
+ const entities = countEntities(config);
2770
+ console.log(
2771
+ `\x1B[32m\u2713\x1B[0m Synced to ${targets.length} target${targets.length === 1 ? "" : "s"}`
2772
+ );
2773
+ console.log("");
2774
+ const maxLabelLen = Math.max(...targets.map((t) => TARGET_LABELS[t].length));
2775
+ for (const target of targets) {
2776
+ const label = TARGET_LABELS[target].padEnd(maxLabelLen);
2777
+ const result = perTarget.get(target);
2778
+ const warnings = result?.warnings ?? [];
2779
+ const targetEntities = entities.filter((e) => {
2780
+ const unsupported = warnings.some(
2781
+ (w) => w.toLowerCase().includes(e.label.toLowerCase()) && w.toLowerCase().includes("not support")
2782
+ );
2783
+ return !unsupported;
2784
+ }).map((e) => pluralize(e.count, e.label));
2785
+ const summary = targetEntities.length > 0 ? targetEntities.join(", ") : "no entities";
2786
+ const warningNotes = warnings.filter((w) => w.toLowerCase().includes("not support")).map((w) => `\x1B[33m\u26A0 ${w}\x1B[0m`);
2787
+ if (warningNotes.length > 0) {
2788
+ console.log(` ${label} \u2014 ${summary}`);
2789
+ for (const note of warningNotes) {
2790
+ console.log(` ${"".padEnd(maxLabelLen)} ${note}`);
2791
+ }
2792
+ } else {
2793
+ console.log(` ${label} \u2014 ${summary}`);
2794
+ }
2795
+ }
2796
+ console.log("");
2797
+ console.log(` ${filesWritten} file${filesWritten === 1 ? "" : "s"} written.`);
2798
+ }
2799
+ function resolveOutputPath(relativePath, isUserScope, projectDir, _targets) {
2800
+ if (!isUserScope) {
2801
+ return (0, import_node_path11.join)(projectDir, relativePath);
2802
+ }
2803
+ for (const [tool, userDir] of Object.entries(USER_OUTPUT_DIRS)) {
2804
+ const prefix = `.${tool}/`;
2805
+ if (relativePath.startsWith(prefix)) {
2806
+ return (0, import_node_path11.join)(userDir, relativePath.slice(prefix.length));
2807
+ }
2808
+ }
2809
+ const copilotPrefixes = [".github/", ".vscode/"];
2810
+ for (const prefix of copilotPrefixes) {
2811
+ if (relativePath.startsWith(prefix)) {
2812
+ return (0, import_node_path11.join)(USER_OUTPUT_DIRS.copilot, relativePath);
2813
+ }
2814
+ }
2815
+ if (relativePath.startsWith(".agent/")) {
2816
+ return (0, import_node_path11.join)(USER_OUTPUT_DIRS.antigravity, relativePath.slice(".agent/".length));
2817
+ }
2818
+ return (0, import_node_path11.join)((0, import_node_os3.homedir)(), relativePath);
2819
+ }
2820
+ function mergeFiles(files) {
2821
+ const byPath = /* @__PURE__ */ new Map();
2822
+ for (const file of files) {
2823
+ const existing = byPath.get(file.path) ?? [];
2824
+ existing.push(file);
2825
+ byPath.set(file.path, existing);
2826
+ }
2827
+ const merged = [];
2828
+ for (const [path, group] of byPath) {
2829
+ if (group.length === 1) {
2830
+ merged.push(group[0]);
2831
+ continue;
2832
+ }
2833
+ if (path.endsWith(".json")) {
2834
+ let result = {};
2835
+ for (const file of group) {
2836
+ const parsed = JSON.parse(file.content);
2837
+ result = deepMerge(result, parsed);
2838
+ }
2839
+ merged.push({ path, content: `${JSON.stringify(result, null, 2)}
2840
+ ` });
2841
+ } else if (path.endsWith(".toml")) {
2842
+ const combined = group.map((f) => f.content.trim()).join("\n\n");
2843
+ merged.push({ path, content: `${combined}
2844
+ ` });
2845
+ } else if (path.endsWith(".md")) {
2846
+ const combined = group.map((f) => f.content.trim()).join("\n\n---\n\n");
2847
+ merged.push({ path, content: `${combined}
2848
+ ` });
2849
+ } else {
2850
+ merged.push(group[group.length - 1]);
2851
+ }
2852
+ }
2853
+ return merged;
2854
+ }
2855
+ var STATUS_COLORS = {
2856
+ new: "\x1B[32m",
2857
+ modified: "\x1B[33m",
2858
+ "up-to-date": "\x1B[2m",
2859
+ conflict: "\x1B[31m",
2860
+ orphaned: "\x1B[2m"
2861
+ };
2862
+ function printStatusTable(statuses, projectDir, isUserScope, targets) {
2863
+ console.log("");
2864
+ for (const entry of statuses) {
2865
+ const color = STATUS_COLORS[entry.status] ?? "";
2866
+ const reset = "\x1B[0m";
2867
+ const outputPath = resolveOutputPath(entry.path, isUserScope, projectDir, targets);
2868
+ const suffix = entry.status === "up-to-date" ? " (skip)" : "";
2869
+ console.log(` ${color}${outputPath.padEnd(40)}${entry.status}${suffix}${reset}`);
2870
+ }
2871
+ console.log("");
2872
+ }
2873
+ function deepMerge(target, source) {
2874
+ const result = { ...target };
2875
+ for (const [key, value] of Object.entries(source)) {
2876
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
2877
+ result[key] = deepMerge(
2878
+ result[key],
2879
+ value
2880
+ );
2881
+ } else if (Array.isArray(value) && Array.isArray(result[key])) {
2882
+ result[key] = [...result[key], ...value];
2883
+ } else {
2884
+ result[key] = value;
2885
+ }
2886
+ }
2887
+ return result;
2888
+ }
2889
+
2890
+ // src/commands/init.ts
2891
+ async function runInit(projectDir, options) {
2892
+ if (isTTY() && !options?.template) {
2893
+ await interactiveInit(projectDir, options);
2894
+ } else {
2895
+ await nonInteractiveInit(projectDir, options);
2896
+ }
2897
+ }
2898
+ async function interactiveInit(projectDir, options) {
2899
+ (0, import_prompts.intro)("ai-dot \u2014 universal AI tool config");
2900
+ const aiDir = (0, import_node_path12.join)(projectDir, ".ai");
2901
+ if ((0, import_node_fs2.existsSync)(aiDir)) {
2902
+ const proceed = cancelGuard(
2903
+ await (0, import_prompts.confirm)({ message: ".ai/ directory already exists. Reinitialize?" })
2904
+ );
2905
+ if (!proceed) {
2906
+ (0, import_prompts.outro)("Keeping existing .ai/ directory.");
2907
+ return;
2908
+ }
2909
+ }
2910
+ const targets = cancelGuard(
2911
+ await (0, import_prompts.multiselect)({
2912
+ message: "Which AI tools do you use?",
2913
+ options: [
2914
+ { value: "claude", label: "Claude Code" },
2915
+ { value: "cursor", label: "Cursor" },
2916
+ { value: "codex", label: "Codex" }
2917
+ ],
2918
+ required: true
2919
+ })
2920
+ );
2921
+ const templateName = cancelGuard(
2922
+ await (0, import_prompts.select)({
2923
+ message: "Start from a template?",
2924
+ options: TEMPLATES.map((t) => ({
2925
+ value: t.name,
2926
+ label: t.label,
2927
+ hint: t.description
2928
+ }))
2929
+ })
2930
+ );
2931
+ const config = getTemplate(templateName);
2932
+ const written = await writeProjectConfig(aiDir, config);
2933
+ console.log(`
2934
+ \x1B[32m\u2713\x1B[0m Created .ai/ from "${templateName}" template`);
2935
+ for (const f of written) {
2936
+ const rel = f.replace(`${projectDir}/`, "");
2937
+ console.log(` ${rel}`);
2938
+ }
2939
+ if (!options?.skipImport) {
2940
+ const detected = await scanForConfigs(projectDir);
2941
+ if (detected.length > 0) {
2942
+ const doImport = cancelGuard(
2943
+ await (0, import_prompts.confirm)({
2944
+ message: `Found ${detected.length} existing config file(s). Import them?`
2945
+ })
2946
+ );
2947
+ if (doImport) {
2948
+ const result = await runImport(projectDir, {
2949
+ interactive: false
2950
+ });
2951
+ if (result.imported.length > 0) {
2952
+ console.log(`
2953
+ \x1B[32m\u2713\x1B[0m Imported ${result.imported.length} file(s)`);
2954
+ for (const f of result.imported) {
2955
+ console.log(` ${f.label}`);
2956
+ }
2957
+ }
2958
+ }
2959
+ }
2960
+ }
2961
+ const doSync = cancelGuard(await (0, import_prompts.confirm)({ message: "Run ai-dot sync now?" }));
2962
+ if (doSync) {
2963
+ console.log("");
2964
+ await runSync(projectDir, { targets, dryRun: false, scope: "project", force: false });
2965
+ }
2966
+ (0, import_prompts.outro)("Done! Edit .ai/ to customize, then run ai-dot sync.");
2967
+ }
2968
+ async function nonInteractiveInit(projectDir, options) {
2969
+ const aiDir = (0, import_node_path12.join)(projectDir, ".ai");
2970
+ const templateName = options?.template ?? "blank";
2971
+ const config = getTemplate(templateName);
2972
+ const written = await writeProjectConfig(aiDir, config);
2973
+ console.log(`\x1B[32m\u2713\x1B[0m Initialized .ai/ from "${templateName}" template`);
2974
+ for (const f of written) {
2975
+ const rel = f.replace(`${projectDir}/`, "");
2976
+ console.log(` ${rel}`);
2977
+ }
2978
+ if (!options?.skipImport) {
2979
+ const detected = await scanForConfigs(projectDir);
2980
+ if (detected.length > 0) {
2981
+ const result = await runImport(projectDir, {
2982
+ interactive: false
2983
+ });
2984
+ if (result.imported.length > 0) {
2985
+ console.log(`\x1B[32m\u2713\x1B[0m Imported ${result.imported.length} existing config file(s)`);
2986
+ }
2987
+ }
2988
+ }
2989
+ if (options?.autoSync) {
2990
+ const targets = options.targets ?? [];
2991
+ await runSync(projectDir, { targets, dryRun: false, scope: "project", force: false });
2992
+ }
2993
+ if (!options?.autoSync) {
2994
+ console.log("\nNext: run \x1B[1mai-dot sync\x1B[0m to generate tool configs.");
2995
+ }
2996
+ }
2997
+
2998
+ // src/commands/status.ts
2999
+ var EMITTERS2 = [
3000
+ skillsEmitter,
3001
+ directivesEmitter,
3002
+ mcpEmitter,
3003
+ agentsEmitter,
3004
+ permissionsEmitter,
3005
+ hooksEmitter
3006
+ ];
3007
+ var STATUS_ICONS = {
3008
+ new: "\x1B[32m+\x1B[0m",
3009
+ "up-to-date": "\x1B[90m=\x1B[0m",
3010
+ modified: "\x1B[33m~\x1B[0m",
3011
+ conflict: "\x1B[31m!\x1B[0m",
3012
+ orphaned: "\x1B[31m-\x1B[0m"
3013
+ };
3014
+ var STATUS_LABELS = {
3015
+ new: "new",
3016
+ "up-to-date": "up-to-date",
3017
+ modified: "modified",
3018
+ conflict: "conflict (manually edited)",
3019
+ orphaned: "orphaned (no longer generated)"
3020
+ };
3021
+ async function runStatus(projectDir) {
3022
+ const { config, errors: loadErrors } = await loadMergedConfig(projectDir);
3023
+ if (loadErrors.length > 0) {
3024
+ console.error("\x1B[31mConfig errors:\x1B[0m");
3025
+ for (const err of loadErrors) {
3026
+ console.error(` ${err.file}${err.line ? `:${err.line}` : ""}: ${err.message}`);
3027
+ }
3028
+ process.exitCode = 1;
3029
+ return;
3030
+ }
3031
+ const { errors: valErrors } = validateConfig(config);
3032
+ if (valErrors.length > 0) {
3033
+ console.error("\x1B[31mValidation errors:\x1B[0m");
3034
+ for (const err of valErrors) {
3035
+ console.error(` ${err.file}: ${err.message}`);
3036
+ }
3037
+ process.exitCode = 1;
3038
+ return;
3039
+ }
3040
+ const allFiles = [];
3041
+ for (const target of ALL_TARGETS) {
3042
+ for (const emitter of EMITTERS2) {
3043
+ const result = emitter.emit(config, target);
3044
+ allFiles.push(...result.files);
3045
+ }
3046
+ }
3047
+ const mergedFiles = mergeFilesByPath(allFiles);
3048
+ const state = await loadState(projectDir);
3049
+ if (!state) {
3050
+ console.log("No previous sync state found. Run \x1B[1mai-dot sync\x1B[0m first.\n");
3051
+ }
3052
+ const entries = await diffFiles(projectDir, mergedFiles, state);
3053
+ const counts = {
3054
+ new: 0,
3055
+ "up-to-date": 0,
3056
+ modified: 0,
3057
+ conflict: 0,
3058
+ orphaned: 0
3059
+ };
3060
+ console.log("\x1B[1mFile status:\x1B[0m\n");
3061
+ for (const entry of entries.sort((a, b) => a.path.localeCompare(b.path))) {
3062
+ counts[entry.status]++;
3063
+ console.log(
3064
+ ` ${STATUS_ICONS[entry.status]} ${entry.path} \x1B[90m${STATUS_LABELS[entry.status]}\x1B[0m`
3065
+ );
3066
+ }
3067
+ console.log("");
3068
+ const parts = [];
3069
+ if (counts.new > 0) parts.push(`\x1B[32m${counts.new} new\x1B[0m`);
3070
+ if (counts.modified > 0) parts.push(`\x1B[33m${counts.modified} modified\x1B[0m`);
3071
+ if (counts["up-to-date"] > 0) parts.push(`\x1B[90m${counts["up-to-date"]} up-to-date\x1B[0m`);
3072
+ if (counts.conflict > 0) parts.push(`\x1B[31m${counts.conflict} conflicts\x1B[0m`);
3073
+ if (counts.orphaned > 0) parts.push(`\x1B[31m${counts.orphaned} orphaned\x1B[0m`);
3074
+ console.log(` ${parts.join(", ")}`);
3075
+ if (counts.conflict > 0) {
3076
+ console.log("\n Use \x1B[1mai-dot sync --force\x1B[0m to overwrite conflicting files.");
3077
+ }
3078
+ }
3079
+ function mergeFilesByPath(files) {
3080
+ const byPath = /* @__PURE__ */ new Map();
3081
+ for (const file of files) {
3082
+ const existing = byPath.get(file.path);
3083
+ if (!existing) {
3084
+ byPath.set(file.path, file);
3085
+ } else if (file.path.endsWith(".json")) {
3086
+ const merged = deepMergeJson(existing.content, file.content);
3087
+ byPath.set(file.path, { path: file.path, content: merged });
3088
+ } else if (file.path.endsWith(".toml")) {
3089
+ byPath.set(file.path, {
3090
+ path: file.path,
3091
+ content: `${existing.content.trim()}
3092
+
3093
+ ${file.content.trim()}
3094
+ `
3095
+ });
3096
+ } else if (file.path.endsWith(".md")) {
3097
+ byPath.set(file.path, {
3098
+ path: file.path,
3099
+ content: `${existing.content.trim()}
3100
+
3101
+ ---
3102
+
3103
+ ${file.content.trim()}
3104
+ `
3105
+ });
3106
+ } else {
3107
+ byPath.set(file.path, file);
3108
+ }
3109
+ }
3110
+ return [...byPath.values()];
3111
+ }
3112
+ function deepMergeJson(a, b) {
3113
+ const objA = JSON.parse(a);
3114
+ const objB = JSON.parse(b);
3115
+ const merged = merge(objA, objB);
3116
+ return `${JSON.stringify(merged, null, 2)}
3117
+ `;
3118
+ }
3119
+ function merge(target, source) {
3120
+ const result = { ...target };
3121
+ for (const [key, value] of Object.entries(source)) {
3122
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
3123
+ result[key] = merge(result[key], value);
3124
+ } else if (Array.isArray(value) && Array.isArray(result[key])) {
3125
+ result[key] = [...result[key], ...value];
3126
+ } else {
3127
+ result[key] = value;
3128
+ }
3129
+ }
3130
+ return result;
3131
+ }
3132
+
3133
+ // src/domain/factories.ts
3134
+ function createDirective(overrides) {
3135
+ return {
3136
+ alwaysApply: true,
3137
+ ...overrides
3138
+ };
3139
+ }
3140
+ function createSkill(overrides) {
3141
+ return {
3142
+ description: "",
3143
+ disableAutoInvocation: false,
3144
+ ...overrides
3145
+ };
3146
+ }
3147
+ function createAgent(overrides) {
3148
+ return {
3149
+ description: "",
3150
+ ...overrides
3151
+ };
3152
+ }
3153
+ function createToolServer(overrides) {
3154
+ return { ...overrides };
3155
+ }
3156
+ function createHook(overrides) {
3157
+ return { ...overrides };
3158
+ }
3159
+ function createPermission(overrides) {
3160
+ return { ...overrides };
3161
+ }
3162
+ function createSetting(overrides) {
3163
+ return { ...overrides };
3164
+ }
3165
+ function createIgnorePattern(overrides) {
3166
+ return { ...overrides };
3167
+ }
3168
+
3169
+ // src/domain/hook.ts
3170
+ var HookEvent = {
3171
+ PreToolUse: "preToolUse",
3172
+ PostToolUse: "postToolUse",
3173
+ PreFileEdit: "preFileEdit",
3174
+ PostFileEdit: "postFileEdit",
3175
+ SessionStart: "sessionStart",
3176
+ SessionEnd: "sessionEnd",
3177
+ UserPromptSubmitted: "userPromptSubmitted",
3178
+ AgentStop: "agentStop",
3179
+ SubagentStop: "subagentStop",
3180
+ ErrorOccurred: "errorOccurred"
3181
+ };
3182
+
3183
+ // src/domain/permission.ts
3184
+ var Decision = {
3185
+ Allow: "allow",
3186
+ Deny: "deny",
3187
+ Ask: "ask"
3188
+ };
3189
+
3190
+ // src/domain/scope.ts
3191
+ var Scope = {
3192
+ Enterprise: "enterprise",
3193
+ Project: "project",
3194
+ User: "user",
3195
+ Local: "local"
3196
+ };
3197
+ var SCOPE_PRECEDENCE = [
3198
+ Scope.Enterprise,
3199
+ Scope.Project,
3200
+ Scope.User,
3201
+ Scope.Local
3202
+ ];
3203
+ function scopeOutranks(a, b) {
3204
+ return SCOPE_PRECEDENCE.indexOf(a) < SCOPE_PRECEDENCE.indexOf(b);
3205
+ }
3206
+
3207
+ // src/domain/tool-server.ts
3208
+ var Transport = {
3209
+ Stdio: "stdio",
3210
+ Http: "http",
3211
+ Sse: "sse"
3212
+ };
3213
+
3214
+ // src/domain/type-guards.ts
3215
+ function isObj(v) {
3216
+ return typeof v === "object" && v !== null;
3217
+ }
3218
+ function hasString(o, k) {
3219
+ return typeof o[k] === "string";
3220
+ }
3221
+ function hasScope(o) {
3222
+ return typeof o.scope === "string" && SCOPE_PRECEDENCE.includes(o.scope);
3223
+ }
3224
+ function isDirective(v) {
3225
+ return isObj(v) && hasString(v, "content") && hasScope(v) && typeof v.alwaysApply === "boolean";
3226
+ }
3227
+ function isSkill(v) {
3228
+ return isObj(v) && hasString(v, "name") && hasString(v, "content") && typeof v.disableAutoInvocation === "boolean";
3229
+ }
3230
+ function isAgent(v) {
3231
+ return isObj(v) && hasString(v, "name") && hasString(v, "instructions");
3232
+ }
3233
+ function isToolServer(v) {
3234
+ return isObj(v) && hasString(v, "name") && hasString(v, "transport") && hasScope(v);
3235
+ }
3236
+ function isHook(v) {
3237
+ return isObj(v) && hasString(v, "event") && hasString(v, "handler") && hasScope(v);
3238
+ }
3239
+ function isPermission(v) {
3240
+ return isObj(v) && hasString(v, "tool") && hasString(v, "decision") && hasScope(v);
3241
+ }
3242
+ function isSetting(v) {
3243
+ return isObj(v) && hasString(v, "key") && "value" in v && hasScope(v);
3244
+ }
3245
+ function isIgnorePattern(v) {
3246
+ return isObj(v) && hasString(v, "pattern") && hasScope(v);
3247
+ }
3248
+ // Annotate the CommonJS export names for ESM import in node:
3249
+ 0 && (module.exports = {
3250
+ ALL_TARGETS,
3251
+ Decision,
3252
+ HookEvent,
3253
+ SCOPE_PRECEDENCE,
3254
+ Scope,
3255
+ TEMPLATES,
3256
+ TEMPLATE_NAMES,
3257
+ TargetTool,
3258
+ Transport,
3259
+ agentsEmitter,
3260
+ contentHash,
3261
+ createAgent,
3262
+ createDirective,
3263
+ createHook,
3264
+ createIgnorePattern,
3265
+ createPermission,
3266
+ createSetting,
3267
+ createSkill,
3268
+ createToolServer,
3269
+ diffFiles,
3270
+ directivesEmitter,
3271
+ emptyConfig,
3272
+ getTemplate,
3273
+ hooksEmitter,
3274
+ isAgent,
3275
+ isDirective,
3276
+ isHook,
3277
+ isIgnorePattern,
3278
+ isPermission,
3279
+ isSetting,
3280
+ isSkill,
3281
+ isToolServer,
3282
+ loadMarkdownFile,
3283
+ loadMergedConfig,
3284
+ loadProjectConfig,
3285
+ loadSkills,
3286
+ loadState,
3287
+ mcpEmitter,
3288
+ mergeConfigs,
3289
+ parseClaude,
3290
+ parseCodex,
3291
+ parseCursor,
3292
+ parseMarkdownWithFrontmatter,
3293
+ parseOpenCode,
3294
+ permissionsEmitter,
3295
+ runCheck,
3296
+ runImport,
3297
+ runImportCommand,
3298
+ runInit,
3299
+ runStatus,
3300
+ runSync,
3301
+ saveState,
3302
+ scanForConfigs,
3303
+ scopeOutranks,
3304
+ skillsEmitter,
3305
+ validateConfig,
3306
+ writeProjectConfig
3307
+ });
3308
+ //# sourceMappingURL=index.cjs.map