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