agentloom 0.1.4 → 0.1.6
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/agents.js +1 -1
- 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 -5
- package/dist/core/importer.d.ts +10 -0
- package/dist/core/importer.js +629 -13
- package/dist/core/lockfile.js +8 -0
- package/dist/core/manifest.js +123 -6
- package/dist/core/migration.js +655 -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 +7 -1
- 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 +1 -0
- package/dist/core/skills.js +21 -2
- package/dist/core/sources.d.ts +2 -0
- package/dist/core/sources.js +34 -5
- package/dist/core/telemetry.d.ts +1 -1
- package/dist/core/telemetry.js +16 -0
- package/dist/sync/index.js +376 -18
- 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,7 @@ 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);
|
|
25
27
|
const mcp = readCanonicalMcp(options.paths);
|
|
26
28
|
const manifest = readManifest(options.paths);
|
|
27
29
|
const effectiveManifest = {
|
|
@@ -46,6 +48,9 @@ export async function syncFromCanonical(options) {
|
|
|
46
48
|
const generatedAgents = new Set();
|
|
47
49
|
const generatedCommands = new Set();
|
|
48
50
|
const generatedMcp = new Set();
|
|
51
|
+
const generatedRules = new Set();
|
|
52
|
+
const updatedRuleInstructionFiles = new Set();
|
|
53
|
+
const retainedRuleInstructionFiles = new Set();
|
|
49
54
|
if (target === "all" || target === "agent") {
|
|
50
55
|
for (const provider of providers) {
|
|
51
56
|
syncProviderAgents({
|
|
@@ -56,6 +61,13 @@ export async function syncFromCanonical(options) {
|
|
|
56
61
|
dryRun: !!options.dryRun,
|
|
57
62
|
});
|
|
58
63
|
}
|
|
64
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
65
|
+
syncCopilotDiscoverySettings({
|
|
66
|
+
paths: options.paths,
|
|
67
|
+
dryRun: !!options.dryRun,
|
|
68
|
+
includeAgentLocations: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
59
71
|
}
|
|
60
72
|
if (target === "all" || target === "command") {
|
|
61
73
|
for (const provider of providers) {
|
|
@@ -67,6 +79,13 @@ export async function syncFromCanonical(options) {
|
|
|
67
79
|
dryRun: !!options.dryRun,
|
|
68
80
|
});
|
|
69
81
|
}
|
|
82
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
83
|
+
syncCopilotDiscoverySettings({
|
|
84
|
+
paths: options.paths,
|
|
85
|
+
dryRun: !!options.dryRun,
|
|
86
|
+
includePromptLocations: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
70
89
|
}
|
|
71
90
|
if (target === "all" || target === "mcp") {
|
|
72
91
|
syncProviderMcp({
|
|
@@ -77,6 +96,33 @@ export async function syncFromCanonical(options) {
|
|
|
77
96
|
dryRun: !!options.dryRun,
|
|
78
97
|
});
|
|
79
98
|
}
|
|
99
|
+
if (target === "all" || target === "rule") {
|
|
100
|
+
syncManagedRuleInstructions({
|
|
101
|
+
paths: options.paths,
|
|
102
|
+
providers,
|
|
103
|
+
rules,
|
|
104
|
+
generated: generatedRules,
|
|
105
|
+
updated: updatedRuleInstructionFiles,
|
|
106
|
+
retained: retainedRuleInstructionFiles,
|
|
107
|
+
previouslyTracked: new Set(effectiveManifest.generatedByEntity?.rule ?? []),
|
|
108
|
+
dryRun: !!options.dryRun,
|
|
109
|
+
});
|
|
110
|
+
if (providers.includes("cursor") && options.paths.scope === "local") {
|
|
111
|
+
syncCursorRules({
|
|
112
|
+
paths: options.paths,
|
|
113
|
+
rules,
|
|
114
|
+
generated: generatedRules,
|
|
115
|
+
dryRun: !!options.dryRun,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (providers.includes("copilot") && options.paths.scope === "global") {
|
|
119
|
+
syncCopilotDiscoverySettings({
|
|
120
|
+
paths: options.paths,
|
|
121
|
+
dryRun: !!options.dryRun,
|
|
122
|
+
includeInstructionLocations: true,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
80
126
|
if (providers.includes("codex")) {
|
|
81
127
|
const includeRoles = target === "all" || target === "agent";
|
|
82
128
|
const includeMcp = target === "all" || target === "mcp";
|
|
@@ -113,18 +159,23 @@ export async function syncFromCanonical(options) {
|
|
|
113
159
|
if (target === "all" || target === "mcp") {
|
|
114
160
|
nextByEntity.mcp = [...generatedMcp].sort();
|
|
115
161
|
}
|
|
162
|
+
if (target === "all" || target === "rule") {
|
|
163
|
+
nextByEntity.rule = [...generatedRules].sort();
|
|
164
|
+
}
|
|
116
165
|
nextManifest.generatedByEntity = pruneGeneratedByEntity(nextByEntity);
|
|
117
166
|
nextManifest.generatedFiles = [
|
|
118
167
|
...new Set([
|
|
119
168
|
...(nextManifest.generatedByEntity.agent ?? []),
|
|
120
169
|
...(nextManifest.generatedByEntity.command ?? []),
|
|
121
170
|
...(nextManifest.generatedByEntity.mcp ?? []),
|
|
171
|
+
...(nextManifest.generatedByEntity.rule ?? []),
|
|
122
172
|
...(nextManifest.generatedByEntity.skill ?? []),
|
|
123
173
|
]),
|
|
124
174
|
].sort();
|
|
125
175
|
const removedFiles = await removeStaleGeneratedFiles({
|
|
126
176
|
oldManifest: manifest,
|
|
127
177
|
newManifest: nextManifest,
|
|
178
|
+
protectedFiles: retainedRuleInstructionFiles,
|
|
128
179
|
dryRun: !!options.dryRun,
|
|
129
180
|
yes: !!options.yes,
|
|
130
181
|
nonInteractive: !!options.nonInteractive,
|
|
@@ -139,7 +190,12 @@ export async function syncFromCanonical(options) {
|
|
|
139
190
|
}
|
|
140
191
|
return {
|
|
141
192
|
providers,
|
|
142
|
-
generatedFiles:
|
|
193
|
+
generatedFiles: [
|
|
194
|
+
...new Set([
|
|
195
|
+
...nextManifest.generatedFiles,
|
|
196
|
+
...updatedRuleInstructionFiles,
|
|
197
|
+
]),
|
|
198
|
+
].sort(),
|
|
143
199
|
removedFiles,
|
|
144
200
|
};
|
|
145
201
|
}
|
|
@@ -224,7 +280,7 @@ function buildProviderAgentContent(provider, agent, providerConfig) {
|
|
|
224
280
|
description: agent.description,
|
|
225
281
|
...providerConfig,
|
|
226
282
|
};
|
|
227
|
-
const fm = YAML.stringify(frontmatter).trimEnd();
|
|
283
|
+
const fm = YAML.stringify(frontmatter, { lineWidth: 0 }).trimEnd();
|
|
228
284
|
return `---\n${fm}\n---\n\n${agent.body.trimStart()}${agent.body.endsWith("\n") ? "" : "\n"}`;
|
|
229
285
|
}
|
|
230
286
|
function syncProviderCommands(options) {
|
|
@@ -232,15 +288,36 @@ function syncProviderCommands(options) {
|
|
|
232
288
|
for (const command of options.commands) {
|
|
233
289
|
const fileName = mapProviderCommandFileName(options.provider, command.fileName);
|
|
234
290
|
const outputPath = path.join(providerDir, fileName);
|
|
291
|
+
const content = renderCommandForProvider(command, options.provider);
|
|
292
|
+
if (content === null)
|
|
293
|
+
continue;
|
|
235
294
|
if (!options.dryRun) {
|
|
236
295
|
ensureDir(path.dirname(outputPath));
|
|
237
|
-
writeTextAtomic(outputPath,
|
|
296
|
+
writeTextAtomic(outputPath, content);
|
|
238
297
|
}
|
|
239
298
|
options.generated.add(outputPath);
|
|
240
299
|
}
|
|
241
300
|
}
|
|
242
301
|
function mapProviderCommandFileName(provider, fileName) {
|
|
243
302
|
const lower = fileName.toLowerCase();
|
|
303
|
+
if (provider === "gemini") {
|
|
304
|
+
if (lower.endsWith(".toml"))
|
|
305
|
+
return fileName;
|
|
306
|
+
if (lower.endsWith(".prompt.md")) {
|
|
307
|
+
return `${fileName.slice(0, -".prompt.md".length)}.toml`;
|
|
308
|
+
}
|
|
309
|
+
if (lower.endsWith(".md")) {
|
|
310
|
+
return `${fileName.slice(0, -3)}.toml`;
|
|
311
|
+
}
|
|
312
|
+
if (lower.endsWith(".mdc")) {
|
|
313
|
+
return `${fileName.slice(0, -4)}.toml`;
|
|
314
|
+
}
|
|
315
|
+
const ext = path.extname(fileName);
|
|
316
|
+
if (ext) {
|
|
317
|
+
return `${fileName.slice(0, -ext.length)}.toml`;
|
|
318
|
+
}
|
|
319
|
+
return `${fileName}.toml`;
|
|
320
|
+
}
|
|
244
321
|
if (provider === "copilot") {
|
|
245
322
|
if (lower.endsWith(".prompt.md"))
|
|
246
323
|
return fileName;
|
|
@@ -261,6 +338,21 @@ function mapProviderCommandFileName(provider, fileName) {
|
|
|
261
338
|
}
|
|
262
339
|
return fileName;
|
|
263
340
|
}
|
|
341
|
+
function syncCursorRules(options) {
|
|
342
|
+
if (options.paths.scope !== "local") {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const rulesDir = getCursorRulesDir(options.paths);
|
|
346
|
+
for (const rule of options.rules) {
|
|
347
|
+
const outputPath = path.join(rulesDir, `${rule.id}.mdc`);
|
|
348
|
+
const content = renderRuleForCursor(rule);
|
|
349
|
+
if (!options.dryRun) {
|
|
350
|
+
ensureDir(path.dirname(outputPath));
|
|
351
|
+
writeTextAtomic(outputPath, content);
|
|
352
|
+
}
|
|
353
|
+
options.generated.add(outputPath);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
264
356
|
function syncProviderMcp(options) {
|
|
265
357
|
for (const provider of options.providers) {
|
|
266
358
|
if (provider === "codex")
|
|
@@ -276,8 +368,18 @@ function syncProviderMcp(options) {
|
|
|
276
368
|
continue;
|
|
277
369
|
}
|
|
278
370
|
if (provider === "claude") {
|
|
279
|
-
const mcpPath = getClaudeMcpPath(options.paths);
|
|
280
371
|
const settingsPath = getClaudeSettingsPath(options.paths);
|
|
372
|
+
const settings = readClaudeSettingsForSync(options.paths);
|
|
373
|
+
if (options.paths.scope === "global") {
|
|
374
|
+
maybeMigrateClaudeGlobalSettings({
|
|
375
|
+
paths: options.paths,
|
|
376
|
+
settingsPath,
|
|
377
|
+
settings,
|
|
378
|
+
dryRun: options.dryRun,
|
|
379
|
+
});
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const mcpPath = getClaudeMcpPath(options.paths);
|
|
281
383
|
const claudeServers = mapMcpServers(resolved, [
|
|
282
384
|
"type",
|
|
283
385
|
"url",
|
|
@@ -292,7 +394,6 @@ function syncProviderMcp(options) {
|
|
|
292
394
|
}
|
|
293
395
|
maybeWriteJson(mcpPath, { mcpServers: claudeServers }, options.dryRun);
|
|
294
396
|
options.generated.add(mcpPath);
|
|
295
|
-
const settings = readJsonIfExists(settingsPath) ?? {};
|
|
296
397
|
settings.enabledMcpjsonServers = Object.keys(claudeServers).sort();
|
|
297
398
|
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
298
399
|
options.generated.add(settingsPath);
|
|
@@ -396,6 +497,233 @@ function syncProviderMcp(options) {
|
|
|
396
497
|
}
|
|
397
498
|
}
|
|
398
499
|
}
|
|
500
|
+
function syncManagedRuleInstructions(options) {
|
|
501
|
+
const cleanupProviders = options.paths.scope === "global"
|
|
502
|
+
? ["claude", "gemini", "copilot", "opencode"]
|
|
503
|
+
: ALL_PROVIDERS;
|
|
504
|
+
const activeTargets = new Set(getRuleInstructionPaths(options.paths, options.providers));
|
|
505
|
+
if (options.paths.scope === "global" && activeTargets.size === 0) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const cleanupTargets = new Set(getRuleInstructionPaths(options.paths, cleanupProviders));
|
|
509
|
+
for (const targetPath of cleanupTargets) {
|
|
510
|
+
const existing = readTextIfExists(targetPath) ?? "";
|
|
511
|
+
const next = upsertManagedRuleBlocks(existing, activeTargets.has(targetPath) ? options.rules : []);
|
|
512
|
+
const shouldTrackGenerated = activeTargets.has(targetPath) &&
|
|
513
|
+
options.rules.length > 0 &&
|
|
514
|
+
next.trim().length > 0;
|
|
515
|
+
if (shouldTrackGenerated) {
|
|
516
|
+
options.generated.add(targetPath);
|
|
517
|
+
}
|
|
518
|
+
const shouldRetainOnDisk = !shouldTrackGenerated &&
|
|
519
|
+
next.trim().length > 0 &&
|
|
520
|
+
fs.existsSync(targetPath);
|
|
521
|
+
if (shouldRetainOnDisk) {
|
|
522
|
+
options.retained.add(targetPath);
|
|
523
|
+
}
|
|
524
|
+
if (next === existing)
|
|
525
|
+
continue;
|
|
526
|
+
options.updated.add(targetPath);
|
|
527
|
+
if (!options.dryRun) {
|
|
528
|
+
if (next.trim().length === 0) {
|
|
529
|
+
if (!options.previouslyTracked.has(targetPath)) {
|
|
530
|
+
removeFileIfExists(targetPath);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
ensureDir(path.dirname(targetPath));
|
|
535
|
+
writeTextAtomic(targetPath, next);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function syncCopilotDiscoverySettings(options) {
|
|
541
|
+
const settingsPath = getVsCodeSettingsPath(options.paths.homeDir);
|
|
542
|
+
const settings = readVsCodeSettings(settingsPath);
|
|
543
|
+
if (!settings) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (options.includePromptLocations) {
|
|
547
|
+
appendPathSetting(settings, "chat.promptFilesLocations", path.join(options.paths.homeDir, ".copilot", "prompts"));
|
|
548
|
+
}
|
|
549
|
+
if (options.includeAgentLocations) {
|
|
550
|
+
appendPathSetting(settings, "chat.agentFilesLocations", path.join(options.paths.homeDir, ".copilot", "agents"));
|
|
551
|
+
}
|
|
552
|
+
if (options.includeInstructionLocations) {
|
|
553
|
+
appendPathSetting(settings, "chat.instructionsFilesLocations", path.join(options.paths.homeDir, ".copilot", "copilot-instructions.md"));
|
|
554
|
+
}
|
|
555
|
+
maybeWriteJson(settingsPath, settings, options.dryRun);
|
|
556
|
+
}
|
|
557
|
+
function readVsCodeSettings(settingsPath) {
|
|
558
|
+
const raw = readTextIfExists(settingsPath);
|
|
559
|
+
if (raw === null) {
|
|
560
|
+
return {};
|
|
561
|
+
}
|
|
562
|
+
const parsed = parseJsonOrJsonc(raw);
|
|
563
|
+
if (!isObject(parsed)) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
return parsed;
|
|
567
|
+
}
|
|
568
|
+
function readClaudeSettingsForSync(paths) {
|
|
569
|
+
const settingsPath = getClaudeSettingsPath(paths);
|
|
570
|
+
const settings = readJsonIfExists(settingsPath);
|
|
571
|
+
if (isObject(settings)) {
|
|
572
|
+
return { ...settings };
|
|
573
|
+
}
|
|
574
|
+
if (paths.scope === "global") {
|
|
575
|
+
const legacySettingsPath = path.join(paths.homeDir, ".claude.json");
|
|
576
|
+
const legacySettings = readJsonIfExists(legacySettingsPath);
|
|
577
|
+
if (isObject(legacySettings)) {
|
|
578
|
+
return { ...legacySettings };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return {};
|
|
582
|
+
}
|
|
583
|
+
function maybeMigrateClaudeGlobalSettings(options) {
|
|
584
|
+
const nextSettings = { ...options.settings };
|
|
585
|
+
delete nextSettings.enabledMcpjsonServers;
|
|
586
|
+
const legacySettingsPath = path.join(options.paths.homeDir, ".claude.json");
|
|
587
|
+
const hasCurrentSettings = fs.existsSync(options.settingsPath) &&
|
|
588
|
+
fs.statSync(options.settingsPath).isFile();
|
|
589
|
+
const hasLegacySettings = fs.existsSync(legacySettingsPath) &&
|
|
590
|
+
fs.statSync(legacySettingsPath).isFile();
|
|
591
|
+
const shouldWrite = hasCurrentSettings ||
|
|
592
|
+
(hasLegacySettings && Object.keys(nextSettings).length > 0);
|
|
593
|
+
if (shouldWrite) {
|
|
594
|
+
maybeWriteJson(options.settingsPath, nextSettings, options.dryRun);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function parseJsonOrJsonc(input) {
|
|
598
|
+
if (input.trim() === "") {
|
|
599
|
+
return {};
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
return JSON.parse(input);
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
try {
|
|
606
|
+
const withoutComments = stripJsonComments(input);
|
|
607
|
+
const normalized = stripTrailingJsonCommas(withoutComments);
|
|
608
|
+
if (normalized.trim() === "") {
|
|
609
|
+
return {};
|
|
610
|
+
}
|
|
611
|
+
return JSON.parse(normalized);
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function stripJsonComments(input) {
|
|
619
|
+
let result = "";
|
|
620
|
+
let inString = false;
|
|
621
|
+
let inLineComment = false;
|
|
622
|
+
let inBlockComment = false;
|
|
623
|
+
let escaped = false;
|
|
624
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
625
|
+
const char = input[i];
|
|
626
|
+
const next = i + 1 < input.length ? input[i + 1] : "";
|
|
627
|
+
if (inLineComment) {
|
|
628
|
+
if (char === "\n") {
|
|
629
|
+
inLineComment = false;
|
|
630
|
+
result += char;
|
|
631
|
+
}
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (inBlockComment) {
|
|
635
|
+
if (char === "*" && next === "/") {
|
|
636
|
+
inBlockComment = false;
|
|
637
|
+
i += 1;
|
|
638
|
+
}
|
|
639
|
+
else if (char === "\n") {
|
|
640
|
+
result += char;
|
|
641
|
+
}
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (inString) {
|
|
645
|
+
result += char;
|
|
646
|
+
if (escaped) {
|
|
647
|
+
escaped = false;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (char === "\\") {
|
|
651
|
+
escaped = true;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (char === '"') {
|
|
655
|
+
inString = false;
|
|
656
|
+
}
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (char === '"') {
|
|
660
|
+
inString = true;
|
|
661
|
+
result += char;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (char === "/" && next === "/") {
|
|
665
|
+
inLineComment = true;
|
|
666
|
+
i += 1;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (char === "/" && next === "*") {
|
|
670
|
+
inBlockComment = true;
|
|
671
|
+
i += 1;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
result += char;
|
|
675
|
+
}
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
function stripTrailingJsonCommas(input) {
|
|
679
|
+
let result = "";
|
|
680
|
+
let inString = false;
|
|
681
|
+
let escaped = false;
|
|
682
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
683
|
+
const char = input[i];
|
|
684
|
+
if (inString) {
|
|
685
|
+
result += char;
|
|
686
|
+
if (escaped) {
|
|
687
|
+
escaped = false;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (char === "\\") {
|
|
691
|
+
escaped = true;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (char === '"') {
|
|
695
|
+
inString = false;
|
|
696
|
+
}
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (char === '"') {
|
|
700
|
+
inString = true;
|
|
701
|
+
result += char;
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (char === ",") {
|
|
705
|
+
let lookahead = i + 1;
|
|
706
|
+
while (lookahead < input.length && /\s/.test(input[lookahead] ?? "")) {
|
|
707
|
+
lookahead += 1;
|
|
708
|
+
}
|
|
709
|
+
const next = input[lookahead];
|
|
710
|
+
if (next === "}" || next === "]") {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
result += char;
|
|
715
|
+
}
|
|
716
|
+
return result;
|
|
717
|
+
}
|
|
718
|
+
function appendPathSetting(settings, key, settingPath) {
|
|
719
|
+
const existing = Array.isArray(settings[key])
|
|
720
|
+
? settings[key].filter((value) => typeof value === "string" && value.trim() !== "")
|
|
721
|
+
: [];
|
|
722
|
+
if (!existing.includes(settingPath)) {
|
|
723
|
+
existing.push(settingPath);
|
|
724
|
+
}
|
|
725
|
+
settings[key] = existing;
|
|
726
|
+
}
|
|
399
727
|
function syncCodex(options) {
|
|
400
728
|
const codexDir = getCodexRootDir(options.paths);
|
|
401
729
|
const codexConfigPath = getCodexConfigPath(options.paths);
|
|
@@ -437,10 +765,11 @@ function syncCodex(options) {
|
|
|
437
765
|
continue;
|
|
438
766
|
const roleTomlPath = path.join(codexAgentsDir, `${role}.toml`);
|
|
439
767
|
const roleInstructionsPath = path.join(codexAgentsDir, `${role}.instructions.md`);
|
|
440
|
-
const
|
|
768
|
+
const developerInstructions = resolveCodexDeveloperInstructions(agent.body, codexConfig);
|
|
769
|
+
const roleToml = buildCodexRoleToml(developerInstructions, codexConfig);
|
|
441
770
|
if (!options.dryRun) {
|
|
442
771
|
ensureDir(codexAgentsDir);
|
|
443
|
-
writeTextAtomic(roleInstructionsPath, `${
|
|
772
|
+
writeTextAtomic(roleInstructionsPath, `${developerInstructions}\n`);
|
|
444
773
|
writeTextAtomic(roleTomlPath, TOML.stringify(roleToml));
|
|
445
774
|
}
|
|
446
775
|
options.generated.add(roleTomlPath);
|
|
@@ -490,9 +819,16 @@ function resolveTrackedCodexEntries(trackedEntries, fallbackEntries) {
|
|
|
490
819
|
const tracked = Array.isArray(trackedEntries) ? trackedEntries : [];
|
|
491
820
|
return [...new Set([...tracked, ...fallbackEntries])].sort();
|
|
492
821
|
}
|
|
493
|
-
function
|
|
822
|
+
function resolveCodexDeveloperInstructions(agentBody, providerConfig) {
|
|
823
|
+
if (typeof providerConfig.developerInstructions === "string" &&
|
|
824
|
+
providerConfig.developerInstructions.trim() !== "") {
|
|
825
|
+
return providerConfig.developerInstructions.trim();
|
|
826
|
+
}
|
|
827
|
+
return agentBody.trimStart().trimEnd();
|
|
828
|
+
}
|
|
829
|
+
function buildCodexRoleToml(developerInstructions, providerConfig) {
|
|
494
830
|
const roleToml = {
|
|
495
|
-
|
|
831
|
+
developer_instructions: developerInstructions,
|
|
496
832
|
};
|
|
497
833
|
if (typeof providerConfig.model === "string") {
|
|
498
834
|
roleToml.model = providerConfig.model;
|
|
@@ -500,6 +836,12 @@ function buildCodexRoleToml(roleInstructionsPath, providerConfig) {
|
|
|
500
836
|
if (typeof providerConfig.reasoningEffort === "string") {
|
|
501
837
|
roleToml.model_reasoning_effort = providerConfig.reasoningEffort;
|
|
502
838
|
}
|
|
839
|
+
if (typeof providerConfig.reasoningSummary === "string") {
|
|
840
|
+
roleToml.model_reasoning_summary = providerConfig.reasoningSummary;
|
|
841
|
+
}
|
|
842
|
+
if (typeof providerConfig.verbosity === "string") {
|
|
843
|
+
roleToml.model_verbosity = providerConfig.verbosity;
|
|
844
|
+
}
|
|
503
845
|
if (typeof providerConfig.approvalPolicy === "string") {
|
|
504
846
|
roleToml.approval_policy = providerConfig.approvalPolicy;
|
|
505
847
|
}
|
|
@@ -507,9 +849,7 @@ function buildCodexRoleToml(roleInstructionsPath, providerConfig) {
|
|
|
507
849
|
roleToml.sandbox_mode = providerConfig.sandboxMode;
|
|
508
850
|
}
|
|
509
851
|
if (typeof providerConfig.webSearch === "boolean") {
|
|
510
|
-
roleToml.
|
|
511
|
-
web_search: providerConfig.webSearch,
|
|
512
|
-
};
|
|
852
|
+
roleToml.web_search = providerConfig.webSearch;
|
|
513
853
|
}
|
|
514
854
|
return roleToml;
|
|
515
855
|
}
|
|
@@ -536,8 +876,10 @@ function maybeWriteJson(filePath, payload, dryRun) {
|
|
|
536
876
|
async function removeStaleGeneratedFiles(options) {
|
|
537
877
|
const oldSet = new Set(options.oldManifest.generatedFiles);
|
|
538
878
|
const newSet = new Set(options.newManifest.generatedFiles);
|
|
879
|
+
const protectedSet = new Set(options.protectedFiles ?? []);
|
|
539
880
|
const stale = [...oldSet]
|
|
540
881
|
.filter((filePath) => !newSet.has(filePath))
|
|
882
|
+
.filter((filePath) => !protectedSet.has(filePath))
|
|
541
883
|
.filter((filePath) => fs.existsSync(filePath));
|
|
542
884
|
if (stale.length === 0)
|
|
543
885
|
return [];
|
|
@@ -574,6 +916,7 @@ function normalizeGeneratedByEntity(manifest) {
|
|
|
574
916
|
agent: Array.isArray(source.agent) ? [...source.agent] : [],
|
|
575
917
|
command: Array.isArray(source.command) ? [...source.command] : [],
|
|
576
918
|
mcp: Array.isArray(source.mcp) ? [...source.mcp] : [],
|
|
919
|
+
rule: Array.isArray(source.rule) ? [...source.rule] : [],
|
|
577
920
|
skill: Array.isArray(source.skill) ? [...source.skill] : [],
|
|
578
921
|
};
|
|
579
922
|
}
|
|
@@ -582,6 +925,7 @@ function inferGeneratedByEntityFromLegacyFiles(generatedFiles) {
|
|
|
582
925
|
agent: [],
|
|
583
926
|
command: [],
|
|
584
927
|
mcp: [],
|
|
928
|
+
rule: [],
|
|
585
929
|
skill: [],
|
|
586
930
|
};
|
|
587
931
|
for (const filePath of generatedFiles) {
|
|
@@ -605,31 +949,45 @@ function classifyLegacyGeneratedFile(filePath) {
|
|
|
605
949
|
if (isLegacyMcpOutputPath(normalized)) {
|
|
606
950
|
return ["mcp"];
|
|
607
951
|
}
|
|
952
|
+
if (isLegacyRuleOutputPath(normalized)) {
|
|
953
|
+
return ["agent", "rule"];
|
|
954
|
+
}
|
|
608
955
|
// Preserve unknown generated paths during scoped syncs.
|
|
609
|
-
return ["agent", "command", "mcp"];
|
|
956
|
+
return ["agent", "command", "mcp", "rule"];
|
|
610
957
|
}
|
|
611
958
|
function isLegacyCommandOutputPath(normalizedPath) {
|
|
612
959
|
return (normalizedPath.includes("/.cursor/commands/") ||
|
|
613
960
|
normalizedPath.includes("/.claude/commands/") ||
|
|
614
961
|
normalizedPath.includes("/.opencode/commands/") ||
|
|
615
962
|
normalizedPath.includes("/.gemini/commands/") ||
|
|
963
|
+
normalizedPath.includes("/.copilot/prompts/") ||
|
|
616
964
|
normalizedPath.includes("/.github/prompts/") ||
|
|
617
965
|
normalizedPath.includes("/.codex/prompts/") ||
|
|
618
966
|
normalizedPath.includes("/.pi/prompts/"));
|
|
619
967
|
}
|
|
620
968
|
function isLegacyAgentOutputPath(normalizedPath) {
|
|
621
969
|
return (normalizedPath.includes("/.cursor/agents/") ||
|
|
622
|
-
normalizedPath.includes("/.cursor/rules/") ||
|
|
623
970
|
normalizedPath.includes("/.claude/agents/") ||
|
|
624
971
|
normalizedPath.includes("/.opencode/agents/") ||
|
|
625
972
|
normalizedPath.includes("/.gemini/agents/") ||
|
|
973
|
+
normalizedPath.includes("/.copilot/agents/") ||
|
|
626
974
|
normalizedPath.includes("/.github/agents/") ||
|
|
627
975
|
normalizedPath.includes("/.codex/agents/") ||
|
|
628
976
|
normalizedPath.includes("/.pi/agents/"));
|
|
629
977
|
}
|
|
978
|
+
function isLegacyRuleOutputPath(normalizedPath) {
|
|
979
|
+
return (normalizedPath.includes("/.cursor/rules/") ||
|
|
980
|
+
normalizedPath.endsWith("/agents.md") ||
|
|
981
|
+
normalizedPath.endsWith("/claude.md") ||
|
|
982
|
+
normalizedPath.endsWith("/gemini.md") ||
|
|
983
|
+
normalizedPath.endsWith("/.github/copilot-instructions.md") ||
|
|
984
|
+
normalizedPath.endsWith("/.copilot/copilot-instructions.md") ||
|
|
985
|
+
normalizedPath.endsWith("/.config/opencode/agents.md"));
|
|
986
|
+
}
|
|
630
987
|
function isLegacyMcpOutputPath(normalizedPath) {
|
|
631
988
|
return (normalizedPath.endsWith("/.cursor/mcp.json") ||
|
|
632
989
|
normalizedPath.endsWith("/.mcp.json") ||
|
|
990
|
+
normalizedPath.endsWith("/.claude.json") ||
|
|
633
991
|
normalizedPath.endsWith("/.claude/settings.json") ||
|
|
634
992
|
normalizedPath.endsWith("/.opencode/opencode.json") ||
|
|
635
993
|
normalizedPath.endsWith("/.gemini/settings.json") ||
|
|
@@ -643,7 +1001,7 @@ function isLegacyCodexConfigPath(normalizedPath) {
|
|
|
643
1001
|
}
|
|
644
1002
|
function pruneGeneratedByEntity(value) {
|
|
645
1003
|
const next = {};
|
|
646
|
-
for (const entity of ["agent", "command", "mcp", "skill"]) {
|
|
1004
|
+
for (const entity of ["agent", "command", "mcp", "rule", "skill"]) {
|
|
647
1005
|
const files = value[entity];
|
|
648
1006
|
if (!files || files.length === 0)
|
|
649
1007
|
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;
|