agentloom 0.1.5 → 0.1.7
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/README.md +92 -6
- package/dist/cli.js +11 -4
- package/dist/commands/add.js +14 -0
- package/dist/commands/delete.js +89 -3
- package/dist/commands/entity-utils.js +3 -0
- package/dist/commands/find.js +146 -12
- package/dist/commands/rule.d.ts +2 -0
- package/dist/commands/rule.js +86 -0
- package/dist/commands/sync.js +13 -4
- package/dist/commands/update.js +90 -7
- package/dist/core/argv.js +2 -0
- package/dist/core/commands.d.ts +12 -0
- package/dist/core/commands.js +106 -6
- package/dist/core/copy.js +12 -12
- package/dist/core/importer.d.ts +10 -0
- package/dist/core/importer.js +941 -46
- package/dist/core/lockfile.js +8 -0
- package/dist/core/manifest.js +1 -1
- package/dist/core/migration.js +650 -66
- package/dist/core/provider-entity-validation.d.ts +8 -0
- package/dist/core/provider-entity-validation.js +34 -0
- package/dist/core/provider-paths.d.ts +8 -1
- package/dist/core/provider-paths.js +69 -5
- package/dist/core/router.js +17 -3
- package/dist/core/rules.d.ts +34 -0
- package/dist/core/rules.js +149 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/skills.d.ts +4 -0
- package/dist/core/skills.js +47 -12
- package/dist/core/sources.d.ts +7 -0
- package/dist/core/sources.js +146 -22
- package/dist/core/telemetry.d.ts +1 -1
- package/dist/core/telemetry.js +16 -0
- package/dist/sync/index.js +403 -17
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
package/dist/sync/index.js
CHANGED
|
@@ -5,11 +5,12 @@ import TOML from "@iarna/toml";
|
|
|
5
5
|
import YAML from "yaml";
|
|
6
6
|
import { ALL_PROVIDERS } from "../types.js";
|
|
7
7
|
import { getProviderConfig, isProviderEnabled, parseAgentsDir, } from "../core/agents.js";
|
|
8
|
-
import { parseCommandsDir } from "../core/commands.js";
|
|
9
|
-
import {
|
|
8
|
+
import { parseCommandsDir, renderCommandForProvider, } from "../core/commands.js";
|
|
9
|
+
import { parseRulesDir, renderRuleForCursor, upsertManagedRuleBlocks, } from "../core/rules.js";
|
|
10
|
+
import { ensureDir, isObject, readJsonIfExists, readTextIfExists, relativePosix, removeFileIfExists, slugify, toPosixPath, writeJsonAtomic, writeTextAtomic, } from "../core/fs.js";
|
|
10
11
|
import { readManifest, writeManifest } from "../core/manifest.js";
|
|
11
12
|
import { readCanonicalMcp, resolveMcpForProvider } from "../core/mcp.js";
|
|
12
|
-
import { getClaudeMcpPath, getClaudeSettingsPath, getCodexAgentsDir, getCodexConfigPath, getCodexRootDir, getCopilotMcpPath, getCursorMcpPath, getGeminiSettingsPath, getOpenCodeConfigPath, getPiMcpPath, getProviderAgentsDir, getProviderCommandsDir, getVsCodeSettingsPath, } from "../core/provider-paths.js";
|
|
13
|
+
import { getClaudeMcpPath, getClaudeSettingsPath, getCodexAgentsDir, getCodexConfigPath, getCodexRootDir, getCopilotMcpPath, getCursorRulesDir, getCursorMcpPath, getGeminiSettingsPath, getOpenCodeConfigPath, getPiMcpPath, getProviderAgentsDir, getProviderCommandsDir, getRuleInstructionPaths, getVsCodeSettingsPath, } from "../core/provider-paths.js";
|
|
13
14
|
import { getGlobalSettingsPath, readSettings, updateLastScope, updateLastScopeBestEffort, } from "../core/settings.js";
|
|
14
15
|
export async function resolveProvidersForSync(options) {
|
|
15
16
|
const settings = readSettings(options.paths.settingsPath);
|
|
@@ -22,6 +23,8 @@ export async function resolveProvidersForSync(options) {
|
|
|
22
23
|
export async function syncFromCanonical(options) {
|
|
23
24
|
const agents = parseAgentsDir(options.paths.agentsDir);
|
|
24
25
|
const commands = parseCommandsDir(options.paths.commandsDir);
|
|
26
|
+
const rules = parseRulesDir(options.paths.rulesDir);
|
|
27
|
+
const managedInstructionRules = rules.filter((rule) => rule.frontmatter.alwaysApply !== false);
|
|
25
28
|
const mcp = readCanonicalMcp(options.paths);
|
|
26
29
|
const manifest = readManifest(options.paths);
|
|
27
30
|
const effectiveManifest = {
|
|
@@ -46,6 +49,9 @@ export async function syncFromCanonical(options) {
|
|
|
46
49
|
const generatedAgents = new Set();
|
|
47
50
|
const generatedCommands = new Set();
|
|
48
51
|
const generatedMcp = new Set();
|
|
52
|
+
const generatedRules = new Set();
|
|
53
|
+
const updatedRuleInstructionFiles = new Set();
|
|
54
|
+
const retainedRuleInstructionFiles = new Set();
|
|
49
55
|
if (target === "all" || target === "agent") {
|
|
50
56
|
for (const provider of providers) {
|
|
51
57
|
syncProviderAgents({
|
|
@@ -56,6 +62,13 @@ export async function syncFromCanonical(options) {
|
|
|
56
62
|
dryRun: !!options.dryRun,
|
|
57
63
|
});
|
|
58
64
|
}
|
|
65
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
66
|
+
syncCopilotDiscoverySettings({
|
|
67
|
+
paths: options.paths,
|
|
68
|
+
dryRun: !!options.dryRun,
|
|
69
|
+
includeAgentLocations: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
59
72
|
}
|
|
60
73
|
if (target === "all" || target === "command") {
|
|
61
74
|
for (const provider of providers) {
|
|
@@ -67,6 +80,13 @@ export async function syncFromCanonical(options) {
|
|
|
67
80
|
dryRun: !!options.dryRun,
|
|
68
81
|
});
|
|
69
82
|
}
|
|
83
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
84
|
+
syncCopilotDiscoverySettings({
|
|
85
|
+
paths: options.paths,
|
|
86
|
+
dryRun: !!options.dryRun,
|
|
87
|
+
includePromptLocations: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
70
90
|
}
|
|
71
91
|
if (target === "all" || target === "mcp") {
|
|
72
92
|
syncProviderMcp({
|
|
@@ -77,6 +97,33 @@ export async function syncFromCanonical(options) {
|
|
|
77
97
|
dryRun: !!options.dryRun,
|
|
78
98
|
});
|
|
79
99
|
}
|
|
100
|
+
if (target === "all" || target === "rule") {
|
|
101
|
+
syncManagedRuleInstructions({
|
|
102
|
+
paths: options.paths,
|
|
103
|
+
providers,
|
|
104
|
+
rules: managedInstructionRules,
|
|
105
|
+
generated: generatedRules,
|
|
106
|
+
updated: updatedRuleInstructionFiles,
|
|
107
|
+
retained: retainedRuleInstructionFiles,
|
|
108
|
+
previouslyTracked: new Set(effectiveManifest.generatedByEntity?.rule ?? []),
|
|
109
|
+
dryRun: !!options.dryRun,
|
|
110
|
+
});
|
|
111
|
+
if (providers.includes("cursor") && options.paths.scope === "local") {
|
|
112
|
+
syncCursorRules({
|
|
113
|
+
paths: options.paths,
|
|
114
|
+
rules,
|
|
115
|
+
generated: generatedRules,
|
|
116
|
+
dryRun: !!options.dryRun,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
120
|
+
syncCopilotDiscoverySettings({
|
|
121
|
+
paths: options.paths,
|
|
122
|
+
dryRun: !!options.dryRun,
|
|
123
|
+
includeInstructionLocations: true,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
80
127
|
if (providers.includes("codex")) {
|
|
81
128
|
const includeRoles = target === "all" || target === "agent";
|
|
82
129
|
const includeMcp = target === "all" || target === "mcp";
|
|
@@ -113,18 +160,23 @@ export async function syncFromCanonical(options) {
|
|
|
113
160
|
if (target === "all" || target === "mcp") {
|
|
114
161
|
nextByEntity.mcp = [...generatedMcp].sort();
|
|
115
162
|
}
|
|
163
|
+
if (target === "all" || target === "rule") {
|
|
164
|
+
nextByEntity.rule = [...generatedRules].sort();
|
|
165
|
+
}
|
|
116
166
|
nextManifest.generatedByEntity = pruneGeneratedByEntity(nextByEntity);
|
|
117
167
|
nextManifest.generatedFiles = [
|
|
118
168
|
...new Set([
|
|
119
169
|
...(nextManifest.generatedByEntity.agent ?? []),
|
|
120
170
|
...(nextManifest.generatedByEntity.command ?? []),
|
|
121
171
|
...(nextManifest.generatedByEntity.mcp ?? []),
|
|
172
|
+
...(nextManifest.generatedByEntity.rule ?? []),
|
|
122
173
|
...(nextManifest.generatedByEntity.skill ?? []),
|
|
123
174
|
]),
|
|
124
175
|
].sort();
|
|
125
176
|
const removedFiles = await removeStaleGeneratedFiles({
|
|
126
177
|
oldManifest: manifest,
|
|
127
178
|
newManifest: nextManifest,
|
|
179
|
+
protectedFiles: retainedRuleInstructionFiles,
|
|
128
180
|
dryRun: !!options.dryRun,
|
|
129
181
|
yes: !!options.yes,
|
|
130
182
|
nonInteractive: !!options.nonInteractive,
|
|
@@ -139,7 +191,12 @@ export async function syncFromCanonical(options) {
|
|
|
139
191
|
}
|
|
140
192
|
return {
|
|
141
193
|
providers,
|
|
142
|
-
generatedFiles:
|
|
194
|
+
generatedFiles: [
|
|
195
|
+
...new Set([
|
|
196
|
+
...nextManifest.generatedFiles,
|
|
197
|
+
...updatedRuleInstructionFiles,
|
|
198
|
+
]),
|
|
199
|
+
].sort(),
|
|
143
200
|
removedFiles,
|
|
144
201
|
};
|
|
145
202
|
}
|
|
@@ -232,15 +289,36 @@ function syncProviderCommands(options) {
|
|
|
232
289
|
for (const command of options.commands) {
|
|
233
290
|
const fileName = mapProviderCommandFileName(options.provider, command.fileName);
|
|
234
291
|
const outputPath = path.join(providerDir, fileName);
|
|
292
|
+
const content = renderCommandForProvider(command, options.provider);
|
|
293
|
+
if (content === null)
|
|
294
|
+
continue;
|
|
235
295
|
if (!options.dryRun) {
|
|
236
296
|
ensureDir(path.dirname(outputPath));
|
|
237
|
-
writeTextAtomic(outputPath,
|
|
297
|
+
writeTextAtomic(outputPath, content);
|
|
238
298
|
}
|
|
239
299
|
options.generated.add(outputPath);
|
|
240
300
|
}
|
|
241
301
|
}
|
|
242
302
|
function mapProviderCommandFileName(provider, fileName) {
|
|
243
303
|
const lower = fileName.toLowerCase();
|
|
304
|
+
if (provider === "gemini") {
|
|
305
|
+
if (lower.endsWith(".toml"))
|
|
306
|
+
return fileName;
|
|
307
|
+
if (lower.endsWith(".prompt.md")) {
|
|
308
|
+
return `${fileName.slice(0, -".prompt.md".length)}.toml`;
|
|
309
|
+
}
|
|
310
|
+
if (lower.endsWith(".md")) {
|
|
311
|
+
return `${fileName.slice(0, -3)}.toml`;
|
|
312
|
+
}
|
|
313
|
+
if (lower.endsWith(".mdc")) {
|
|
314
|
+
return `${fileName.slice(0, -4)}.toml`;
|
|
315
|
+
}
|
|
316
|
+
const ext = path.extname(fileName);
|
|
317
|
+
if (ext) {
|
|
318
|
+
return `${fileName.slice(0, -ext.length)}.toml`;
|
|
319
|
+
}
|
|
320
|
+
return `${fileName}.toml`;
|
|
321
|
+
}
|
|
244
322
|
if (provider === "copilot") {
|
|
245
323
|
if (lower.endsWith(".prompt.md"))
|
|
246
324
|
return fileName;
|
|
@@ -261,6 +339,21 @@ function mapProviderCommandFileName(provider, fileName) {
|
|
|
261
339
|
}
|
|
262
340
|
return fileName;
|
|
263
341
|
}
|
|
342
|
+
function syncCursorRules(options) {
|
|
343
|
+
if (options.paths.scope !== "local") {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const rulesDir = getCursorRulesDir(options.paths);
|
|
347
|
+
for (const rule of options.rules) {
|
|
348
|
+
const outputPath = path.join(rulesDir, `${rule.id}.mdc`);
|
|
349
|
+
const content = renderRuleForCursor(rule);
|
|
350
|
+
if (!options.dryRun) {
|
|
351
|
+
ensureDir(path.dirname(outputPath));
|
|
352
|
+
writeTextAtomic(outputPath, content);
|
|
353
|
+
}
|
|
354
|
+
options.generated.add(outputPath);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
264
357
|
function syncProviderMcp(options) {
|
|
265
358
|
for (const provider of options.providers) {
|
|
266
359
|
if (provider === "codex")
|
|
@@ -276,8 +369,18 @@ function syncProviderMcp(options) {
|
|
|
276
369
|
continue;
|
|
277
370
|
}
|
|
278
371
|
if (provider === "claude") {
|
|
279
|
-
const mcpPath = getClaudeMcpPath(options.paths);
|
|
280
372
|
const settingsPath = getClaudeSettingsPath(options.paths);
|
|
373
|
+
const settings = readClaudeSettingsForSync(options.paths);
|
|
374
|
+
if (options.paths.scope === "global") {
|
|
375
|
+
maybeMigrateClaudeGlobalSettings({
|
|
376
|
+
paths: options.paths,
|
|
377
|
+
settingsPath,
|
|
378
|
+
settings,
|
|
379
|
+
dryRun: options.dryRun,
|
|
380
|
+
});
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const mcpPath = getClaudeMcpPath(options.paths);
|
|
281
384
|
const claudeServers = mapMcpServers(resolved, [
|
|
282
385
|
"type",
|
|
283
386
|
"url",
|
|
@@ -292,7 +395,6 @@ function syncProviderMcp(options) {
|
|
|
292
395
|
}
|
|
293
396
|
maybeWriteJson(mcpPath, { mcpServers: claudeServers }, options.dryRun);
|
|
294
397
|
options.generated.add(mcpPath);
|
|
295
|
-
const settings = readJsonIfExists(settingsPath) ?? {};
|
|
296
398
|
settings.enabledMcpjsonServers = Object.keys(claudeServers).sort();
|
|
297
399
|
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
298
400
|
options.generated.add(settingsPath);
|
|
@@ -396,6 +498,260 @@ function syncProviderMcp(options) {
|
|
|
396
498
|
}
|
|
397
499
|
}
|
|
398
500
|
}
|
|
501
|
+
function syncManagedRuleInstructions(options) {
|
|
502
|
+
const cleanupProviders = options.paths.scope === "global"
|
|
503
|
+
? ["claude", "gemini", "copilot", "opencode"]
|
|
504
|
+
: ALL_PROVIDERS;
|
|
505
|
+
const activeTargets = new Set(getRuleInstructionPaths(options.paths, options.providers));
|
|
506
|
+
if (options.paths.scope === "global" && activeTargets.size === 0) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const cleanupTargets = new Set(getRuleInstructionPaths(options.paths, cleanupProviders));
|
|
510
|
+
for (const targetPath of cleanupTargets) {
|
|
511
|
+
if (!fs.existsSync(targetPath)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const stat = fs.lstatSync(targetPath);
|
|
515
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
516
|
+
options.retained.add(targetPath);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const existing = readTextIfExists(targetPath);
|
|
520
|
+
if (existing === null) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const next = upsertManagedRuleBlocks(existing, activeTargets.has(targetPath) ? options.rules : []);
|
|
524
|
+
const shouldTrackGenerated = activeTargets.has(targetPath) &&
|
|
525
|
+
options.rules.length > 0 &&
|
|
526
|
+
next.trim().length > 0;
|
|
527
|
+
if (shouldTrackGenerated) {
|
|
528
|
+
options.generated.add(targetPath);
|
|
529
|
+
}
|
|
530
|
+
const shouldRetainOnDisk = !shouldTrackGenerated &&
|
|
531
|
+
next.trim().length > 0 &&
|
|
532
|
+
fs.existsSync(targetPath);
|
|
533
|
+
if (shouldRetainOnDisk) {
|
|
534
|
+
options.retained.add(targetPath);
|
|
535
|
+
}
|
|
536
|
+
if (next === existing)
|
|
537
|
+
continue;
|
|
538
|
+
options.updated.add(targetPath);
|
|
539
|
+
if (!options.dryRun) {
|
|
540
|
+
if (next.trim().length === 0) {
|
|
541
|
+
if (!options.previouslyTracked.has(targetPath)) {
|
|
542
|
+
removeFileIfExists(targetPath);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
writeTextAtomic(targetPath, next);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function syncCopilotDiscoverySettings(options) {
|
|
552
|
+
const settingsPath = getVsCodeSettingsPath(options.paths.homeDir);
|
|
553
|
+
const settings = readVsCodeSettings(settingsPath);
|
|
554
|
+
if (!settings) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (options.includePromptLocations) {
|
|
558
|
+
appendPathSetting(settings, "chat.promptFilesLocations", path.join(options.paths.homeDir, ".copilot", "prompts"));
|
|
559
|
+
}
|
|
560
|
+
if (options.includeAgentLocations) {
|
|
561
|
+
appendPathSetting(settings, "chat.agentFilesLocations", path.join(options.paths.homeDir, ".copilot", "agents"));
|
|
562
|
+
}
|
|
563
|
+
if (options.includeInstructionLocations) {
|
|
564
|
+
const instructionPath = path.join(options.paths.homeDir, ".copilot", "copilot-instructions.md");
|
|
565
|
+
setPathSettingEnabled(settings, "chat.instructionsFilesLocations", instructionPath, fs.existsSync(instructionPath));
|
|
566
|
+
}
|
|
567
|
+
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
568
|
+
}
|
|
569
|
+
function readVsCodeSettings(settingsPath) {
|
|
570
|
+
const raw = readTextIfExists(settingsPath);
|
|
571
|
+
if (raw === null) {
|
|
572
|
+
return {};
|
|
573
|
+
}
|
|
574
|
+
const parsed = parseJsonOrJsonc(raw);
|
|
575
|
+
if (!isObject(parsed)) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
return parsed;
|
|
579
|
+
}
|
|
580
|
+
function readClaudeSettingsForSync(paths) {
|
|
581
|
+
const settingsPath = getClaudeSettingsPath(paths);
|
|
582
|
+
const settings = readJsonIfExists(settingsPath);
|
|
583
|
+
if (isObject(settings)) {
|
|
584
|
+
return { ...settings };
|
|
585
|
+
}
|
|
586
|
+
if (paths.scope === "global") {
|
|
587
|
+
const legacySettingsPath = path.join(paths.homeDir, ".claude.json");
|
|
588
|
+
const legacySettings = readJsonIfExists(legacySettingsPath);
|
|
589
|
+
if (isObject(legacySettings)) {
|
|
590
|
+
return { ...legacySettings };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return {};
|
|
594
|
+
}
|
|
595
|
+
function maybeMigrateClaudeGlobalSettings(options) {
|
|
596
|
+
const nextSettings = { ...options.settings };
|
|
597
|
+
delete nextSettings.enabledMcpjsonServers;
|
|
598
|
+
const legacySettingsPath = path.join(options.paths.homeDir, ".claude.json");
|
|
599
|
+
const hasCurrentSettings = fs.existsSync(options.settingsPath) &&
|
|
600
|
+
fs.statSync(options.settingsPath).isFile();
|
|
601
|
+
const hasLegacySettings = fs.existsSync(legacySettingsPath) &&
|
|
602
|
+
fs.statSync(legacySettingsPath).isFile();
|
|
603
|
+
const shouldWrite = hasCurrentSettings ||
|
|
604
|
+
(hasLegacySettings && Object.keys(nextSettings).length > 0);
|
|
605
|
+
if (shouldWrite) {
|
|
606
|
+
maybeWriteJson(options.settingsPath, nextSettings, options.dryRun);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function parseJsonOrJsonc(input) {
|
|
610
|
+
if (input.trim() === "") {
|
|
611
|
+
return {};
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
return JSON.parse(input);
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
try {
|
|
618
|
+
const withoutComments = stripJsonComments(input);
|
|
619
|
+
const normalized = stripTrailingJsonCommas(withoutComments);
|
|
620
|
+
if (normalized.trim() === "") {
|
|
621
|
+
return {};
|
|
622
|
+
}
|
|
623
|
+
return JSON.parse(normalized);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function stripJsonComments(input) {
|
|
631
|
+
let result = "";
|
|
632
|
+
let inString = false;
|
|
633
|
+
let inLineComment = false;
|
|
634
|
+
let inBlockComment = false;
|
|
635
|
+
let escaped = false;
|
|
636
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
637
|
+
const char = input[i];
|
|
638
|
+
const next = i + 1 < input.length ? input[i + 1] : "";
|
|
639
|
+
if (inLineComment) {
|
|
640
|
+
if (char === "\n") {
|
|
641
|
+
inLineComment = false;
|
|
642
|
+
result += char;
|
|
643
|
+
}
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (inBlockComment) {
|
|
647
|
+
if (char === "*" && next === "/") {
|
|
648
|
+
inBlockComment = false;
|
|
649
|
+
i += 1;
|
|
650
|
+
}
|
|
651
|
+
else if (char === "\n") {
|
|
652
|
+
result += char;
|
|
653
|
+
}
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (inString) {
|
|
657
|
+
result += char;
|
|
658
|
+
if (escaped) {
|
|
659
|
+
escaped = false;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (char === "\\") {
|
|
663
|
+
escaped = true;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (char === '"') {
|
|
667
|
+
inString = false;
|
|
668
|
+
}
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (char === '"') {
|
|
672
|
+
inString = true;
|
|
673
|
+
result += char;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (char === "/" && next === "/") {
|
|
677
|
+
inLineComment = true;
|
|
678
|
+
i += 1;
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (char === "/" && next === "*") {
|
|
682
|
+
inBlockComment = true;
|
|
683
|
+
i += 1;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
result += char;
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
function stripTrailingJsonCommas(input) {
|
|
691
|
+
let result = "";
|
|
692
|
+
let inString = false;
|
|
693
|
+
let escaped = false;
|
|
694
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
695
|
+
const char = input[i];
|
|
696
|
+
if (inString) {
|
|
697
|
+
result += char;
|
|
698
|
+
if (escaped) {
|
|
699
|
+
escaped = false;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (char === "\\") {
|
|
703
|
+
escaped = true;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (char === '"') {
|
|
707
|
+
inString = false;
|
|
708
|
+
}
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (char === '"') {
|
|
712
|
+
inString = true;
|
|
713
|
+
result += char;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (char === ",") {
|
|
717
|
+
let lookahead = i + 1;
|
|
718
|
+
while (lookahead < input.length && /\s/.test(input[lookahead] ?? "")) {
|
|
719
|
+
lookahead += 1;
|
|
720
|
+
}
|
|
721
|
+
const next = input[lookahead];
|
|
722
|
+
if (next === "}" || next === "]") {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
result += char;
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
730
|
+
function appendPathSetting(settings, key, settingPath) {
|
|
731
|
+
const existing = Array.isArray(settings[key])
|
|
732
|
+
? settings[key].filter((value) => typeof value === "string" && value.trim() !== "")
|
|
733
|
+
: [];
|
|
734
|
+
if (!existing.includes(settingPath)) {
|
|
735
|
+
existing.push(settingPath);
|
|
736
|
+
}
|
|
737
|
+
settings[key] = existing;
|
|
738
|
+
}
|
|
739
|
+
function setPathSettingEnabled(settings, key, settingPath, enabled) {
|
|
740
|
+
if (enabled) {
|
|
741
|
+
appendPathSetting(settings, key, settingPath);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const existing = Array.isArray(settings[key])
|
|
745
|
+
? settings[key].filter((value) => typeof value === "string" &&
|
|
746
|
+
value.trim() !== "" &&
|
|
747
|
+
value !== settingPath)
|
|
748
|
+
: [];
|
|
749
|
+
if (existing.length === 0) {
|
|
750
|
+
delete settings[key];
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
settings[key] = existing;
|
|
754
|
+
}
|
|
399
755
|
function syncCodex(options) {
|
|
400
756
|
const codexDir = getCodexRootDir(options.paths);
|
|
401
757
|
const codexConfigPath = getCodexConfigPath(options.paths);
|
|
@@ -437,10 +793,11 @@ function syncCodex(options) {
|
|
|
437
793
|
continue;
|
|
438
794
|
const roleTomlPath = path.join(codexAgentsDir, `${role}.toml`);
|
|
439
795
|
const roleInstructionsPath = path.join(codexAgentsDir, `${role}.instructions.md`);
|
|
440
|
-
const
|
|
796
|
+
const developerInstructions = resolveCodexDeveloperInstructions(agent.body, codexConfig);
|
|
797
|
+
const roleToml = buildCodexRoleToml(developerInstructions, codexConfig);
|
|
441
798
|
if (!options.dryRun) {
|
|
442
799
|
ensureDir(codexAgentsDir);
|
|
443
|
-
writeTextAtomic(roleInstructionsPath, `${
|
|
800
|
+
writeTextAtomic(roleInstructionsPath, `${developerInstructions}\n`);
|
|
444
801
|
writeTextAtomic(roleTomlPath, TOML.stringify(roleToml));
|
|
445
802
|
}
|
|
446
803
|
options.generated.add(roleTomlPath);
|
|
@@ -490,9 +847,16 @@ function resolveTrackedCodexEntries(trackedEntries, fallbackEntries) {
|
|
|
490
847
|
const tracked = Array.isArray(trackedEntries) ? trackedEntries : [];
|
|
491
848
|
return [...new Set([...tracked, ...fallbackEntries])].sort();
|
|
492
849
|
}
|
|
493
|
-
function
|
|
850
|
+
function resolveCodexDeveloperInstructions(agentBody, providerConfig) {
|
|
851
|
+
if (typeof providerConfig.developerInstructions === "string" &&
|
|
852
|
+
providerConfig.developerInstructions.trim() !== "") {
|
|
853
|
+
return providerConfig.developerInstructions.trim();
|
|
854
|
+
}
|
|
855
|
+
return agentBody.trimStart().trimEnd();
|
|
856
|
+
}
|
|
857
|
+
function buildCodexRoleToml(developerInstructions, providerConfig) {
|
|
494
858
|
const roleToml = {
|
|
495
|
-
|
|
859
|
+
developer_instructions: developerInstructions,
|
|
496
860
|
};
|
|
497
861
|
if (typeof providerConfig.model === "string") {
|
|
498
862
|
roleToml.model = providerConfig.model;
|
|
@@ -500,6 +864,12 @@ function buildCodexRoleToml(roleInstructionsPath, providerConfig) {
|
|
|
500
864
|
if (typeof providerConfig.reasoningEffort === "string") {
|
|
501
865
|
roleToml.model_reasoning_effort = providerConfig.reasoningEffort;
|
|
502
866
|
}
|
|
867
|
+
if (typeof providerConfig.reasoningSummary === "string") {
|
|
868
|
+
roleToml.model_reasoning_summary = providerConfig.reasoningSummary;
|
|
869
|
+
}
|
|
870
|
+
if (typeof providerConfig.verbosity === "string") {
|
|
871
|
+
roleToml.model_verbosity = providerConfig.verbosity;
|
|
872
|
+
}
|
|
503
873
|
if (typeof providerConfig.approvalPolicy === "string") {
|
|
504
874
|
roleToml.approval_policy = providerConfig.approvalPolicy;
|
|
505
875
|
}
|
|
@@ -507,9 +877,7 @@ function buildCodexRoleToml(roleInstructionsPath, providerConfig) {
|
|
|
507
877
|
roleToml.sandbox_mode = providerConfig.sandboxMode;
|
|
508
878
|
}
|
|
509
879
|
if (typeof providerConfig.webSearch === "boolean") {
|
|
510
|
-
roleToml.
|
|
511
|
-
web_search: providerConfig.webSearch,
|
|
512
|
-
};
|
|
880
|
+
roleToml.web_search = providerConfig.webSearch;
|
|
513
881
|
}
|
|
514
882
|
return roleToml;
|
|
515
883
|
}
|
|
@@ -536,8 +904,10 @@ function maybeWriteJson(filePath, payload, dryRun) {
|
|
|
536
904
|
async function removeStaleGeneratedFiles(options) {
|
|
537
905
|
const oldSet = new Set(options.oldManifest.generatedFiles);
|
|
538
906
|
const newSet = new Set(options.newManifest.generatedFiles);
|
|
907
|
+
const protectedSet = new Set(options.protectedFiles ?? []);
|
|
539
908
|
const stale = [...oldSet]
|
|
540
909
|
.filter((filePath) => !newSet.has(filePath))
|
|
910
|
+
.filter((filePath) => !protectedSet.has(filePath))
|
|
541
911
|
.filter((filePath) => fs.existsSync(filePath));
|
|
542
912
|
if (stale.length === 0)
|
|
543
913
|
return [];
|
|
@@ -574,6 +944,7 @@ function normalizeGeneratedByEntity(manifest) {
|
|
|
574
944
|
agent: Array.isArray(source.agent) ? [...source.agent] : [],
|
|
575
945
|
command: Array.isArray(source.command) ? [...source.command] : [],
|
|
576
946
|
mcp: Array.isArray(source.mcp) ? [...source.mcp] : [],
|
|
947
|
+
rule: Array.isArray(source.rule) ? [...source.rule] : [],
|
|
577
948
|
skill: Array.isArray(source.skill) ? [...source.skill] : [],
|
|
578
949
|
};
|
|
579
950
|
}
|
|
@@ -582,6 +953,7 @@ function inferGeneratedByEntityFromLegacyFiles(generatedFiles) {
|
|
|
582
953
|
agent: [],
|
|
583
954
|
command: [],
|
|
584
955
|
mcp: [],
|
|
956
|
+
rule: [],
|
|
585
957
|
skill: [],
|
|
586
958
|
};
|
|
587
959
|
for (const filePath of generatedFiles) {
|
|
@@ -605,31 +977,45 @@ function classifyLegacyGeneratedFile(filePath) {
|
|
|
605
977
|
if (isLegacyMcpOutputPath(normalized)) {
|
|
606
978
|
return ["mcp"];
|
|
607
979
|
}
|
|
980
|
+
if (isLegacyRuleOutputPath(normalized)) {
|
|
981
|
+
return ["agent", "rule"];
|
|
982
|
+
}
|
|
608
983
|
// Preserve unknown generated paths during scoped syncs.
|
|
609
|
-
return ["agent", "command", "mcp"];
|
|
984
|
+
return ["agent", "command", "mcp", "rule"];
|
|
610
985
|
}
|
|
611
986
|
function isLegacyCommandOutputPath(normalizedPath) {
|
|
612
987
|
return (normalizedPath.includes("/.cursor/commands/") ||
|
|
613
988
|
normalizedPath.includes("/.claude/commands/") ||
|
|
614
989
|
normalizedPath.includes("/.opencode/commands/") ||
|
|
615
990
|
normalizedPath.includes("/.gemini/commands/") ||
|
|
991
|
+
normalizedPath.includes("/.copilot/prompts/") ||
|
|
616
992
|
normalizedPath.includes("/.github/prompts/") ||
|
|
617
993
|
normalizedPath.includes("/.codex/prompts/") ||
|
|
618
994
|
normalizedPath.includes("/.pi/prompts/"));
|
|
619
995
|
}
|
|
620
996
|
function isLegacyAgentOutputPath(normalizedPath) {
|
|
621
997
|
return (normalizedPath.includes("/.cursor/agents/") ||
|
|
622
|
-
normalizedPath.includes("/.cursor/rules/") ||
|
|
623
998
|
normalizedPath.includes("/.claude/agents/") ||
|
|
624
999
|
normalizedPath.includes("/.opencode/agents/") ||
|
|
625
1000
|
normalizedPath.includes("/.gemini/agents/") ||
|
|
1001
|
+
normalizedPath.includes("/.copilot/agents/") ||
|
|
626
1002
|
normalizedPath.includes("/.github/agents/") ||
|
|
627
1003
|
normalizedPath.includes("/.codex/agents/") ||
|
|
628
1004
|
normalizedPath.includes("/.pi/agents/"));
|
|
629
1005
|
}
|
|
1006
|
+
function isLegacyRuleOutputPath(normalizedPath) {
|
|
1007
|
+
return (normalizedPath.includes("/.cursor/rules/") ||
|
|
1008
|
+
normalizedPath.endsWith("/agents.md") ||
|
|
1009
|
+
normalizedPath.endsWith("/claude.md") ||
|
|
1010
|
+
normalizedPath.endsWith("/gemini.md") ||
|
|
1011
|
+
normalizedPath.endsWith("/.github/copilot-instructions.md") ||
|
|
1012
|
+
normalizedPath.endsWith("/.copilot/copilot-instructions.md") ||
|
|
1013
|
+
normalizedPath.endsWith("/.config/opencode/agents.md"));
|
|
1014
|
+
}
|
|
630
1015
|
function isLegacyMcpOutputPath(normalizedPath) {
|
|
631
1016
|
return (normalizedPath.endsWith("/.cursor/mcp.json") ||
|
|
632
1017
|
normalizedPath.endsWith("/.mcp.json") ||
|
|
1018
|
+
normalizedPath.endsWith("/.claude.json") ||
|
|
633
1019
|
normalizedPath.endsWith("/.claude/settings.json") ||
|
|
634
1020
|
normalizedPath.endsWith("/.opencode/opencode.json") ||
|
|
635
1021
|
normalizedPath.endsWith("/.gemini/settings.json") ||
|
|
@@ -643,7 +1029,7 @@ function isLegacyCodexConfigPath(normalizedPath) {
|
|
|
643
1029
|
}
|
|
644
1030
|
function pruneGeneratedByEntity(value) {
|
|
645
1031
|
const next = {};
|
|
646
|
-
for (const entity of ["agent", "command", "mcp", "skill"]) {
|
|
1032
|
+
for (const entity of ["agent", "command", "mcp", "rule", "skill"]) {
|
|
647
1033
|
const files = value[entity];
|
|
648
1034
|
if (!files || files.length === 0)
|
|
649
1035
|
continue;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const ALL_PROVIDERS: readonly ["cursor", "claude", "codex", "opencode", "gemini", "copilot", "pi"];
|
|
2
2
|
export type Provider = (typeof ALL_PROVIDERS)[number];
|
|
3
3
|
export type Scope = "local" | "global";
|
|
4
|
-
export type EntityType = "agent" | "command" | "mcp" | "skill";
|
|
4
|
+
export type EntityType = "agent" | "command" | "mcp" | "rule" | "skill";
|
|
5
5
|
export type SelectionMode = "all" | "custom";
|
|
6
6
|
export interface AgentFrontmatter {
|
|
7
7
|
name: string;
|
|
@@ -37,6 +37,9 @@ export interface LockEntry {
|
|
|
37
37
|
commandRenameMap?: Record<string, string>;
|
|
38
38
|
importedMcpServers: string[];
|
|
39
39
|
selectedSourceMcpServers?: string[];
|
|
40
|
+
importedRules: string[];
|
|
41
|
+
selectedSourceRules?: string[];
|
|
42
|
+
ruleRenameMap?: Record<string, string>;
|
|
40
43
|
importedSkills: string[];
|
|
41
44
|
selectedSourceSkills?: string[];
|
|
42
45
|
skillsProviders?: Provider[];
|
|
@@ -64,6 +67,7 @@ export interface ScopePaths {
|
|
|
64
67
|
agentsRoot: string;
|
|
65
68
|
agentsDir: string;
|
|
66
69
|
commandsDir: string;
|
|
70
|
+
rulesDir: string;
|
|
67
71
|
skillsDir: string;
|
|
68
72
|
mcpPath: string;
|
|
69
73
|
lockPath: string;
|