rulesync 35.0.0 → 36.0.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/README.md +2 -2
- package/dist/cli/index.cjs +447 -15
- package/dist/cli/index.js +447 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D11JFP84.cjs → import-DVcemXlR.cjs} +1167 -213
- package/dist/{import-BbF_R5Qx.js → import-Tya9qZRK.js} +1146 -198
- package/dist/{import-BbF_R5Qx.js.map → import-Tya9qZRK.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -457,36 +457,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
457
457
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
458
458
|
//#endregion
|
|
459
459
|
//#region src/constants/rulesync-paths.ts
|
|
460
|
-
const { join: join$
|
|
460
|
+
const { join: join$422 } = node_path.posix;
|
|
461
461
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
462
462
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
463
463
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
464
464
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
465
465
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
466
466
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
467
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
467
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$422(RULES_FEATURE_SUBDIR, ".curated");
|
|
468
468
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
469
469
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
470
470
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
471
471
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
472
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
473
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
475
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
477
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
478
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
480
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
481
|
-
join$
|
|
482
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
483
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$422(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
473
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
475
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
477
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
478
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
479
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
480
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
481
|
+
join$422(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
482
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
483
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
484
484
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
485
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
485
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
486
486
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
487
487
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
488
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
489
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
488
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
489
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
490
490
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
491
491
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
492
492
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -699,6 +699,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
699
699
|
"hermesagent",
|
|
700
700
|
"junie",
|
|
701
701
|
"kilo",
|
|
702
|
+
"kimi-code",
|
|
702
703
|
"kimi-code-plugin",
|
|
703
704
|
"kiro",
|
|
704
705
|
"kiro-cli",
|
|
@@ -8173,7 +8174,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8173
8174
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
8174
8175
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
8175
8176
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8176
|
-
* `todoWrite`, `
|
|
8177
|
+
* `todoWrite`, `codeModeOnly`, `freeform`, `workflowsEnabled`, `workflowNameOnly`,
|
|
8178
|
+
* `executionSandbox`) and
|
|
8177
8179
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8178
8180
|
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8179
8181
|
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
@@ -8192,7 +8194,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8192
8194
|
* changes what the file said is reported in either scope, naming what that key
|
|
8193
8195
|
* decides there — the autonomy and containment
|
|
8194
8196
|
* controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
|
|
8195
|
-
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8197
|
+
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8198
|
+
* `codeModeOnly`, `freeform`, and `eager`, which
|
|
8196
8199
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8197
8200
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8198
8201
|
* classifier config, the skill kill-switches, and, because these groups are
|
|
@@ -10885,13 +10888,15 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
10885
10888
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
10886
10889
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10887
10890
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10888
|
-
metadata: zod_mini.z.optional(zod_mini.z.unknown())
|
|
10891
|
+
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10892
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
10889
10893
|
})),
|
|
10890
10894
|
mimocode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10891
10895
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
10892
10896
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10893
10897
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10894
|
-
metadata: zod_mini.z.optional(zod_mini.z.unknown())
|
|
10898
|
+
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10899
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
10895
10900
|
})),
|
|
10896
10901
|
kilo: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10897
10902
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -11344,7 +11349,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11344
11349
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11345
11350
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11346
11351
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11347
|
-
* commandcode, lettacode). Each tool's own section may override that
|
|
11352
|
+
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11348
11353
|
* default with a per-target value. A defined section value (including `false`)
|
|
11349
11354
|
* always wins over the root default.
|
|
11350
11355
|
*
|
|
@@ -13543,8 +13548,8 @@ function diffSharedConfigOwnedKeys({ fileKey, existingContent, newContent }) {
|
|
|
13543
13548
|
return operations;
|
|
13544
13549
|
}
|
|
13545
13550
|
const READ_TOOL_NAME = "Read";
|
|
13546
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${
|
|
13547
|
-
const buildReadDenyEntry = (pattern) => `${
|
|
13551
|
+
const isReadDenyEntry = (entry, readToolName = READ_TOOL_NAME) => entry.startsWith(`${readToolName}(`) && entry.endsWith(")");
|
|
13552
|
+
const buildReadDenyEntry = (pattern, readToolName = READ_TOOL_NAME) => `${readToolName}(${pattern})`;
|
|
13548
13553
|
const parsePermissionsBlock = (settings) => {
|
|
13549
13554
|
const permissions = settings.permissions ?? {};
|
|
13550
13555
|
return {
|
|
@@ -13568,9 +13573,9 @@ const withPermissions = (settings, next) => {
|
|
|
13568
13573
|
};
|
|
13569
13574
|
};
|
|
13570
13575
|
const applyIgnoreReadDenies = (params) => {
|
|
13571
|
-
const { settings, readDenies } = params;
|
|
13576
|
+
const { settings, readDenies, readToolName = READ_TOOL_NAME } = params;
|
|
13572
13577
|
const current = parsePermissionsBlock(settings);
|
|
13573
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
13578
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry, readToolName) || readDenies.includes(entry));
|
|
13574
13579
|
return withPermissions(settings, {
|
|
13575
13580
|
allow: current.allow,
|
|
13576
13581
|
ask: current.ask,
|
|
@@ -17897,6 +17902,7 @@ const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
|
17897
17902
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
17898
17903
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
17899
17904
|
const ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME = "plugin.json";
|
|
17905
|
+
const ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS = [".antigravity-plugin", ".gemini-plugin"];
|
|
17900
17906
|
const AUGMENTCODE_PLUGIN_RULES_DIR = "rules";
|
|
17901
17907
|
const AUGMENTCODE_PLUGIN_COMMANDS_DIR = "commands";
|
|
17902
17908
|
const AUGMENTCODE_PLUGIN_AGENTS_DIR = "agents";
|
|
@@ -19543,9 +19549,36 @@ function commandSlug(relativeFilePath) {
|
|
|
19543
19549
|
* skill. Once the command is removed from every source tree's `commands/`
|
|
19544
19550
|
* directory, the directory stops matching and the skills feature cleans
|
|
19545
19551
|
* it up as a regular orphan.
|
|
19552
|
+
*
|
|
19553
|
+
* When `toolTarget` is given, only commands whose `targets` include it (or
|
|
19554
|
+
* `*`) count: a command aimed at other tools never reaches this tool's skills
|
|
19555
|
+
* tree, so it must not shadow a same-named skill there. Commands that cannot
|
|
19556
|
+
* be parsed are skipped, as the commands feature reports them itself.
|
|
19546
19557
|
*/
|
|
19547
|
-
async function rulesyncCommandSlugExists({ inputRoots, dirName }) {
|
|
19548
|
-
|
|
19558
|
+
async function rulesyncCommandSlugExists({ inputRoots, dirName, toolTarget }) {
|
|
19559
|
+
const matching = (await Promise.all(inputRoots.map(async (root) => {
|
|
19560
|
+
const commandsDirPath = (0, node_path.join)(root, COMMANDS_FEATURE_SUBDIR);
|
|
19561
|
+
return (await findFilesByGlobs("**/*.md", { cwd: commandsDirPath })).map((filePath) => ({
|
|
19562
|
+
root,
|
|
19563
|
+
commandsDirPath,
|
|
19564
|
+
filePath
|
|
19565
|
+
}));
|
|
19566
|
+
}))).flat().filter(({ filePath }) => commandSlug((0, node_path.basename)(filePath)) === dirName);
|
|
19567
|
+
if (toolTarget === void 0) return matching.length > 0;
|
|
19568
|
+
for (const { root, commandsDirPath, filePath } of matching) {
|
|
19569
|
+
let targets;
|
|
19570
|
+
try {
|
|
19571
|
+
targets = (await RulesyncCommand.fromFile({
|
|
19572
|
+
outputRoot: root,
|
|
19573
|
+
relativeDirPath: COMMANDS_FEATURE_SUBDIR,
|
|
19574
|
+
relativeFilePath: (0, node_path.relative)(commandsDirPath, filePath)
|
|
19575
|
+
})).getFrontmatter().targets;
|
|
19576
|
+
} catch {
|
|
19577
|
+
continue;
|
|
19578
|
+
}
|
|
19579
|
+
if (targets.includes("*") || targets.includes(toolTarget)) return true;
|
|
19580
|
+
}
|
|
19581
|
+
return false;
|
|
19549
19582
|
}
|
|
19550
19583
|
//#endregion
|
|
19551
19584
|
//#region src/features/commands/devin-command.ts
|
|
@@ -19563,7 +19596,7 @@ const DEVIN_COMMAND_SECTION_KEYS = [
|
|
|
19563
19596
|
"permissions",
|
|
19564
19597
|
"triggers"
|
|
19565
19598
|
];
|
|
19566
|
-
function commandSkillContent$
|
|
19599
|
+
function commandSkillContent$2(rulesyncCommand) {
|
|
19567
19600
|
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
19568
19601
|
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
19569
19602
|
const description = frontmatter.description ?? `${slug} command`;
|
|
@@ -19637,7 +19670,7 @@ var DevinCommand = class DevinCommand extends ToolCommand {
|
|
|
19637
19670
|
relativeDirPath: paths.relativeDirPath,
|
|
19638
19671
|
relativeFilePath: SKILL_FILE_NAME,
|
|
19639
19672
|
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
19640
|
-
fileContent: commandSkillContent$
|
|
19673
|
+
fileContent: commandSkillContent$2(rulesyncCommand)
|
|
19641
19674
|
});
|
|
19642
19675
|
}
|
|
19643
19676
|
getFileContent() {
|
|
@@ -21457,6 +21490,180 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
21457
21490
|
}
|
|
21458
21491
|
};
|
|
21459
21492
|
//#endregion
|
|
21493
|
+
//#region src/constants/kimi-code-paths.ts
|
|
21494
|
+
const KIMI_CODE_DIR = ".kimi-code";
|
|
21495
|
+
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
21496
|
+
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
21497
|
+
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
21498
|
+
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
21499
|
+
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
21500
|
+
const KIMI_CODE_SKILLS_DIR_PATH = (0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
21501
|
+
(0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
21502
|
+
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
21503
|
+
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = (0, node_path.join)(".agents", "agents");
|
|
21504
|
+
//#endregion
|
|
21505
|
+
//#region src/utils/kimi-code.ts
|
|
21506
|
+
function getKimiCodeHome() {
|
|
21507
|
+
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
21508
|
+
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
21509
|
+
}
|
|
21510
|
+
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
21511
|
+
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
21512
|
+
}
|
|
21513
|
+
/**
|
|
21514
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
21515
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
21516
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
21517
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
21518
|
+
*/
|
|
21519
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
21520
|
+
return [{
|
|
21521
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
21522
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21523
|
+
}, {
|
|
21524
|
+
relativeDirPath: ".",
|
|
21525
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21526
|
+
}];
|
|
21527
|
+
}
|
|
21528
|
+
/**
|
|
21529
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
21530
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
21531
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
21532
|
+
*/
|
|
21533
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
21534
|
+
return sharedConfigFileKey({
|
|
21535
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
21536
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21537
|
+
});
|
|
21538
|
+
}
|
|
21539
|
+
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
21540
|
+
return getToolRulesyncOutputRoot({
|
|
21541
|
+
nativeOutputRoot,
|
|
21542
|
+
global,
|
|
21543
|
+
toolHome: getKimiCodeHome
|
|
21544
|
+
});
|
|
21545
|
+
}
|
|
21546
|
+
//#endregion
|
|
21547
|
+
//#region src/features/commands/kimi-code-command.ts
|
|
21548
|
+
/**
|
|
21549
|
+
* Kimi Code SKILL.md frontmatter keys a command may author through its
|
|
21550
|
+
* `kimi-code:` section, each with the alias spellings Kimi also accepts. An
|
|
21551
|
+
* alias is normalized to the canonical key; the canonical spelling wins when
|
|
21552
|
+
* both are present.
|
|
21553
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21554
|
+
*/
|
|
21555
|
+
const KIMI_CODE_COMMAND_SECTION_KEYS = [
|
|
21556
|
+
{
|
|
21557
|
+
key: "type",
|
|
21558
|
+
aliases: []
|
|
21559
|
+
},
|
|
21560
|
+
{
|
|
21561
|
+
key: "whenToUse",
|
|
21562
|
+
aliases: ["when-to-use", "when_to_use"]
|
|
21563
|
+
},
|
|
21564
|
+
{
|
|
21565
|
+
key: "disableModelInvocation",
|
|
21566
|
+
aliases: ["disable-model-invocation", "disable_model_invocation"]
|
|
21567
|
+
},
|
|
21568
|
+
{
|
|
21569
|
+
key: "arguments",
|
|
21570
|
+
aliases: []
|
|
21571
|
+
}
|
|
21572
|
+
];
|
|
21573
|
+
function pickKimiCodeSectionKeys(section) {
|
|
21574
|
+
const picked = {};
|
|
21575
|
+
for (const { key, aliases } of KIMI_CODE_COMMAND_SECTION_KEYS) {
|
|
21576
|
+
const spelling = [key, ...aliases].find((candidate) => section[candidate] !== void 0);
|
|
21577
|
+
if (spelling !== void 0) picked[key] = section[spelling];
|
|
21578
|
+
}
|
|
21579
|
+
return picked;
|
|
21580
|
+
}
|
|
21581
|
+
function commandSkillContent$1(rulesyncCommand) {
|
|
21582
|
+
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
21583
|
+
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
21584
|
+
const description = frontmatter.description?.trim() || `${slug} command`;
|
|
21585
|
+
return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
|
|
21586
|
+
name: slug,
|
|
21587
|
+
description,
|
|
21588
|
+
disableModelInvocation: true,
|
|
21589
|
+
...pickKimiCodeSectionKeys(frontmatter["kimi-code"] ?? {})
|
|
21590
|
+
});
|
|
21591
|
+
}
|
|
21592
|
+
/**
|
|
21593
|
+
* Represents a Kimi Code slash command, emitted as a Kimi Code Skill.
|
|
21594
|
+
*
|
|
21595
|
+
* Kimi Code has no standalone custom-command directory outside plugins: every
|
|
21596
|
+
* external Skill is registered as a slash command (`/skill:<name>`, with a
|
|
21597
|
+
* `/<name>` shorthand when the bare name is free), and the Skill body supports
|
|
21598
|
+
* `$ARGUMENTS`, positional `$0` / `$1`, and named `$<name>` placeholders.
|
|
21599
|
+
* Commands are therefore emitted onto the native skills surface, one
|
|
21600
|
+
* `SKILL.md` per command: `.kimi-code/skills/<slug>/SKILL.md` (project) and
|
|
21601
|
+
* `~/.kimi-code/skills/<slug>/SKILL.md` (global; `$KIMI_CODE_HOME/skills/`
|
|
21602
|
+
* when `KIMI_CODE_HOME` is set). Directory-form `SKILL.md` requires both
|
|
21603
|
+
* `name` and `description`, so both are always written.
|
|
21604
|
+
*
|
|
21605
|
+
* Import and deletion are intentionally no-ops for this target: the skills
|
|
21606
|
+
* feature owns the `.kimi-code/skills/` tree, so importing it as commands
|
|
21607
|
+
* would double-import every skill (mirrors the Devin, Warp and Hermes Agent
|
|
21608
|
+
* commands targets).
|
|
21609
|
+
*
|
|
21610
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21611
|
+
* @see https://moonshotai.github.io/kimi-code/en/reference/slash-commands.html
|
|
21612
|
+
*/
|
|
21613
|
+
var KimiCodeCommand = class KimiCodeCommand extends ToolCommand {
|
|
21614
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
21615
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
21616
|
+
rulesyncCommand,
|
|
21617
|
+
toolTarget: "kimi-code"
|
|
21618
|
+
});
|
|
21619
|
+
}
|
|
21620
|
+
static getSettablePaths({ global = false } = {}) {
|
|
21621
|
+
return { relativeDirPath: getKimiCodeRelativeDirPath({
|
|
21622
|
+
global,
|
|
21623
|
+
relativeDirPath: KIMI_CODE_SKILLS_DIR_NAME
|
|
21624
|
+
}) };
|
|
21625
|
+
}
|
|
21626
|
+
constructor({ slug, ...params }) {
|
|
21627
|
+
super({
|
|
21628
|
+
...params,
|
|
21629
|
+
...slug !== void 0 && {
|
|
21630
|
+
relativeDirPath: (0, node_path.join)(params.relativeDirPath, slug),
|
|
21631
|
+
relativeFilePath: "SKILL.md"
|
|
21632
|
+
}
|
|
21633
|
+
});
|
|
21634
|
+
}
|
|
21635
|
+
validate() {
|
|
21636
|
+
return {
|
|
21637
|
+
success: true,
|
|
21638
|
+
error: null
|
|
21639
|
+
};
|
|
21640
|
+
}
|
|
21641
|
+
toRulesyncCommand() {
|
|
21642
|
+
const slug = (0, node_path.basename)((0, node_path.dirname)(this.getRelativePathFromCwd()));
|
|
21643
|
+
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
21644
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
21645
|
+
return new RulesyncCommand({
|
|
21646
|
+
relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
|
|
21647
|
+
relativeFilePath: `${slug}.md`,
|
|
21648
|
+
frontmatter: { description },
|
|
21649
|
+
body: body.trimStart()
|
|
21650
|
+
});
|
|
21651
|
+
}
|
|
21652
|
+
static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
|
|
21653
|
+
const paths = KimiCodeCommand.getSettablePaths({ global });
|
|
21654
|
+
return new KimiCodeCommand({
|
|
21655
|
+
outputRoot,
|
|
21656
|
+
relativeDirPath: paths.relativeDirPath,
|
|
21657
|
+
relativeFilePath: SKILL_FILE_NAME,
|
|
21658
|
+
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
21659
|
+
fileContent: commandSkillContent$1(rulesyncCommand)
|
|
21660
|
+
});
|
|
21661
|
+
}
|
|
21662
|
+
getFileContent() {
|
|
21663
|
+
return this.fileContent;
|
|
21664
|
+
}
|
|
21665
|
+
};
|
|
21666
|
+
//#endregion
|
|
21460
21667
|
//#region src/features/commands/kimi-code-plugin-command.ts
|
|
21461
21668
|
/**
|
|
21462
21669
|
* Kimi Code reads only `name` and `description` from a plugin command. The
|
|
@@ -21611,6 +21818,14 @@ const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
|
|
|
21611
21818
|
const KIRO_MCP_FILE_NAME = "mcp.json";
|
|
21612
21819
|
const KIRO_IGNORE_FILE_NAME = ".kiroignore";
|
|
21613
21820
|
const KIRO_GLOBAL_IGNORE_FILE_NAME = "kiroignore";
|
|
21821
|
+
/**
|
|
21822
|
+
* Kiro's capability-based permission rules (IDE 1.0, CLI V3). The user-level
|
|
21823
|
+
* file lives at `~/.kiro/settings/permissions.yaml`; the workspace-level file
|
|
21824
|
+
* sits under a hash-derived `~/.kiro/workspace-roots/<hash>/` directory outside
|
|
21825
|
+
* the repository, so only the user-level file is generated.
|
|
21826
|
+
* @see https://kiro.dev/docs/permissions/
|
|
21827
|
+
*/
|
|
21828
|
+
const KIRO_PERMISSIONS_FILE_NAME = "permissions.yaml";
|
|
21614
21829
|
//#endregion
|
|
21615
21830
|
//#region src/features/commands/kiro-command.ts
|
|
21616
21831
|
var KiroCommand = class KiroCommand extends ToolCommand {
|
|
@@ -23653,6 +23868,11 @@ const TraeCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
23653
23868
|
name: zod_mini.z.optional(zod_mini.z.string())
|
|
23654
23869
|
});
|
|
23655
23870
|
/**
|
|
23871
|
+
* Trae reads project commands only up to three directory levels below
|
|
23872
|
+
* `.trae/commands/`; a command file nested deeper is never picked up.
|
|
23873
|
+
*/
|
|
23874
|
+
const TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH = 3;
|
|
23875
|
+
/**
|
|
23656
23876
|
* Custom command for Trae.
|
|
23657
23877
|
*
|
|
23658
23878
|
* Trae reads Markdown command files under `.trae/commands/` (project, up to
|
|
@@ -23712,10 +23932,11 @@ var TraeCommand = class TraeCommand extends ToolCommand {
|
|
|
23712
23932
|
}
|
|
23713
23933
|
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
23714
23934
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
23935
|
+
const { name: _name, ...traeFields } = rulesyncFrontmatter.trae ?? {};
|
|
23715
23936
|
const traeFrontmatter = {
|
|
23716
|
-
name:
|
|
23937
|
+
name: this.deriveName(rulesyncCommand),
|
|
23717
23938
|
description: rulesyncFrontmatter.description,
|
|
23718
|
-
...
|
|
23939
|
+
...traeFields
|
|
23719
23940
|
};
|
|
23720
23941
|
return new TraeCommand({
|
|
23721
23942
|
outputRoot,
|
|
@@ -23741,6 +23962,36 @@ var TraeCommand = class TraeCommand extends ToolCommand {
|
|
|
23741
23962
|
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
23742
23963
|
};
|
|
23743
23964
|
}
|
|
23965
|
+
/**
|
|
23966
|
+
* Warn about commands Trae will not load as written: Trae documents `name`
|
|
23967
|
+
* as the command's unique identifier, so two commands deriving the same
|
|
23968
|
+
* `name` (e.g. `a/review.md` and `b/review.md`) collide, and it reads
|
|
23969
|
+
* project commands only up to three nested directory levels. Both files are
|
|
23970
|
+
* still written, so this warns rather than failing the run.
|
|
23971
|
+
*/
|
|
23972
|
+
static validateRulesyncCommands({ rulesyncCommands, logger, global = false }) {
|
|
23973
|
+
const pathsByName = /* @__PURE__ */ new Map();
|
|
23974
|
+
for (const rulesyncCommand of rulesyncCommands) {
|
|
23975
|
+
if (!this.isTargetedByRulesyncCommand(rulesyncCommand)) continue;
|
|
23976
|
+
const relativeFilePath = toPosixPath(rulesyncCommand.getRelativeFilePath());
|
|
23977
|
+
const name = this.deriveName(rulesyncCommand);
|
|
23978
|
+
if (typeof name === "string") pathsByName.set(name, [...pathsByName.get(name) ?? [], relativeFilePath]);
|
|
23979
|
+
const dirDepth = relativeFilePath.split("/").length - 1;
|
|
23980
|
+
if (!global && dirDepth > TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH) logger.warn(`Trae reads project commands only up to ${TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH} nested directory levels, so ${quoteForLog(relativeFilePath)} is written but never loaded. Move it to a shallower directory.`);
|
|
23981
|
+
}
|
|
23982
|
+
for (const [name, paths] of pathsByName) {
|
|
23983
|
+
if (paths.length < 2) continue;
|
|
23984
|
+
logger.warn(`Trae command name ${quoteForLog(name)} is shared by ${paths.map(quoteForLog).join(", ")}, but Trae treats \`name\` as a unique identifier. Rename a file or set \`trae.name\` to tell them apart.`);
|
|
23985
|
+
}
|
|
23986
|
+
}
|
|
23987
|
+
/**
|
|
23988
|
+
* The `name` Trae sees for a command: the `trae` section's `name` when set,
|
|
23989
|
+
* otherwise the file name stem. A non-string override is returned as is so
|
|
23990
|
+
* frontmatter validation still rejects it.
|
|
23991
|
+
*/
|
|
23992
|
+
static deriveName(rulesyncCommand) {
|
|
23993
|
+
return rulesyncCommand.getFrontmatter().trae?.name ?? (0, node_path.basename)(rulesyncCommand.getRelativeFilePath(), ".md");
|
|
23994
|
+
}
|
|
23744
23995
|
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
23745
23996
|
return this.isTargetedByRulesyncCommandDefault({
|
|
23746
23997
|
rulesyncCommand,
|
|
@@ -24324,6 +24575,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24324
24575
|
supportsSubdirectory: false
|
|
24325
24576
|
}
|
|
24326
24577
|
}],
|
|
24578
|
+
["kimi-code", {
|
|
24579
|
+
class: KimiCodeCommand,
|
|
24580
|
+
meta: {
|
|
24581
|
+
extension: "md",
|
|
24582
|
+
supportsProject: true,
|
|
24583
|
+
supportsGlobal: true,
|
|
24584
|
+
isSimulated: false,
|
|
24585
|
+
supportsSubdirectory: false,
|
|
24586
|
+
skipToolFileScan: true
|
|
24587
|
+
}
|
|
24588
|
+
}],
|
|
24327
24589
|
["kimi-code-plugin", {
|
|
24328
24590
|
class: KimiCodePluginCommand,
|
|
24329
24591
|
meta: {
|
|
@@ -24577,7 +24839,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
24577
24839
|
await factory.class.validateRulesyncCommands?.({
|
|
24578
24840
|
inputRoots: this.inputRoots,
|
|
24579
24841
|
rulesyncCommands,
|
|
24580
|
-
logger: this.logger
|
|
24842
|
+
logger: this.logger,
|
|
24843
|
+
global: this.global
|
|
24581
24844
|
});
|
|
24582
24845
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
24583
24846
|
const toolCommands = [];
|
|
@@ -26257,8 +26520,8 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
26257
26520
|
* side either way, so the value is dropped rather than carried into a config
|
|
26258
26521
|
* the tool would ignore or refuse.
|
|
26259
26522
|
*/
|
|
26260
|
-
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
26261
|
-
const { canonical, tool, commandOnly, events } = spec;
|
|
26523
|
+
function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction, warn }) {
|
|
26524
|
+
const { canonical, tool, commandOnly, hookTypes, events, ignoredEvents, ignoredWhenFlags } = spec;
|
|
26262
26525
|
const field = direction === "generated" ? canonical : tool;
|
|
26263
26526
|
if (!isFieldApplicable({
|
|
26264
26527
|
commandOnly,
|
|
@@ -26267,10 +26530,23 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }
|
|
|
26267
26530
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
26268
26531
|
return false;
|
|
26269
26532
|
}
|
|
26533
|
+
if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
|
|
26534
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${[...hookTypes].map((type) => `"${type}"`).join(", ")} hooks only, so it is not ${direction}.`);
|
|
26535
|
+
return false;
|
|
26536
|
+
}
|
|
26270
26537
|
if (events !== void 0 && !events.has(eventName)) {
|
|
26271
26538
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${[...events].map((event) => `"${event}"`).join(", ")} only, so it is not ${direction}.`);
|
|
26272
26539
|
return false;
|
|
26273
26540
|
}
|
|
26541
|
+
if (ignoredEvents?.has(eventName)) {
|
|
26542
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on "${eventName}", so it is not ${direction}.`);
|
|
26543
|
+
return false;
|
|
26544
|
+
}
|
|
26545
|
+
const setFlag = hookType === "command" ? ignoredWhenFlags?.find((flag) => flags[flag] === true) : void 0;
|
|
26546
|
+
if (setFlag !== void 0) {
|
|
26547
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on a hook that sets "${setFlag}", so it is not ${direction}.`);
|
|
26548
|
+
return false;
|
|
26549
|
+
}
|
|
26274
26550
|
return true;
|
|
26275
26551
|
}
|
|
26276
26552
|
/**
|
|
@@ -26287,6 +26563,7 @@ function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
|
26287
26563
|
spec,
|
|
26288
26564
|
hookType,
|
|
26289
26565
|
eventName,
|
|
26566
|
+
flags: def,
|
|
26290
26567
|
direction: "generated",
|
|
26291
26568
|
warn
|
|
26292
26569
|
})) emitted[spec.tool] = spec.trueValue;
|
|
@@ -26308,6 +26585,7 @@ function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
|
26308
26585
|
spec,
|
|
26309
26586
|
hookType,
|
|
26310
26587
|
eventName,
|
|
26588
|
+
flags: h,
|
|
26311
26589
|
direction: "imported",
|
|
26312
26590
|
warn
|
|
26313
26591
|
})) continue;
|
|
@@ -28200,6 +28478,20 @@ const CLAUDE_SPEC = {
|
|
|
28200
28478
|
tool: "continueOnBlock"
|
|
28201
28479
|
}
|
|
28202
28480
|
],
|
|
28481
|
+
keywordPassthroughFields: [{
|
|
28482
|
+
canonical: "failClosed",
|
|
28483
|
+
tool: "onFailure",
|
|
28484
|
+
trueValue: "block",
|
|
28485
|
+
falseValue: "continue",
|
|
28486
|
+
hookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
28487
|
+
ignoredEvents: /* @__PURE__ */ new Set([
|
|
28488
|
+
"stop",
|
|
28489
|
+
"subagentStop",
|
|
28490
|
+
"taskCompleted",
|
|
28491
|
+
"teammateIdle"
|
|
28492
|
+
]),
|
|
28493
|
+
ignoredWhenFlags: ["async", "asyncRewake"]
|
|
28494
|
+
}],
|
|
28203
28495
|
arrayPassthroughFields: [{
|
|
28204
28496
|
canonical: "args",
|
|
28205
28497
|
tool: "args",
|
|
@@ -28922,18 +29214,20 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28922
29214
|
canonicalToToolEventNames: CANONICAL_TO_CODEXCLI_EVENT_NAMES,
|
|
28923
29215
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
28924
29216
|
projectDirVar: "",
|
|
28925
|
-
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
29217
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command", "mcp_tool"]),
|
|
28926
29218
|
passthroughFields: ["name", "description"],
|
|
28927
29219
|
stringPassthroughFields: [{
|
|
28928
29220
|
canonical: "commandWindows",
|
|
28929
|
-
tool: "commandWindows"
|
|
29221
|
+
tool: "commandWindows",
|
|
29222
|
+
commandOnly: true
|
|
28930
29223
|
}, {
|
|
28931
29224
|
canonical: "statusMessage",
|
|
28932
29225
|
tool: "statusMessage"
|
|
28933
29226
|
}],
|
|
28934
29227
|
numberPassthroughFields: [{
|
|
28935
29228
|
canonical: "additionalContextLimit",
|
|
28936
|
-
tool: "additionalContextLimit"
|
|
29229
|
+
tool: "additionalContextLimit",
|
|
29230
|
+
commandOnly: true
|
|
28937
29231
|
}],
|
|
28938
29232
|
booleanPassthroughFields: [{
|
|
28939
29233
|
canonical: "async",
|
|
@@ -28942,6 +29236,74 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28942
29236
|
}]
|
|
28943
29237
|
};
|
|
28944
29238
|
/**
|
|
29239
|
+
* Whether a JSON value holds a `null` anywhere. Codex rejects an `mcp_tool`
|
|
29240
|
+
* hook `input` that cannot be represented as TOML (it hashes the handler as
|
|
29241
|
+
* TOML for trust review), and `null` is the one JSON value TOML lacks.
|
|
29242
|
+
*/
|
|
29243
|
+
function containsNull(value) {
|
|
29244
|
+
if (value === null) return true;
|
|
29245
|
+
if (Array.isArray(value)) return value.some(containsNull);
|
|
29246
|
+
if (typeof value === "object") return Object.values(value).some(containsNull);
|
|
29247
|
+
return false;
|
|
29248
|
+
}
|
|
29249
|
+
const WHOLE_FILE_FAILURE = "Codex fails to load the whole hooks file on such a handler";
|
|
29250
|
+
/**
|
|
29251
|
+
* Why this generated handler must not be written, or `undefined` when Codex
|
|
29252
|
+
* can load and run it. Codex deserializes `timeout` as `Option<u64>` on every
|
|
29253
|
+
* handler type, and `server` / `tool` as required strings plus `input` as a
|
|
29254
|
+
* TOML-representable map on `mcp_tool`; a handler that fails that stops the
|
|
29255
|
+
* whole hooks file from loading rather than just that one hook. An empty
|
|
29256
|
+
* `server` / `tool` string still loads but can never resolve to a tool.
|
|
29257
|
+
* https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs
|
|
29258
|
+
*/
|
|
29259
|
+
function describeRejectedHandler(handler) {
|
|
29260
|
+
const { timeout } = handler;
|
|
29261
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isSafeInteger(timeout) || timeout < 0)) return `its "timeout" ${JSON.stringify(timeout) ?? String(timeout)} is not a non-negative integer, and ${WHOLE_FILE_FAILURE}`;
|
|
29262
|
+
if (handler.type !== "mcp_tool") return;
|
|
29263
|
+
for (const field of ["server", "tool"]) {
|
|
29264
|
+
const value = handler[field];
|
|
29265
|
+
if (typeof value !== "string") return `it has no "${field}", and ${WHOLE_FILE_FAILURE}`;
|
|
29266
|
+
if (value === "") return `its "${field}" is empty, so it can never resolve to an MCP tool`;
|
|
29267
|
+
}
|
|
29268
|
+
if (containsNull(handler.input)) return `its "input" holds a null, which Codex cannot represent as TOML, and ${WHOLE_FILE_FAILURE}`;
|
|
29269
|
+
}
|
|
29270
|
+
/**
|
|
29271
|
+
* Drop the generated handlers Codex would refuse to load (or could never
|
|
29272
|
+
* run), and warn about the `mcp_tool` ones Codex loads but skips (`SessionEnd`
|
|
29273
|
+
* MCP hooks are skipped with a startup warning). Matcher groups left without
|
|
29274
|
+
* handlers are removed. https://learn.chatgpt.com/docs/hooks
|
|
29275
|
+
*/
|
|
29276
|
+
function filterCodexHandlers({ hooks, logger }) {
|
|
29277
|
+
const result = {};
|
|
29278
|
+
const skippedSessionEndTools = [];
|
|
29279
|
+
for (const [eventName, groups] of Object.entries(hooks)) {
|
|
29280
|
+
const kept = [];
|
|
29281
|
+
for (const group of groups) {
|
|
29282
|
+
const entry = group;
|
|
29283
|
+
if (!Array.isArray(entry.hooks)) {
|
|
29284
|
+
kept.push(group);
|
|
29285
|
+
continue;
|
|
29286
|
+
}
|
|
29287
|
+
const handlers = entry.hooks.filter((handler) => {
|
|
29288
|
+
const reason = describeRejectedHandler(handler);
|
|
29289
|
+
if (reason !== void 0) {
|
|
29290
|
+
logger?.warn(`Skipping a "${String(handler.type)}" hook on Codex CLI "${eventName}": ${reason}.`);
|
|
29291
|
+
return false;
|
|
29292
|
+
}
|
|
29293
|
+
if (eventName === "SessionEnd" && handler.type === "mcp_tool") skippedSessionEndTools.push(`${String(handler.server)}/${String(handler.tool)}`);
|
|
29294
|
+
return true;
|
|
29295
|
+
});
|
|
29296
|
+
if (handlers.length > 0) kept.push({
|
|
29297
|
+
...entry,
|
|
29298
|
+
hooks: handlers
|
|
29299
|
+
});
|
|
29300
|
+
}
|
|
29301
|
+
if (kept.length > 0) result[eventName] = kept;
|
|
29302
|
+
}
|
|
29303
|
+
if (skippedSessionEndTools.length > 0) logger?.warn(`Codex CLI skips "mcp_tool" hooks on "SessionEnd" with a startup warning, so these are written but will not run: ${skippedSessionEndTools.join(", ")}.`);
|
|
29304
|
+
return result;
|
|
29305
|
+
}
|
|
29306
|
+
/**
|
|
28945
29307
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
28946
29308
|
* Reads the existing file (if any), parses TOML, and returns the content without writing to
|
|
28947
29309
|
* disk. The caller is responsible for writing via the normal write phase.
|
|
@@ -29034,10 +29396,13 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
29034
29396
|
const config = rulesyncHooks.getJson();
|
|
29035
29397
|
const merged = mergeGeneratedHookLists({
|
|
29036
29398
|
existingContent,
|
|
29037
|
-
generatedHooks:
|
|
29038
|
-
|
|
29039
|
-
|
|
29040
|
-
|
|
29399
|
+
generatedHooks: filterCodexHandlers({
|
|
29400
|
+
hooks: canonicalToToolHooks({
|
|
29401
|
+
config,
|
|
29402
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
29403
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
29404
|
+
logger
|
|
29405
|
+
}),
|
|
29041
29406
|
logger
|
|
29042
29407
|
}),
|
|
29043
29408
|
shape: "matcher-groups",
|
|
@@ -32216,60 +32581,6 @@ var KiloHooks = class KiloHooks extends ToolHooks {
|
|
|
32216
32581
|
}
|
|
32217
32582
|
};
|
|
32218
32583
|
//#endregion
|
|
32219
|
-
//#region src/constants/kimi-code-paths.ts
|
|
32220
|
-
const KIMI_CODE_DIR = ".kimi-code";
|
|
32221
|
-
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
32222
|
-
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
32223
|
-
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
32224
|
-
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
32225
|
-
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
32226
|
-
const KIMI_CODE_SKILLS_DIR_PATH = (0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
32227
|
-
(0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
32228
|
-
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
32229
|
-
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = (0, node_path.join)(".agents", "agents");
|
|
32230
|
-
//#endregion
|
|
32231
|
-
//#region src/utils/kimi-code.ts
|
|
32232
|
-
function getKimiCodeHome() {
|
|
32233
|
-
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
32234
|
-
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
32235
|
-
}
|
|
32236
|
-
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
32237
|
-
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
32238
|
-
}
|
|
32239
|
-
/**
|
|
32240
|
-
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
32241
|
-
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
32242
|
-
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
32243
|
-
* guards checked against them — do not depend on the ambient environment.
|
|
32244
|
-
*/
|
|
32245
|
-
function getKimiCodeSharedConfigWritePaths() {
|
|
32246
|
-
return [{
|
|
32247
|
-
relativeDirPath: KIMI_CODE_DIR,
|
|
32248
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32249
|
-
}, {
|
|
32250
|
-
relativeDirPath: ".",
|
|
32251
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32252
|
-
}];
|
|
32253
|
-
}
|
|
32254
|
-
/**
|
|
32255
|
-
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
32256
|
-
* Both spellings carry the same declaration, but passing the key of the file
|
|
32257
|
-
* being written keeps the write path and the drift guards on the same entry.
|
|
32258
|
-
*/
|
|
32259
|
-
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
32260
|
-
return sharedConfigFileKey({
|
|
32261
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
32262
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32263
|
-
});
|
|
32264
|
-
}
|
|
32265
|
-
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
32266
|
-
return getToolRulesyncOutputRoot({
|
|
32267
|
-
nativeOutputRoot,
|
|
32268
|
-
global,
|
|
32269
|
-
toolHome: getKimiCodeHome
|
|
32270
|
-
});
|
|
32271
|
-
}
|
|
32272
|
-
//#endregion
|
|
32273
32584
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
32274
32585
|
function runFromTrustedDirectory({ command, trustedDirectory }) {
|
|
32275
32586
|
if (process.platform === "win32") return `set "RULESYNC_KIMI_HOOK_CWD=1" && cd /d "${trustedDirectory.replaceAll("%", "%%").replaceAll("\"", "\"\"")}" && ${command}`;
|
|
@@ -35726,7 +36037,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
35726
36037
|
supportsImport: true
|
|
35727
36038
|
},
|
|
35728
36039
|
supportedEvents: CODEXCLI_HOOK_EVENTS,
|
|
35729
|
-
supportedHookTypes: ["command"],
|
|
36040
|
+
supportedHookTypes: ["command", "mcp_tool"],
|
|
35730
36041
|
supportsMatcher: true
|
|
35731
36042
|
}],
|
|
35732
36043
|
["commandcode", {
|
|
@@ -37988,6 +38299,23 @@ function toReasonixTable(value) {
|
|
|
37988
38299
|
if (!isPlainObject$1(value)) return {};
|
|
37989
38300
|
return { ...value };
|
|
37990
38301
|
}
|
|
38302
|
+
/**
|
|
38303
|
+
* The tool name a Reasonix `[permissions]` rule must use to reach file reads.
|
|
38304
|
+
* Reasonix matches a rule's tool exactly against its registered tools
|
|
38305
|
+
* (`read_file`, `write_file`, `grep`, ...); only `Bash`, `Edit`, `Browser` and
|
|
38306
|
+
* `Computer` are Claude-Code-style aliases (`canonicalRuleTool` in
|
|
38307
|
+
* `internal/safety/permission/permission.go`), so a `Read(...)` rule matches
|
|
38308
|
+
* nothing. Shared by the `ignore` and `permissions` adapters, which both write
|
|
38309
|
+
* read denies into the same table.
|
|
38310
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
38311
|
+
*/
|
|
38312
|
+
const REASONIX_READ_TOOL_NAME = "read_file";
|
|
38313
|
+
/**
|
|
38314
|
+
* The spelling rulesync wrote for read rules before it followed the Reasonix
|
|
38315
|
+
* tool vocabulary. Still accepted on import, and an entry spelled this way is
|
|
38316
|
+
* treated as rulesync-owned on generate so the dormant copy is replaced.
|
|
38317
|
+
*/
|
|
38318
|
+
const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
|
|
37991
38319
|
//#endregion
|
|
37992
38320
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
37993
38321
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -38006,10 +38334,15 @@ const asClaudeStyleSettings = (document) => {
|
|
|
38006
38334
|
...table,
|
|
38007
38335
|
allow: toReasonixStringArray(table.allow),
|
|
38008
38336
|
ask: toReasonixStringArray(table.ask),
|
|
38009
|
-
deny: toReasonixStringArray(table.deny)
|
|
38337
|
+
deny: toReasonixStringArray(table.deny).filter((entry) => !isReadDenyEntry(entry, REASONIX_LEGACY_READ_TOOL_NAME))
|
|
38010
38338
|
}
|
|
38011
38339
|
};
|
|
38012
38340
|
};
|
|
38341
|
+
/** The pattern of a `read_file(...)` (or legacy `Read(...)`) deny, else null. */
|
|
38342
|
+
const readDenyPatternOf = (entry) => {
|
|
38343
|
+
for (const toolName of [REASONIX_READ_TOOL_NAME, REASONIX_LEGACY_READ_TOOL_NAME]) if (isReadDenyEntry(entry, toolName)) return entry.slice(toolName.length + 1, -1);
|
|
38344
|
+
return null;
|
|
38345
|
+
};
|
|
38013
38346
|
/**
|
|
38014
38347
|
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
38015
38348
|
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
@@ -38022,13 +38355,16 @@ const withoutEmptyPermissions = (settings) => {
|
|
|
38022
38355
|
return document;
|
|
38023
38356
|
};
|
|
38024
38357
|
/**
|
|
38025
|
-
* Writes `.rulesyncignore` patterns as `
|
|
38358
|
+
* Writes `.rulesyncignore` patterns as `read_file(<pattern>)` entries in the
|
|
38026
38359
|
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
38027
38360
|
* `~/.reasonix/config.toml` (global).
|
|
38028
38361
|
*
|
|
38029
38362
|
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
38030
38363
|
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
38031
38364
|
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
38365
|
+
* The tool is spelled `read_file` because Reasonix matches rule tools against
|
|
38366
|
+
* its registered tool names (see {@link REASONIX_READ_TOOL_NAME}); a legacy
|
|
38367
|
+
* `Read(...)` deny is still read back on import.
|
|
38032
38368
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
38033
38369
|
*/
|
|
38034
38370
|
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
@@ -38055,7 +38391,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38055
38391
|
return false;
|
|
38056
38392
|
}
|
|
38057
38393
|
toRulesyncIgnore() {
|
|
38058
|
-
const rulesyncPatterns = this.patterns.
|
|
38394
|
+
const rulesyncPatterns = (0, es_toolkit.uniq)(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
|
|
38059
38395
|
return new RulesyncIgnore({
|
|
38060
38396
|
outputRoot: this.outputRoot,
|
|
38061
38397
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -38064,7 +38400,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38064
38400
|
});
|
|
38065
38401
|
}
|
|
38066
38402
|
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
38067
|
-
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
38403
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern, REASONIX_READ_TOOL_NAME));
|
|
38068
38404
|
const paths = this.getSettablePaths({ global });
|
|
38069
38405
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
38070
38406
|
const existingDocument = parseSharedConfig({
|
|
@@ -38074,7 +38410,8 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38074
38410
|
});
|
|
38075
38411
|
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
38076
38412
|
settings: asClaudeStyleSettings(existingDocument),
|
|
38077
|
-
readDenies
|
|
38413
|
+
readDenies,
|
|
38414
|
+
readToolName: REASONIX_READ_TOOL_NAME
|
|
38078
38415
|
}));
|
|
38079
38416
|
return new ReasonixIgnore({
|
|
38080
38417
|
outputRoot,
|
|
@@ -45577,7 +45914,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
45577
45914
|
feature: "mcp",
|
|
45578
45915
|
existingContent: fileContent ?? "",
|
|
45579
45916
|
patch: {
|
|
45580
|
-
mcp: convertedMcp,
|
|
45917
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
45581
45918
|
...buildKiloMcpPermissionPatch({
|
|
45582
45919
|
existing: existingDocument,
|
|
45583
45920
|
managedServerNames: (0, es_toolkit.uniq)([...Object.keys(mcpServers), ...Object.keys(isRecord$1(existingDocument.mcp) ? existingDocument.mcp : {})]),
|
|
@@ -46437,7 +46774,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
46437
46774
|
feature: "mcp",
|
|
46438
46775
|
existingContent: fileContent ?? "",
|
|
46439
46776
|
patch: {
|
|
46440
|
-
mcp: convertedMcp,
|
|
46777
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
46441
46778
|
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
46442
46779
|
},
|
|
46443
46780
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath),
|
|
@@ -46785,9 +47122,12 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46785
47122
|
/**
|
|
46786
47123
|
* Meta Muse Code MCP servers.
|
|
46787
47124
|
*
|
|
46788
|
-
* Muse Code
|
|
46789
|
-
* block of the GLOBAL user settings file
|
|
46790
|
-
*
|
|
47125
|
+
* rulesync writes Muse Code MCP servers only to the `mcp_servers` (or
|
|
47126
|
+
* `mcpServers`) block of the GLOBAL user settings file
|
|
47127
|
+
* `~/.config/muse/settings.json`, the location extending.md documents. Muse
|
|
47128
|
+
* Code 1.4.0+ also loads a trusted project `.mcp.json` (changelog only), which
|
|
47129
|
+
* is not emitted here because the `claudecode` target already writes that
|
|
47130
|
+
* file. The settings file must carry
|
|
46791
47131
|
* `"schema_version": 1` — a file that omits that key fails every command at
|
|
46792
47132
|
* startup with `malformed settings file` — so the key is bootstrapped when the
|
|
46793
47133
|
* file is created and preserved when it already exists. Other settings keys are
|
|
@@ -46795,6 +47135,7 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46795
47135
|
*
|
|
46796
47136
|
* @see https://dev.meta.ai/docs/muse-code/configuration.md
|
|
46797
47137
|
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
47138
|
+
* @see https://dev.meta.ai/docs/muse-code/changelog
|
|
46798
47139
|
*/
|
|
46799
47140
|
var MusecodeMcp = class MusecodeMcp extends ToolMcp {
|
|
46800
47141
|
json;
|
|
@@ -48027,7 +48368,7 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
48027
48368
|
feature: "mcp",
|
|
48028
48369
|
existingContent,
|
|
48029
48370
|
patch: {
|
|
48030
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()),
|
|
48371
|
+
mcpServers: undefinedWhenEmpty(convertToQwencodeFormat(rulesyncMcp.getMcpServers())),
|
|
48031
48372
|
...mcpPatch !== void 0 && { mcp: mcpPatch }
|
|
48032
48373
|
},
|
|
48033
48374
|
filePath
|
|
@@ -55439,6 +55780,7 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55439
55780
|
"dialogExpiry",
|
|
55440
55781
|
"feedbackDrafts",
|
|
55441
55782
|
"footerLinksRegexes",
|
|
55783
|
+
"forceLoginGatewayUrl",
|
|
55442
55784
|
"modelPicker",
|
|
55443
55785
|
"pluginConfigs",
|
|
55444
55786
|
"prependPlugins",
|
|
@@ -55452,6 +55794,39 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55452
55794
|
"vimInsertModeRemaps"
|
|
55453
55795
|
]);
|
|
55454
55796
|
/**
|
|
55797
|
+
* Nested settings paths under an otherwise project-honored top-level key that
|
|
55798
|
+
* Claude Code reads only from user settings — the nested counterpart of
|
|
55799
|
+
* {@link CLAUDECODE_USER_SCOPE_ONLY_KEYS}. `worktree` is `Any file`, but
|
|
55800
|
+
* `worktree.location` (where Desktop SSH sessions create worktrees on the
|
|
55801
|
+
* remote machine) is documented with a `User` scope, so it is dropped from a
|
|
55802
|
+
* project `.claude/settings.json` and written only under `--global`, while its
|
|
55803
|
+
* siblings are kept.
|
|
55804
|
+
*
|
|
55805
|
+
* @see https://code.claude.com/docs/en/settings-reference#worktree-location
|
|
55806
|
+
*/
|
|
55807
|
+
const CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS = [["worktree", "location"]];
|
|
55808
|
+
/**
|
|
55809
|
+
* Drops {@link CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS} from the top-level
|
|
55810
|
+
* passthrough at project scope, in place, warning once per dropped path. The
|
|
55811
|
+
* authored subtree is copied before the deletion so the caller's config is not
|
|
55812
|
+
* mutated, and a parent the removal emptied is dropped with it.
|
|
55813
|
+
*/
|
|
55814
|
+
function stripUserScopeOnlyNestedPaths({ overrides, global, relativeFilePath, logger }) {
|
|
55815
|
+
if (global) return;
|
|
55816
|
+
for (const path of CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS) {
|
|
55817
|
+
const [topLevelKey] = path;
|
|
55818
|
+
if (topLevelKey === void 0 || !isRecord$1(overrides[topLevelKey])) continue;
|
|
55819
|
+
const copy = { [topLevelKey]: structuredClone(overrides[topLevelKey]) };
|
|
55820
|
+
if (!deleteSandboxPath({
|
|
55821
|
+
target: copy,
|
|
55822
|
+
path
|
|
55823
|
+
})) continue;
|
|
55824
|
+
if (copy[topLevelKey] === void 0) delete overrides[topLevelKey];
|
|
55825
|
+
else overrides[topLevelKey] = copy[topLevelKey];
|
|
55826
|
+
logger?.warn(`Claude Code permissions: '${path.map((segment) => displayKey(segment)).join(".")}' is not honored in the project-scoped ${relativeFilePath}, so it is not written there — Claude Code reads it from user settings only. Author it in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
55827
|
+
}
|
|
55828
|
+
}
|
|
55829
|
+
/**
|
|
55455
55830
|
* Top-level settings keys neither file rulesync writes can honor, with the file
|
|
55456
55831
|
* that does. `Managed` keys are read only from the settings file an organization
|
|
55457
55832
|
* deploys, and `Global config` keys only from `~/.claude.json` — rulesync writes
|
|
@@ -55489,7 +55864,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
55489
55864
|
disableMobileSimulatorTools: "managed settings",
|
|
55490
55865
|
disableSideloadFlags: "managed settings",
|
|
55491
55866
|
externalEditorContext: "~/.claude.json",
|
|
55492
|
-
forceLoginGatewayUrl: "managed settings",
|
|
55493
55867
|
forceRemoteSettingsRefresh: "managed settings",
|
|
55494
55868
|
gatewayInternalNetworks: "managed settings",
|
|
55495
55869
|
leftArrowOpensAgents: "~/.claude.json",
|
|
@@ -55648,8 +56022,9 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55648
56022
|
/**
|
|
55649
56023
|
* `env` variables a project or local settings file cannot set, because a
|
|
55650
56024
|
* checked-out repository should not control them: where Claude Code stores its
|
|
55651
|
-
* own files, what session content it exports, where telemetry goes,
|
|
55652
|
-
* starts or syncs
|
|
56025
|
+
* own files, what session content it exports, where telemetry goes, how it
|
|
56026
|
+
* starts or syncs, the timers on an unanswered dialog, and whether it processes
|
|
56027
|
+
* attachments or expands `@` file mentions. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
55653
56028
|
*
|
|
55654
56029
|
* Only the names the settings reference lists are covered; its directory and
|
|
55655
56030
|
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
@@ -55660,6 +56035,10 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55660
56035
|
*/
|
|
55661
56036
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
55662
56037
|
"BETA_TRACING_ENDPOINT",
|
|
56038
|
+
"CLAUDE_AFK_COUNTDOWN_MS",
|
|
56039
|
+
"CLAUDE_AFK_TIMEOUT_MS",
|
|
56040
|
+
"CLAUDE_CODE_DISABLE_ATTACHMENTS",
|
|
56041
|
+
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
55663
56042
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
55664
56043
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
55665
56044
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
@@ -55668,6 +56047,7 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
55668
56047
|
"CLAUDE_CODE_SYNC_PLUGINS",
|
|
55669
56048
|
"CLAUDE_CODE_SYNC_SKILLS",
|
|
55670
56049
|
"CLAUDE_CODE_TMPDIR",
|
|
56050
|
+
"CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS",
|
|
55671
56051
|
"CLAUDE_CONFIG_DIR",
|
|
55672
56052
|
"ENABLE_BETA_TRACING_DETAILED",
|
|
55673
56053
|
"ENABLE_ENHANCED_TELEMETRY_BETA",
|
|
@@ -55713,6 +56093,13 @@ const CLAUDECODE_ENV_CONTENT_OFF_VALUES = /* @__PURE__ */ new Set([
|
|
|
55713
56093
|
* writing a value Claude Code ignores is the old behavior, while dropping an
|
|
55714
56094
|
* opt-out it honors would remove a privacy setting. `OTEL_LOG_ASSISTANT_RESPONSES`
|
|
55715
56095
|
* has no such exception and sits in the plain name table above.
|
|
56096
|
+
*
|
|
56097
|
+
* `CLAUDE_CODE_ENABLE_CFC` follows the same shape for a different reason: a
|
|
56098
|
+
* project cannot turn Claude in Chrome on, since a checked-out repository must
|
|
56099
|
+
* not be able to connect Claude to the user's browser, so only an off value is
|
|
56100
|
+
* kept at project scope.
|
|
56101
|
+
*
|
|
56102
|
+
* @see https://code.claude.com/docs/en/chrome#project-settings-cant-turn-on-chrome
|
|
55716
56103
|
*/
|
|
55717
56104
|
const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
55718
56105
|
OTEL_LOGS_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
@@ -55720,7 +56107,8 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
55720
56107
|
OTEL_TRACES_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
55721
56108
|
OTEL_LOG_USER_PROMPTS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55722
56109
|
OTEL_LOG_TOOL_CONTENT: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55723
|
-
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
56110
|
+
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
56111
|
+
CLAUDE_CODE_ENABLE_CFC: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
55724
56112
|
};
|
|
55725
56113
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
55726
56114
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_")) return true;
|
|
@@ -55757,7 +56145,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
55757
56145
|
kept[name] = value;
|
|
55758
56146
|
}
|
|
55759
56147
|
if (ignoredEverywhere.length > 0) logger?.warn(`Claude Code permissions: Claude Code ignores ${ignoredEverywhere.map((name) => `'env.${name}'`).join(", ")} in every settings file, so ${ignoredEverywhere.length === 1 ? "it is" : "they are"} not written to ${relativeFilePath}. Set ${ignoredEverywhere.length === 1 ? "it" : "them"} in the environment Claude Code starts from instead, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`);
|
|
55760
|
-
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry,
|
|
56148
|
+
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
55761
56149
|
return kept;
|
|
55762
56150
|
}
|
|
55763
56151
|
/**
|
|
@@ -55988,6 +56376,12 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
55988
56376
|
relativeFilePath: paths.relativeFilePath,
|
|
55989
56377
|
logger
|
|
55990
56378
|
});
|
|
56379
|
+
stripUserScopeOnlyNestedPaths({
|
|
56380
|
+
overrides: overrideTopLevel,
|
|
56381
|
+
global,
|
|
56382
|
+
relativeFilePath: paths.relativeFilePath,
|
|
56383
|
+
logger
|
|
56384
|
+
});
|
|
55991
56385
|
const { filtered: scopedTopLevel, trustAffecting: trustAffectingTopLevel } = stripUnhonoredTopLevelKeys({
|
|
55992
56386
|
overrides: overrideTopLevel,
|
|
55993
56387
|
global,
|
|
@@ -56405,7 +56799,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
56405
56799
|
"ask",
|
|
56406
56800
|
"deny"
|
|
56407
56801
|
];
|
|
56408
|
-
const CATCH_ALL_PATTERN$
|
|
56802
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
56409
56803
|
const MCP_CATEGORY = "mcp";
|
|
56410
56804
|
const MCP_RULE_PREFIX = "mcp__";
|
|
56411
56805
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -56478,7 +56872,7 @@ function categoryOfRule$2(rule) {
|
|
|
56478
56872
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
56479
56873
|
*/
|
|
56480
56874
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
56481
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
56875
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$12;
|
|
56482
56876
|
if (category === MCP_CATEGORY) {
|
|
56483
56877
|
if (catchAll && action === "allow") {
|
|
56484
56878
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -56487,7 +56881,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
56487
56881
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
56488
56882
|
}
|
|
56489
56883
|
if (category === "*") {
|
|
56490
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
56884
|
+
if (catchAll) return CATCH_ALL_PATTERN$12;
|
|
56491
56885
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
56492
56886
|
return null;
|
|
56493
56887
|
}
|
|
@@ -56505,7 +56899,7 @@ function parseCodebuddyRule(rule) {
|
|
|
56505
56899
|
const { specifier } = splitCodebuddyRule(rule);
|
|
56506
56900
|
if (specifier === void 0) return {
|
|
56507
56901
|
category,
|
|
56508
|
-
pattern: CATCH_ALL_PATTERN$
|
|
56902
|
+
pattern: CATCH_ALL_PATTERN$12
|
|
56509
56903
|
};
|
|
56510
56904
|
const trimmed = specifier.trim();
|
|
56511
56905
|
return trimmed === "" ? null : {
|
|
@@ -58756,7 +59150,7 @@ function mapBashActionToDecision(action) {
|
|
|
58756
59150
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
58757
59151
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
58758
59152
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
58759
|
-
const CATCH_ALL_PATTERN$
|
|
59153
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
58760
59154
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
58761
59155
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
58762
59156
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -58781,14 +59175,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
|
58781
59175
|
web_fetch: "webfetch",
|
|
58782
59176
|
web_search: "websearch"
|
|
58783
59177
|
};
|
|
58784
|
-
const ACTION_RANK$
|
|
59178
|
+
const ACTION_RANK$5 = {
|
|
58785
59179
|
allow: 0,
|
|
58786
59180
|
ask: 1,
|
|
58787
59181
|
deny: 2
|
|
58788
59182
|
};
|
|
58789
59183
|
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
58790
59184
|
function isStricterAction({ action, existing }) {
|
|
58791
|
-
return existing === void 0 || ACTION_RANK$
|
|
59185
|
+
return existing === void 0 || ACTION_RANK$5[action] > ACTION_RANK$5[existing];
|
|
58792
59186
|
}
|
|
58793
59187
|
/**
|
|
58794
59188
|
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
@@ -58811,10 +59205,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
58811
59205
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
58812
59206
|
*/
|
|
58813
59207
|
function buildCommandcodeRule(category, pattern) {
|
|
58814
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
59208
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$11 || pattern === "";
|
|
58815
59209
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
58816
59210
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
58817
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
59211
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$11 : null;
|
|
58818
59212
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
58819
59213
|
if (tool === void 0) return null;
|
|
58820
59214
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -58850,7 +59244,7 @@ function splitCommandcodeRule(rule) {
|
|
|
58850
59244
|
}
|
|
58851
59245
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
58852
59246
|
function isCatchAllSpecifier(inner) {
|
|
58853
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
59247
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$11;
|
|
58854
59248
|
}
|
|
58855
59249
|
/**
|
|
58856
59250
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -58884,34 +59278,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
58884
59278
|
if (server.length === 0) return null;
|
|
58885
59279
|
const everyServer = {
|
|
58886
59280
|
category: "mcp",
|
|
58887
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59281
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58888
59282
|
notInAllow: true
|
|
58889
59283
|
};
|
|
58890
59284
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
58891
|
-
if (server === CATCH_ALL_PATTERN$
|
|
58892
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
59285
|
+
if (server === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59286
|
+
if (server.includes(CATCH_ALL_PATTERN$11)) return null;
|
|
58893
59287
|
return {
|
|
58894
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
58895
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59288
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$11 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
|
|
59289
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58896
59290
|
notInAllow: false
|
|
58897
59291
|
};
|
|
58898
59292
|
}
|
|
58899
59293
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
58900
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
59294
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$11);
|
|
58901
59295
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
58902
59296
|
category,
|
|
58903
59297
|
pattern: inner,
|
|
58904
59298
|
notInAllow: true
|
|
58905
59299
|
} : null;
|
|
58906
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
58907
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
59300
|
+
if (remainder === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59301
|
+
if (rest === CATCH_ALL_PATTERN$11 && !server.includes(CATCH_ALL_PATTERN$11)) return {
|
|
58908
59302
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
58909
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59303
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58910
59304
|
notInAllow: true
|
|
58911
59305
|
};
|
|
58912
59306
|
return namesTool ? {
|
|
58913
59307
|
category,
|
|
58914
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59308
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58915
59309
|
notInAllow: false
|
|
58916
59310
|
} : null;
|
|
58917
59311
|
}
|
|
@@ -58935,14 +59329,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58935
59329
|
if (category === "bash") {
|
|
58936
59330
|
if (isCatchAllSpecifier(inner)) return {
|
|
58937
59331
|
category,
|
|
58938
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59332
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58939
59333
|
notInAllow: false
|
|
58940
59334
|
};
|
|
58941
59335
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
58942
59336
|
if (normalized === "" || normalized === ":*") return null;
|
|
58943
59337
|
return isCatchAllSpecifier(normalized) ? {
|
|
58944
59338
|
category,
|
|
58945
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59339
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58946
59340
|
notInAllow: true
|
|
58947
59341
|
} : {
|
|
58948
59342
|
category,
|
|
@@ -58952,7 +59346,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58952
59346
|
}
|
|
58953
59347
|
if (isCatchAllSpecifier(inner)) return {
|
|
58954
59348
|
category,
|
|
58955
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59349
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58956
59350
|
notInAllow: false
|
|
58957
59351
|
};
|
|
58958
59352
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -58971,9 +59365,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58971
59365
|
*/
|
|
58972
59366
|
function parseCommandcodeRule(rule) {
|
|
58973
59367
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
58974
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
59368
|
+
if (tool === CATCH_ALL_PATTERN$11) return isCatchAllSpecifier(inner) ? {
|
|
58975
59369
|
category: "*",
|
|
58976
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59370
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58977
59371
|
notInAllow: true
|
|
58978
59372
|
} : null;
|
|
58979
59373
|
const lowered = tool.toLowerCase();
|
|
@@ -59043,7 +59437,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
59043
59437
|
}).filter((rule) => {
|
|
59044
59438
|
const parsed = parseCommandcodeRule(rule);
|
|
59045
59439
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
59046
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
59440
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$11})`);
|
|
59047
59441
|
if (key !== "allow" && isStricterAction({
|
|
59048
59442
|
action: key,
|
|
59049
59443
|
existing: replacement
|
|
@@ -59100,7 +59494,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
59100
59494
|
const ranked = /* @__PURE__ */ new Map();
|
|
59101
59495
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
59102
59496
|
for (const [category, rules] of Object.entries(permission)) {
|
|
59103
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
59497
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$11);
|
|
59104
59498
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
59105
59499
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
59106
59500
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -59371,7 +59765,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
59371
59765
|
//#endregion
|
|
59372
59766
|
//#region src/features/permissions/continue-permissions.ts
|
|
59373
59767
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
59374
|
-
const CATCH_ALL_PATTERN$
|
|
59768
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
59375
59769
|
/**
|
|
59376
59770
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
59377
59771
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -59379,7 +59773,7 @@ const CATCH_ALL_PATTERN$9 = "*";
|
|
|
59379
59773
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
59380
59774
|
*/
|
|
59381
59775
|
function normalizeCatchAllPattern$1(pattern) {
|
|
59382
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
59776
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
|
|
59383
59777
|
}
|
|
59384
59778
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
59385
59779
|
bash: "Bash",
|
|
@@ -59416,7 +59810,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
59416
59810
|
* `Tool(pattern)`.
|
|
59417
59811
|
*/
|
|
59418
59812
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
59419
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
59813
|
+
return pattern === CATCH_ALL_PATTERN$10 ? toolName : `${toolName}(${pattern})`;
|
|
59420
59814
|
}
|
|
59421
59815
|
/**
|
|
59422
59816
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -59430,7 +59824,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
59430
59824
|
if (toolName === "") return;
|
|
59431
59825
|
return {
|
|
59432
59826
|
toolName,
|
|
59433
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
59827
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$10 : pattern
|
|
59434
59828
|
};
|
|
59435
59829
|
}
|
|
59436
59830
|
/**
|
|
@@ -59604,7 +59998,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
59604
59998
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59605
59999
|
continue;
|
|
59606
60000
|
}
|
|
59607
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
60001
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$10) {
|
|
59608
60002
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59609
60003
|
continue;
|
|
59610
60004
|
}
|
|
@@ -59972,14 +60366,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
59972
60366
|
};
|
|
59973
60367
|
//#endregion
|
|
59974
60368
|
//#region src/features/permissions/crush-permissions.ts
|
|
59975
|
-
const CATCH_ALL_PATTERN$
|
|
60369
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
59976
60370
|
/**
|
|
59977
60371
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
59978
60372
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
59979
60373
|
* still be written.
|
|
59980
60374
|
*/
|
|
59981
60375
|
function normalizeCatchAllPattern(pattern) {
|
|
59982
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
60376
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
|
|
59983
60377
|
}
|
|
59984
60378
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
59985
60379
|
bash: "bash",
|
|
@@ -60178,7 +60572,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
60178
60572
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
60179
60573
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
60180
60574
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
60181
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
60575
|
+
if (pattern === CATCH_ALL_PATTERN$9) {
|
|
60182
60576
|
catchAllAction = action;
|
|
60183
60577
|
continue;
|
|
60184
60578
|
}
|
|
@@ -60251,11 +60645,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
60251
60645
|
};
|
|
60252
60646
|
for (const entry of disabled) {
|
|
60253
60647
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
60254
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60648
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] = "deny";
|
|
60255
60649
|
}
|
|
60256
60650
|
for (const entry of allowed) {
|
|
60257
60651
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
60258
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60652
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] ??= "allow";
|
|
60259
60653
|
}
|
|
60260
60654
|
return { permission };
|
|
60261
60655
|
}
|
|
@@ -62120,7 +62514,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
62120
62514
|
//#region src/features/permissions/goose-permissions.ts
|
|
62121
62515
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
62122
62516
|
const GOOSE_USER_KEY = "user";
|
|
62123
|
-
const CATCH_ALL_PATTERN$
|
|
62517
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
62124
62518
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
62125
62519
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
62126
62520
|
shell: ["bash"],
|
|
@@ -62132,7 +62526,7 @@ const ACTION_TO_GOOSE_LIST = {
|
|
|
62132
62526
|
ask: "ask_before",
|
|
62133
62527
|
deny: "never_allow"
|
|
62134
62528
|
};
|
|
62135
|
-
const ACTION_RANK$
|
|
62529
|
+
const ACTION_RANK$4 = {
|
|
62136
62530
|
allow: 0,
|
|
62137
62531
|
ask: 1,
|
|
62138
62532
|
deny: 2
|
|
@@ -62284,14 +62678,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62284
62678
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
62285
62679
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
62286
62680
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
62287
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
62681
|
+
if (pattern !== CATCH_ALL_PATTERN$8) {
|
|
62288
62682
|
logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
62289
62683
|
continue;
|
|
62290
62684
|
}
|
|
62291
62685
|
const previous = assigned.get(toolName);
|
|
62292
62686
|
if (previous === action) continue;
|
|
62293
62687
|
if (previous !== void 0) {
|
|
62294
|
-
const stricter = ACTION_RANK$
|
|
62688
|
+
const stricter = ACTION_RANK$4[action] > ACTION_RANK$4[previous] ? action : previous;
|
|
62295
62689
|
logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
|
|
62296
62690
|
if (stricter === previous) continue;
|
|
62297
62691
|
const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
|
|
@@ -62315,7 +62709,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
62315
62709
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62316
62710
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62317
62711
|
permission[category] ??= {};
|
|
62318
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
62712
|
+
permission[category][CATCH_ALL_PATTERN$8] = action;
|
|
62319
62713
|
}
|
|
62320
62714
|
}
|
|
62321
62715
|
return { permission };
|
|
@@ -62326,7 +62720,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
62326
62720
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
62327
62721
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
62328
62722
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
62329
|
-
const CATCH_ALL_PATTERN$
|
|
62723
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
62330
62724
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
62331
62725
|
const CATEGORY_TO_GROK_TOOL = {
|
|
62332
62726
|
bash: "Bash",
|
|
@@ -62351,6 +62745,8 @@ const GROK_TOOL_TO_CATEGORY = {
|
|
|
62351
62745
|
...Object.fromEntries(["SendSubagentMessage", "SendAgentMessage"].map((tool) => [tool, "agent"]))
|
|
62352
62746
|
};
|
|
62353
62747
|
const GROK_MCP_TOOL = "MCPTool";
|
|
62748
|
+
const GROK_ALL_TOOLS_ENTRY = "*";
|
|
62749
|
+
const GROK_VERBOSE_ANY_TOOL = "any";
|
|
62354
62750
|
const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
62355
62751
|
...Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category])),
|
|
62356
62752
|
agent_message: "agent"
|
|
@@ -62365,22 +62761,26 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
62365
62761
|
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
62366
62762
|
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
62367
62763
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
62368
|
-
* concrete pattern emits `Tool(pattern)`.
|
|
62764
|
+
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
62765
|
+
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
62766
|
+
* under it has no Grok spelling and returns `null`.
|
|
62369
62767
|
*/
|
|
62370
62768
|
function buildGrokEntry(category, pattern) {
|
|
62769
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$7 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
62371
62770
|
if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
|
|
62372
62771
|
const remainder = category.slice(5);
|
|
62373
62772
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
62374
62773
|
}
|
|
62375
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
62774
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
62376
62775
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
62377
62776
|
if (tool === void 0) return null;
|
|
62378
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
62777
|
+
return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
62379
62778
|
}
|
|
62380
62779
|
/**
|
|
62381
62780
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
62382
|
-
* pattern. Entries without parentheses are treated as catch-all (`*`)
|
|
62383
|
-
*
|
|
62781
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`), and the
|
|
62782
|
+
* bare `*` rule becomes the all-tools `*` category. Returns `null` for tool
|
|
62783
|
+
* prefixes with no canonical equivalent.
|
|
62384
62784
|
*/
|
|
62385
62785
|
function parseGrokEntry(entry) {
|
|
62386
62786
|
const trimmed = entry.trim();
|
|
@@ -62394,19 +62794,23 @@ function parseGrokEntry(entry) {
|
|
|
62394
62794
|
tool = trimmed.slice(0, parenIndex);
|
|
62395
62795
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
62396
62796
|
}
|
|
62797
|
+
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
62798
|
+
category: "*",
|
|
62799
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62800
|
+
};
|
|
62397
62801
|
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
|
|
62398
62802
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
62399
62803
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
62400
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62804
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62401
62805
|
} : {
|
|
62402
62806
|
category: "mcp",
|
|
62403
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62807
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62404
62808
|
};
|
|
62405
62809
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
62406
62810
|
if (category === void 0) return null;
|
|
62407
62811
|
return {
|
|
62408
62812
|
category,
|
|
62409
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
62813
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$7
|
|
62410
62814
|
};
|
|
62411
62815
|
}
|
|
62412
62816
|
/**
|
|
@@ -62423,14 +62827,14 @@ function parseGrokEntry(entry) {
|
|
|
62423
62827
|
function parseClaudeStyleMcpEntry(entry) {
|
|
62424
62828
|
const rest = entry.slice(5);
|
|
62425
62829
|
if (rest.length === 0) return null;
|
|
62426
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
62830
|
+
if (rest === CATCH_ALL_PATTERN$7) return {
|
|
62427
62831
|
category: "mcp",
|
|
62428
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62832
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62429
62833
|
};
|
|
62430
62834
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
62431
62835
|
return {
|
|
62432
62836
|
category: `${MCP_CANONICAL_PREFIX$3}${address}`,
|
|
62433
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62837
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62434
62838
|
};
|
|
62435
62839
|
}
|
|
62436
62840
|
/**
|
|
@@ -62451,15 +62855,20 @@ function parseGrokRule(rule) {
|
|
|
62451
62855
|
if (typeof tool !== "string") return null;
|
|
62452
62856
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
62453
62857
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
62454
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
62858
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$7;
|
|
62455
62859
|
const lowerTool = tool.trim().toLowerCase();
|
|
62456
|
-
if (
|
|
62860
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62861
|
+
category: "*",
|
|
62862
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62863
|
+
action
|
|
62864
|
+
} : null;
|
|
62865
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62457
62866
|
category: "mcp",
|
|
62458
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62867
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62459
62868
|
action
|
|
62460
62869
|
} : {
|
|
62461
62870
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
62462
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62871
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62463
62872
|
action
|
|
62464
62873
|
};
|
|
62465
62874
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -62489,7 +62898,10 @@ function parseGrokRule(rule) {
|
|
|
62489
62898
|
* subagent rather than its launch, and whose pattern is a subagent id
|
|
62490
62899
|
* rather than a subagent type); `mcp__*` maps to `MCPTool(...)` (a scoped
|
|
62491
62900
|
* MCP category folds its address into the parentheses, so a non-`*`
|
|
62492
|
-
* argument pattern on it is not represented)
|
|
62901
|
+
* argument pattern on it is not represented); the all-tools `*` category
|
|
62902
|
+
* maps to Grok's bare `*` rule (which matches every tool) for its
|
|
62903
|
+
* catch-all pattern, while a narrower `*` pattern is skipped with a
|
|
62904
|
+
* warning. The one category with no Grok tool (`notebookedit`) is
|
|
62493
62905
|
* skipped (with a warning when it carries a `deny` rule, to surface the
|
|
62494
62906
|
* gap). When two canonical rules collapse onto the same Grok
|
|
62495
62907
|
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
@@ -62500,7 +62912,9 @@ function parseGrokRule(rule) {
|
|
|
62500
62912
|
* verbose `[[permission.rules]]` tables
|
|
62501
62913
|
* (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
|
|
62502
62914
|
* field is matched case-insensitively against the same tool table, plus
|
|
62503
|
-
* the documented `mcp` alias for the compact form's `MCPTool`.
|
|
62915
|
+
* the documented `mcp` alias for the compact form's `MCPTool`. A bare `*`
|
|
62916
|
+
* entry and a tool-wide verbose `any` rule both import as the all-tools
|
|
62917
|
+
* `*` category. Rules
|
|
62504
62918
|
* from the two forms are merged with the same `deny > ask > allow`
|
|
62505
62919
|
* precedence. When neither form carries a rule (older configs), the coarse
|
|
62506
62920
|
* `[ui] permission_mode` is used as a fallback. Generate always emits the
|
|
@@ -62525,7 +62939,7 @@ function parseGrokRule(rule) {
|
|
|
62525
62939
|
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
62526
62940
|
* every other key
|
|
62527
62941
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
62528
|
-
* entries for tool prefixes rulesync cannot model (e.g. `
|
|
62942
|
+
* entries for tool prefixes rulesync cannot model (e.g. a pattern-only entry such as `src/**`) are preserved.
|
|
62529
62943
|
* The file is never deleted.
|
|
62530
62944
|
*/
|
|
62531
62945
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -62607,7 +63021,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62607
63021
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
62608
63022
|
}
|
|
62609
63023
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
62610
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
63024
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$7]: legacyModeAction(parsed) } } };
|
|
62611
63025
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
62612
63026
|
}
|
|
62613
63027
|
validate() {
|
|
@@ -62627,14 +63041,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62627
63041
|
});
|
|
62628
63042
|
}
|
|
62629
63043
|
};
|
|
62630
|
-
const ACTION_RANK$
|
|
63044
|
+
const ACTION_RANK$3 = {
|
|
62631
63045
|
allow: 0,
|
|
62632
63046
|
ask: 1,
|
|
62633
63047
|
deny: 2
|
|
62634
63048
|
};
|
|
62635
63049
|
/**
|
|
62636
63050
|
* Collect user-authored entries from an existing `[permission]` array whose
|
|
62637
|
-
* tool prefix rulesync cannot model (e.g. `
|
|
63051
|
+
* tool prefix rulesync cannot model (e.g. a pattern-only entry such as `src/**`). Such entries are
|
|
62638
63052
|
* preserved verbatim so replacing the arrays does not silently drop them
|
|
62639
63053
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
62640
63054
|
*/
|
|
@@ -62653,13 +63067,26 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62653
63067
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) for (const [pattern, action] of Object.entries(rules)) {
|
|
62654
63068
|
const entry = buildGrokEntry(category, pattern);
|
|
62655
63069
|
if (entry === null) {
|
|
63070
|
+
if (category === "*") {
|
|
63071
|
+
logger?.warn(`Grok CLI permissions: skipping the all-tools '*' rule '${pattern}' — Grok's bare '*' rule takes no pattern. Write it under the categories it is meant for (for example 'bash' or 'read').`);
|
|
63072
|
+
continue;
|
|
63073
|
+
}
|
|
62656
63074
|
if (action === "deny" && logger) logger.warn(`Grok CLI has no permission tool for the '${category}' category; its 'deny' rule could not be represented and was skipped.`);
|
|
62657
63075
|
continue;
|
|
62658
63076
|
}
|
|
62659
63077
|
const existing = ranked.get(entry);
|
|
62660
63078
|
if (existing !== void 0 && existing !== action) logger?.warn(`Grok permission entry '${entry}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
|
|
62661
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63079
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) ranked.set(entry, action);
|
|
62662
63080
|
}
|
|
63081
|
+
warnRemovedAllToolsRestrictions({
|
|
63082
|
+
existingPermission,
|
|
63083
|
+
ranked,
|
|
63084
|
+
logger
|
|
63085
|
+
});
|
|
63086
|
+
warnEntriesShadowedByAllTools({
|
|
63087
|
+
ranked,
|
|
63088
|
+
logger
|
|
63089
|
+
});
|
|
62663
63090
|
const allow = unmanagedEntries(existingPermission, "allow");
|
|
62664
63091
|
const deny = unmanagedEntries(existingPermission, "deny");
|
|
62665
63092
|
const ask = unmanagedEntries(existingPermission, "ask");
|
|
@@ -62673,6 +63100,38 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62673
63100
|
};
|
|
62674
63101
|
}
|
|
62675
63102
|
/**
|
|
63103
|
+
* Warn when generate drops an existing bare `*` `deny`/`ask` entry without
|
|
63104
|
+
* writing an equally strict one back. Such an entry is Rulesync-owned (it maps
|
|
63105
|
+
* to the all-tools `*` category), so a config without a matching canonical
|
|
63106
|
+
* rule removes it — which silently lifts a restriction on every tool.
|
|
63107
|
+
*/
|
|
63108
|
+
function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger }) {
|
|
63109
|
+
if (!logger) return;
|
|
63110
|
+
const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63111
|
+
for (const action of ["deny", "ask"]) {
|
|
63112
|
+
const entries = existingPermission[action];
|
|
63113
|
+
if (!isStringArray$2(entries)) continue;
|
|
63114
|
+
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63115
|
+
if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
|
|
63116
|
+
logger.warn(`Grok CLI permissions: removing the existing bare '*' entry from the '${action}' array — Rulesync now manages it through the all-tools '*' category, and the canonical config has no '*' '${action}' rule. Add "*": { "*": "${action}" } to keep restricting every tool.`);
|
|
63117
|
+
}
|
|
63118
|
+
}
|
|
63119
|
+
/**
|
|
63120
|
+
* Warn about entries that a bare `*` `deny`/`ask` makes ineffective: Grok
|
|
63121
|
+
* evaluates by severity (`deny > ask > allow`), so a specific `allow` (and,
|
|
63122
|
+
* under a `*` deny, a specific `ask`) never takes effect next to it. Shell
|
|
63123
|
+
* rules are already folded by `honorAllToolsOnBash`, so this covers the other
|
|
63124
|
+
* tools.
|
|
63125
|
+
*/
|
|
63126
|
+
function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
63127
|
+
if (!logger) return;
|
|
63128
|
+
const allTools = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63129
|
+
if (allTools === void 0 || allTools === "allow") return;
|
|
63130
|
+
const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$3[action] < ACTION_RANK$3[allTools]).map(([entry]) => entry).toSorted();
|
|
63131
|
+
if (shadowed.length === 0) return;
|
|
63132
|
+
logger.warn(`Grok CLI permissions: the bare '*' '${allTools}' rule applies to every tool, and Grok evaluates deny > ask > allow, so these less strict entries never take effect: ${shadowed.join(", ")}.`);
|
|
63133
|
+
}
|
|
63134
|
+
/**
|
|
62676
63135
|
* Parse Grok's `[permission]` section back into a canonical permission map,
|
|
62677
63136
|
* reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
|
|
62678
63137
|
* Claude-style entries and the verbose `[[permission.rules]]` tables
|
|
@@ -62691,7 +63150,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
62691
63150
|
const record = ({ category, pattern, action }) => {
|
|
62692
63151
|
const bucket = result[category] ??= {};
|
|
62693
63152
|
const existing = bucket[pattern];
|
|
62694
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63153
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) bucket[pattern] = action;
|
|
62695
63154
|
};
|
|
62696
63155
|
const applyEntries = (entries, action) => {
|
|
62697
63156
|
for (const entry of entries ?? []) {
|
|
@@ -63095,7 +63554,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
63095
63554
|
mcpTools: "mcp",
|
|
63096
63555
|
readOutsideProject: "read"
|
|
63097
63556
|
};
|
|
63098
|
-
function isPermissionAction$
|
|
63557
|
+
function isPermissionAction$2(value) {
|
|
63099
63558
|
return PermissionActionSchema.safeParse(value).success;
|
|
63100
63559
|
}
|
|
63101
63560
|
/**
|
|
@@ -63281,7 +63740,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
|
63281
63740
|
for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
|
|
63282
63741
|
if (!rule || typeof rule !== "object") continue;
|
|
63283
63742
|
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
63284
|
-
if (pattern === void 0 || !isPermissionAction$
|
|
63743
|
+
if (pattern === void 0 || !isPermissionAction$2(rule.action)) continue;
|
|
63285
63744
|
(permission[category] ??= {})[pattern] = rule.action;
|
|
63286
63745
|
}
|
|
63287
63746
|
}
|
|
@@ -64240,6 +64699,367 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
64240
64699
|
return rules;
|
|
64241
64700
|
}
|
|
64242
64701
|
//#endregion
|
|
64702
|
+
//#region src/features/permissions/kiro-capability-permissions.ts
|
|
64703
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
64704
|
+
const CANONICAL_TO_KIRO_CAPABILITY = {
|
|
64705
|
+
bash: "shell",
|
|
64706
|
+
read: "fs_read",
|
|
64707
|
+
edit: "fs_write",
|
|
64708
|
+
write: "fs_write",
|
|
64709
|
+
webfetch: "web_fetch",
|
|
64710
|
+
websearch: "web_search"
|
|
64711
|
+
};
|
|
64712
|
+
const KIRO_CAPABILITY_TO_CANONICAL = {
|
|
64713
|
+
shell: "bash",
|
|
64714
|
+
fs_read: "read",
|
|
64715
|
+
fs_write: "write",
|
|
64716
|
+
web_fetch: "webfetch",
|
|
64717
|
+
web_search: "websearch"
|
|
64718
|
+
};
|
|
64719
|
+
const MANAGED_CAPABILITIES = new Set(Object.keys(KIRO_CAPABILITY_TO_CANONICAL));
|
|
64720
|
+
const ACTION_RANK$2 = {
|
|
64721
|
+
allow: 0,
|
|
64722
|
+
ask: 1,
|
|
64723
|
+
deny: 2
|
|
64724
|
+
};
|
|
64725
|
+
function isPermissionAction$1(value) {
|
|
64726
|
+
return value === "allow" || value === "ask" || value === "deny";
|
|
64727
|
+
}
|
|
64728
|
+
/**
|
|
64729
|
+
* Whether an existing `permissions.yaml` rule can be read back into the
|
|
64730
|
+
* canonical model: a rule for a capability rulesync maps, with no `exclude`
|
|
64731
|
+
* list (the canonical model has no exclusion). Used on import only.
|
|
64732
|
+
*/
|
|
64733
|
+
function isImportableRule(rule) {
|
|
64734
|
+
return isRecord$1(rule) && typeof rule.capability === "string" && MANAGED_CAPABILITIES.has(rule.capability) && rule.exclude === void 0;
|
|
64735
|
+
}
|
|
64736
|
+
/**
|
|
64737
|
+
* Key identifying one (capability, pattern) pair. The catch-all `*` stands for
|
|
64738
|
+
* a match-less rule.
|
|
64739
|
+
*/
|
|
64740
|
+
function rulePatternKey(capability, pattern) {
|
|
64741
|
+
return `${capability}\u0000${pattern}`;
|
|
64742
|
+
}
|
|
64743
|
+
/**
|
|
64744
|
+
* Classify one existing (capability, pattern, effect) entry against the
|
|
64745
|
+
* generated rules: an exact duplicate (same effect) is replaced by the
|
|
64746
|
+
* generated rule; a different effect is a conflict and is kept; anything
|
|
64747
|
+
* rulesync does not emit is unrelated and kept.
|
|
64748
|
+
*/
|
|
64749
|
+
function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
64750
|
+
const effects = emitted.get(rulePatternKey(capability, pattern));
|
|
64751
|
+
if (effects === void 0) return "unrelated";
|
|
64752
|
+
return isPermissionAction$1(effect) && effects.has(effect) ? "duplicate" : "conflict";
|
|
64753
|
+
}
|
|
64754
|
+
/**
|
|
64755
|
+
* Merge one existing rule against the generated entries (see
|
|
64756
|
+
* {@link mergeKiroCapabilityRules}). Returns the rule to keep (possibly with a
|
|
64757
|
+
* narrowed `match`), or `undefined` when every entry is an exact duplicate, and
|
|
64758
|
+
* how many entries rulesync does not generate.
|
|
64759
|
+
*/
|
|
64760
|
+
function mergeExistingRule({ rule, emitted, logger }) {
|
|
64761
|
+
if (!isRecord$1(rule) || typeof rule.capability !== "string" || rule.exclude !== void 0 || !MANAGED_CAPABILITIES.has(rule.capability) || rule.match !== void 0 && !isStringArray$2(rule.match)) return {
|
|
64762
|
+
rule,
|
|
64763
|
+
unrelatedEntries: 0
|
|
64764
|
+
};
|
|
64765
|
+
const capability = rule.capability;
|
|
64766
|
+
const isCatchAll = rule.match === void 0;
|
|
64767
|
+
const patterns = isStringArray$2(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
|
|
64768
|
+
if (patterns.length === 0) return {
|
|
64769
|
+
rule,
|
|
64770
|
+
unrelatedEntries: 0
|
|
64771
|
+
};
|
|
64772
|
+
const remaining = [];
|
|
64773
|
+
let unrelatedEntries = 0;
|
|
64774
|
+
for (const pattern of patterns) {
|
|
64775
|
+
const outcome = classifyExistingEntry({
|
|
64776
|
+
emitted,
|
|
64777
|
+
capability,
|
|
64778
|
+
pattern,
|
|
64779
|
+
effect: rule.effect
|
|
64780
|
+
});
|
|
64781
|
+
if (outcome === "duplicate") continue;
|
|
64782
|
+
remaining.push(pattern);
|
|
64783
|
+
if (outcome === "unrelated") {
|
|
64784
|
+
unrelatedEntries++;
|
|
64785
|
+
continue;
|
|
64786
|
+
}
|
|
64787
|
+
logger?.warn(`Kiro permissions.yaml already has a ${capability} rule for "${pattern}" with effect "${String(rule.effect)}"; keeping it alongside the generated rule. Kiro applies the stricter effect (deny > ask > allow).`);
|
|
64788
|
+
}
|
|
64789
|
+
if (remaining.length === 0) return {
|
|
64790
|
+
rule: void 0,
|
|
64791
|
+
unrelatedEntries
|
|
64792
|
+
};
|
|
64793
|
+
if (remaining.length === patterns.length || isCatchAll) return {
|
|
64794
|
+
rule,
|
|
64795
|
+
unrelatedEntries
|
|
64796
|
+
};
|
|
64797
|
+
return {
|
|
64798
|
+
rule: {
|
|
64799
|
+
...rule,
|
|
64800
|
+
match: remaining
|
|
64801
|
+
},
|
|
64802
|
+
unrelatedEntries
|
|
64803
|
+
};
|
|
64804
|
+
}
|
|
64805
|
+
/**
|
|
64806
|
+
* Merge the existing rules with the generated ones. Rulesync keeps no
|
|
64807
|
+
* ownership record in the file, so it only removes what it is about to write
|
|
64808
|
+
* again: an existing rule loses the `match` entries that exactly duplicate a
|
|
64809
|
+
* generated (capability, pattern, effect) — a match-less rule counts as the `*`
|
|
64810
|
+
* pattern — and is dropped only when no entry is left.
|
|
64811
|
+
*
|
|
64812
|
+
* Everything else is kept verbatim: rules Kiro saved ("Always allow/deny"),
|
|
64813
|
+
* hand-written rules, rules with `exclude`, rules for capabilities rulesync
|
|
64814
|
+
* does not map, malformed entries. An existing rule with the same pattern but a
|
|
64815
|
+
* different effect is kept too, with a warning: Kiro resolves deny > ask >
|
|
64816
|
+
* allow, so the stricter rule still wins and a user's `deny` is never loosened.
|
|
64817
|
+
* Rerunning generate is therefore idempotent.
|
|
64818
|
+
*/
|
|
64819
|
+
function mergeKiroCapabilityRules({ existingRules, generatedRules, logger }) {
|
|
64820
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
64821
|
+
for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$6]) {
|
|
64822
|
+
const key = rulePatternKey(rule.capability, pattern);
|
|
64823
|
+
const effects = emitted.get(key) ?? /* @__PURE__ */ new Set();
|
|
64824
|
+
effects.add(rule.effect);
|
|
64825
|
+
emitted.set(key, effects);
|
|
64826
|
+
}
|
|
64827
|
+
const kept = [];
|
|
64828
|
+
let unrelatedManagedEntries = 0;
|
|
64829
|
+
for (const rule of existingRules) {
|
|
64830
|
+
const result = mergeExistingRule({
|
|
64831
|
+
rule,
|
|
64832
|
+
emitted,
|
|
64833
|
+
logger
|
|
64834
|
+
});
|
|
64835
|
+
if (result.rule !== void 0) kept.push(result.rule);
|
|
64836
|
+
unrelatedManagedEntries += result.unrelatedEntries;
|
|
64837
|
+
}
|
|
64838
|
+
if (unrelatedManagedEntries > 0) logger?.warn(`Kiro permissions.yaml keeps ${unrelatedManagedEntries} existing shell/fs_read/fs_write/web_fetch/web_search rule ${unrelatedManagedEntries === 1 ? "entry" : "entries"} that .rulesync/permissions.json does not generate. Rulesync never removes rules from this file and cannot tell which of these it wrote earlier; delete any stale ones by hand.`);
|
|
64839
|
+
return [...kept, ...generatedRules];
|
|
64840
|
+
}
|
|
64841
|
+
const LOCATION_INDEPENDENT_FS_PATTERN = /^(?:\/|~|\*\*|[A-Za-z]:[\\/])/;
|
|
64842
|
+
/**
|
|
64843
|
+
* Permissions adapter for Kiro's capability-based `permissions.yaml` (Kiro IDE
|
|
64844
|
+
* 1.0, and the same engine in Kiro CLI V3). Used for the `kiro-cli` and
|
|
64845
|
+
* `kiro-ide` targets in **global** scope, where it writes the user-level
|
|
64846
|
+
* `~/.kiro/settings/permissions.yaml`. Project scope keeps writing the agent
|
|
64847
|
+
* config `.kiro/agents/default.json` through {@link KiroPermissions}: the
|
|
64848
|
+
* workspace-level `permissions.yaml` lives under a hash-derived directory in the
|
|
64849
|
+
* home directory, not in the repository, so it cannot be generated.
|
|
64850
|
+
*
|
|
64851
|
+
* Mapping (rulesync canonical -> Kiro rule):
|
|
64852
|
+
* - Category -> `capability`: `bash` -> `shell`, `read` -> `fs_read`,
|
|
64853
|
+
* `edit`/`write` -> `fs_write`, `webfetch` -> `web_fetch`, `websearch` ->
|
|
64854
|
+
* `web_search`. Other categories are skipped with a warning.
|
|
64855
|
+
* - Action -> `effect` 1:1, including `ask`.
|
|
64856
|
+
* - Pattern -> `match` entry. The catch-all `*` becomes a rule with no `match`
|
|
64857
|
+
* (the whole capability), because a filesystem `*` only matches one path
|
|
64858
|
+
* component in Kiro.
|
|
64859
|
+
*
|
|
64860
|
+
* Kiro also writes into this file (consent decisions persisted globally), so it
|
|
64861
|
+
* is merged, never replaced or deleted: every top-level key other than `rules`
|
|
64862
|
+
* is preserved, and existing rules are kept except for entries that exactly
|
|
64863
|
+
* repeat a generated rule (see {@link mergeKiroCapabilityRules}). Rulesync
|
|
64864
|
+
* never retracts a rule an earlier run wrote; it warns instead. A run that
|
|
64865
|
+
* changes nothing returns the existing content verbatim; otherwise the file is
|
|
64866
|
+
* re-serialized and its YAML comments are lost.
|
|
64867
|
+
*
|
|
64868
|
+
* @see https://kiro.dev/docs/permissions/
|
|
64869
|
+
*/
|
|
64870
|
+
var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPermissions {
|
|
64871
|
+
constructor(params) {
|
|
64872
|
+
super({
|
|
64873
|
+
...params,
|
|
64874
|
+
fileContent: params.fileContent ?? ""
|
|
64875
|
+
});
|
|
64876
|
+
}
|
|
64877
|
+
isDeletable() {
|
|
64878
|
+
return false;
|
|
64879
|
+
}
|
|
64880
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
64881
|
+
return true;
|
|
64882
|
+
}
|
|
64883
|
+
static getSettablePaths(_options) {
|
|
64884
|
+
return {
|
|
64885
|
+
relativeDirPath: KIRO_SETTINGS_DIR_PATH,
|
|
64886
|
+
relativeFilePath: KIRO_PERMISSIONS_FILE_NAME
|
|
64887
|
+
};
|
|
64888
|
+
}
|
|
64889
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
64890
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64891
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
64892
|
+
return new KiroCapabilityPermissions({
|
|
64893
|
+
outputRoot,
|
|
64894
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64895
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64896
|
+
fileContent,
|
|
64897
|
+
validate,
|
|
64898
|
+
global: true
|
|
64899
|
+
});
|
|
64900
|
+
}
|
|
64901
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
64902
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64903
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
64904
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
64905
|
+
let parsed;
|
|
64906
|
+
try {
|
|
64907
|
+
parsed = loadYaml(existingContent) ?? {};
|
|
64908
|
+
} catch (error) {
|
|
64909
|
+
throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
64910
|
+
}
|
|
64911
|
+
if (!isRecord$1(parsed)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: not a map`);
|
|
64912
|
+
if (parsed.rules !== void 0 && !Array.isArray(parsed.rules)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: 'rules' is not a list`);
|
|
64913
|
+
const config = rulesyncPermissions.getJson();
|
|
64914
|
+
if (config.kiro?.toolsSettings !== void 0) logger?.warn("Kiro permissions: 'kiro.toolsSettings' applies only to the project-scope agent config (.kiro/agents/default.json); ignoring it for ~/.kiro/settings/permissions.yaml.");
|
|
64915
|
+
const generatedRules = buildKiroCapabilityRules({
|
|
64916
|
+
config,
|
|
64917
|
+
logger
|
|
64918
|
+
});
|
|
64919
|
+
const rules = mergeKiroCapabilityRules({
|
|
64920
|
+
existingRules: parsed.rules ?? [],
|
|
64921
|
+
generatedRules,
|
|
64922
|
+
logger
|
|
64923
|
+
});
|
|
64924
|
+
const next = { ...parsed };
|
|
64925
|
+
if (rules.length > 0 || parsed.rules !== void 0) next.rules = rules;
|
|
64926
|
+
return new KiroCapabilityPermissions({
|
|
64927
|
+
outputRoot,
|
|
64928
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64929
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64930
|
+
fileContent: serializeKiroPermissions({
|
|
64931
|
+
next,
|
|
64932
|
+
parsed,
|
|
64933
|
+
existingContent
|
|
64934
|
+
}),
|
|
64935
|
+
validate: true,
|
|
64936
|
+
global: true
|
|
64937
|
+
});
|
|
64938
|
+
}
|
|
64939
|
+
toRulesyncPermissions() {
|
|
64940
|
+
let parsed;
|
|
64941
|
+
try {
|
|
64942
|
+
parsed = loadYaml(this.getFileContent()) ?? {};
|
|
64943
|
+
} catch (error) {
|
|
64944
|
+
throw new Error(`Failed to parse Kiro permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
64945
|
+
}
|
|
64946
|
+
const rules = isRecord$1(parsed) && Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
64947
|
+
const permission = {};
|
|
64948
|
+
for (const rule of rules) {
|
|
64949
|
+
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
|
|
64950
|
+
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
64951
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$2(rule.match) ? rule.match : [];
|
|
64952
|
+
const categoryRules = permission[category] ??= {};
|
|
64953
|
+
for (const pattern of patterns) {
|
|
64954
|
+
const previous = categoryRules[pattern];
|
|
64955
|
+
if (previous === void 0 || ACTION_RANK$2[rule.effect] > ACTION_RANK$2[previous]) categoryRules[pattern] = rule.effect;
|
|
64956
|
+
}
|
|
64957
|
+
}
|
|
64958
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
64959
|
+
}
|
|
64960
|
+
validate() {
|
|
64961
|
+
return {
|
|
64962
|
+
success: true,
|
|
64963
|
+
error: null
|
|
64964
|
+
};
|
|
64965
|
+
}
|
|
64966
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
64967
|
+
return new KiroCapabilityPermissions({
|
|
64968
|
+
outputRoot,
|
|
64969
|
+
relativeDirPath,
|
|
64970
|
+
relativeFilePath,
|
|
64971
|
+
fileContent: "",
|
|
64972
|
+
validate: false,
|
|
64973
|
+
global: true
|
|
64974
|
+
});
|
|
64975
|
+
}
|
|
64976
|
+
};
|
|
64977
|
+
/**
|
|
64978
|
+
* Convert the canonical `permission` block into Kiro capability rules: one rule
|
|
64979
|
+
* per (capability, effect) carrying every non-catch-all pattern as `match`, plus
|
|
64980
|
+
* a match-less rule when the catch-all `*` is set.
|
|
64981
|
+
*/
|
|
64982
|
+
function buildKiroCapabilityRules({ config, logger }) {
|
|
64983
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
64984
|
+
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
64985
|
+
const capability = CANONICAL_TO_KIRO_CAPABILITY[category];
|
|
64986
|
+
if (capability === void 0) {
|
|
64987
|
+
logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
|
|
64988
|
+
continue;
|
|
64989
|
+
}
|
|
64990
|
+
for (const [pattern, effect] of Object.entries(rules)) {
|
|
64991
|
+
warnOnWorkspaceRelativeFsPattern({
|
|
64992
|
+
capability,
|
|
64993
|
+
category,
|
|
64994
|
+
pattern,
|
|
64995
|
+
logger
|
|
64996
|
+
});
|
|
64997
|
+
const key = `${capability}\u0000${effect}`;
|
|
64998
|
+
const bucket = buckets.get(key) ?? {
|
|
64999
|
+
capability,
|
|
65000
|
+
effect,
|
|
65001
|
+
patterns: /* @__PURE__ */ new Set()
|
|
65002
|
+
};
|
|
65003
|
+
bucket.patterns.add(pattern);
|
|
65004
|
+
buckets.set(key, bucket);
|
|
65005
|
+
}
|
|
65006
|
+
}
|
|
65007
|
+
const result = [];
|
|
65008
|
+
for (const { capability, effect, patterns } of buckets.values()) {
|
|
65009
|
+
if (patterns.has(CATCH_ALL_PATTERN$6)) result.push({
|
|
65010
|
+
capability,
|
|
65011
|
+
effect
|
|
65012
|
+
});
|
|
65013
|
+
const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$6);
|
|
65014
|
+
if (match.length > 0) result.push({
|
|
65015
|
+
capability,
|
|
65016
|
+
match,
|
|
65017
|
+
effect
|
|
65018
|
+
});
|
|
65019
|
+
}
|
|
65020
|
+
return result;
|
|
65021
|
+
}
|
|
65022
|
+
/**
|
|
65023
|
+
* Serialize the merged config. When the merge changes nothing, the existing
|
|
65024
|
+
* content is returned verbatim so a run with nothing to change never rewrites
|
|
65025
|
+
* the file (and never drops its YAML comments).
|
|
65026
|
+
*/
|
|
65027
|
+
function serializeKiroPermissions({ next, parsed, existingContent }) {
|
|
65028
|
+
if ((0, node_util.isDeepStrictEqual)(next, parsed)) return existingContent;
|
|
65029
|
+
return Object.keys(next).length === 0 ? "" : (0, js_yaml.dump)(next);
|
|
65030
|
+
}
|
|
65031
|
+
/**
|
|
65032
|
+
* `~/.kiro/settings/permissions.yaml` applies to every workspace, so a relative
|
|
65033
|
+
* filesystem pattern such as `src/**` is not tied to the project it was written
|
|
65034
|
+
* for. Warn so the author can make it absolute, `~`-relative or `**`-prefixed.
|
|
65035
|
+
*/
|
|
65036
|
+
function warnOnWorkspaceRelativeFsPattern({ capability, category, pattern, logger }) {
|
|
65037
|
+
if (capability !== "fs_read" && capability !== "fs_write") return;
|
|
65038
|
+
if (pattern === CATCH_ALL_PATTERN$6 || LOCATION_INDEPENDENT_FS_PATTERN.test(pattern)) return;
|
|
65039
|
+
logger?.warn(`Kiro permissions.yaml is user-level and applies to every workspace, but the ${category} pattern "${pattern}" is relative. Use an absolute, ~-relative or **-prefixed pattern to target a specific location.`);
|
|
65040
|
+
}
|
|
65041
|
+
/**
|
|
65042
|
+
* Permissions factory for the `kiro-cli` / `kiro-ide` targets: project scope
|
|
65043
|
+
* writes the agent config `.kiro/agents/default.json` ({@link KiroPermissions}),
|
|
65044
|
+
* global scope writes `~/.kiro/settings/permissions.yaml`
|
|
65045
|
+
* ({@link KiroCapabilityPermissions}). The deprecated `kiro` alias stays on
|
|
65046
|
+
* {@link KiroPermissions} only.
|
|
65047
|
+
*/
|
|
65048
|
+
const KiroScopedPermissions = {
|
|
65049
|
+
getSettablePaths(options = {}) {
|
|
65050
|
+
return options.global ? KiroCapabilityPermissions.getSettablePaths(options) : KiroPermissions.getSettablePaths(options);
|
|
65051
|
+
},
|
|
65052
|
+
fromFile(params) {
|
|
65053
|
+
return params.global ? KiroCapabilityPermissions.fromFile(params) : KiroPermissions.fromFile(params);
|
|
65054
|
+
},
|
|
65055
|
+
fromRulesyncPermissions(params) {
|
|
65056
|
+
return params.global ? KiroCapabilityPermissions.fromRulesyncPermissions(params) : KiroPermissions.fromRulesyncPermissions(params);
|
|
65057
|
+
},
|
|
65058
|
+
forDeletion(params) {
|
|
65059
|
+
return params.global ? KiroCapabilityPermissions.forDeletion(params) : KiroPermissions.forDeletion(params);
|
|
65060
|
+
}
|
|
65061
|
+
};
|
|
65062
|
+
//#endregion
|
|
64243
65063
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
64244
65064
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
64245
65065
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
@@ -66267,6 +67087,8 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
66267
67087
|
"eager",
|
|
66268
67088
|
"listDirectory",
|
|
66269
67089
|
"todoWrite",
|
|
67090
|
+
"codeModeOnly",
|
|
67091
|
+
"freeform",
|
|
66270
67092
|
"workflowsEnabled",
|
|
66271
67093
|
"workflowNameOnly",
|
|
66272
67094
|
"executionSandbox"
|
|
@@ -66393,6 +67215,16 @@ const QWEN_SCOPED_TOOLS_KEYS = {
|
|
|
66393
67215
|
rule: "global-machine-wide",
|
|
66394
67216
|
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`todo_write\` tool is registered in this repository.`,
|
|
66395
67217
|
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `todo_write` tool is registered for every project on this machine."
|
|
67218
|
+
},
|
|
67219
|
+
codeModeOnly: {
|
|
67220
|
+
rule: "global-machine-wide",
|
|
67221
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether ordinary tools are reached only through Code Mode's \`exec\` tool in this repository.`,
|
|
67222
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether ordinary tools are reached only through Code Mode's `exec` tool for every project on this machine."
|
|
67223
|
+
},
|
|
67224
|
+
freeform: {
|
|
67225
|
+
rule: "global-machine-wide",
|
|
67226
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether Code Mode's \`exec\` tool takes raw text input on OpenAI Responses models in this repository.`,
|
|
67227
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether Code Mode's `exec` tool takes raw text input on OpenAI Responses models for every project on this machine."
|
|
66396
67228
|
}
|
|
66397
67229
|
};
|
|
66398
67230
|
/**
|
|
@@ -66856,22 +67688,42 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
66856
67688
|
//#endregion
|
|
66857
67689
|
//#region src/features/permissions/reasonix-permissions.ts
|
|
66858
67690
|
/**
|
|
66859
|
-
* Mapping from rulesync canonical tool category names (lowercase) to
|
|
66860
|
-
*
|
|
66861
|
-
*
|
|
66862
|
-
* Reasonix's
|
|
66863
|
-
*
|
|
66864
|
-
*
|
|
66865
|
-
*
|
|
66866
|
-
*
|
|
66867
|
-
*
|
|
66868
|
-
*
|
|
66869
|
-
*
|
|
67691
|
+
* Mapping from rulesync canonical tool category names (lowercase) to the tool
|
|
67692
|
+
* names a Reasonix `[permissions]` rule must use.
|
|
67693
|
+
*
|
|
67694
|
+
* Reasonix matches a rule's tool exactly against its registered tools, which
|
|
67695
|
+
* are snake_case (`read_file`, `write_file`, `grep`, `glob`, `web_fetch`,
|
|
67696
|
+
* `web_search`, `notebook_edit`, `task`). Only `Bash`, `Edit` (every file
|
|
67697
|
+
* writer: `write_file`, `edit_file`, `multi_edit`, `notebook_edit`, ...),
|
|
67698
|
+
* `Browser` and `Computer` are Claude-Code-style family aliases
|
|
67699
|
+
* (`canonicalRuleTool` / `groupOf`), so a `Read(...)` or `Write(...)` rule
|
|
67700
|
+
* matches nothing and Reasonix v2.34.0+ reports it as dormant. Checked on both
|
|
67701
|
+
* release lines: v1.39.8 (`internal/permission/permission.go`) and v2.41.0
|
|
67702
|
+
* (`internal/safety/permission/permission.go`, `rule_group.go`).
|
|
67703
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
67704
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/rule_group.go
|
|
66870
67705
|
*/
|
|
66871
67706
|
const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
66872
67707
|
bash: "Bash",
|
|
66873
|
-
read:
|
|
67708
|
+
read: REASONIX_READ_TOOL_NAME,
|
|
66874
67709
|
edit: "Edit",
|
|
67710
|
+
write: "write_file",
|
|
67711
|
+
webfetch: "web_fetch",
|
|
67712
|
+
websearch: "web_search",
|
|
67713
|
+
grep: "grep",
|
|
67714
|
+
glob: "glob",
|
|
67715
|
+
notebookedit: "notebook_edit",
|
|
67716
|
+
agent: "task"
|
|
67717
|
+
};
|
|
67718
|
+
/**
|
|
67719
|
+
* The Claude-Code-style spellings rulesync emitted before it followed the
|
|
67720
|
+
* Reasonix tool vocabulary. None of them reaches a Reasonix tool, so they are
|
|
67721
|
+
* read back on import (a file generated by an older rulesync still imports)
|
|
67722
|
+
* and counted as managed on generate (the dormant copy is replaced rather than
|
|
67723
|
+
* kept beside the working rule).
|
|
67724
|
+
*/
|
|
67725
|
+
const CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES = {
|
|
67726
|
+
read: REASONIX_LEGACY_READ_TOOL_NAME,
|
|
66875
67727
|
write: "Write",
|
|
66876
67728
|
webfetch: "WebFetch",
|
|
66877
67729
|
websearch: "WebSearch",
|
|
@@ -66881,9 +67733,16 @@ const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
|
66881
67733
|
agent: "Agent"
|
|
66882
67734
|
};
|
|
66883
67735
|
/**
|
|
66884
|
-
* Reverse mapping from Reasonix tool names
|
|
67736
|
+
* Reverse mapping from Reasonix tool names (current and legacy spellings) to
|
|
67737
|
+
* rulesync canonical names.
|
|
66885
67738
|
*/
|
|
66886
|
-
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
67739
|
+
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries([...Object.entries(CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]), ...Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k])]);
|
|
67740
|
+
/** Every Reasonix tool name a canonical category owns on generate. */
|
|
67741
|
+
function reasonixToolNamesOf(canonical) {
|
|
67742
|
+
const legacy = CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES[canonical];
|
|
67743
|
+
const current = toReasonixToolName(canonical);
|
|
67744
|
+
return legacy === void 0 ? [current] : [current, legacy];
|
|
67745
|
+
}
|
|
66887
67746
|
function toReasonixToolName(canonical) {
|
|
66888
67747
|
return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
|
|
66889
67748
|
}
|
|
@@ -67156,7 +68015,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
67156
68015
|
const parsed = parseReasonixConfig(existingContent);
|
|
67157
68016
|
const config = rulesyncPermissions.getJson();
|
|
67158
68017
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
67159
|
-
const managedToolNames = new Set(Object.keys(config.permission).
|
|
68018
|
+
const managedToolNames = new Set(Object.keys(config.permission).flatMap((category) => reasonixToolNamesOf(category)));
|
|
67160
68019
|
const override = config.reasonix;
|
|
67161
68020
|
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
67162
68021
|
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
@@ -67173,12 +68032,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
67173
68032
|
rawOwnedEntries,
|
|
67174
68033
|
logger
|
|
67175
68034
|
});
|
|
67176
|
-
if (logger && managedToolNames.has("
|
|
68035
|
+
if (logger && managedToolNames.has("read_file")) {
|
|
68036
|
+
const readToolNames = new Set(reasonixToolNamesOf("read"));
|
|
67177
68037
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
67178
68038
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
67179
|
-
return toolName
|
|
68039
|
+
return readToolNames.has(toolName);
|
|
67180
68040
|
});
|
|
67181
|
-
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '
|
|
68041
|
+
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '${REASONIX_READ_TOOL_NAME}' tool and will overwrite ${droppedReadDenyEntries.length} existing read deny entries (possibly from ignore feature). Permissions take precedence.`);
|
|
67182
68042
|
}
|
|
67183
68043
|
const mergedPermissions = { ...existingPermissions };
|
|
67184
68044
|
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
@@ -72524,18 +73384,18 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
72524
73384
|
}
|
|
72525
73385
|
}],
|
|
72526
73386
|
["kiro-cli", {
|
|
72527
|
-
class:
|
|
73387
|
+
class: KiroScopedPermissions,
|
|
72528
73388
|
meta: {
|
|
72529
73389
|
supportsProject: true,
|
|
72530
|
-
supportsGlobal:
|
|
73390
|
+
supportsGlobal: true,
|
|
72531
73391
|
supportsImport: true
|
|
72532
73392
|
}
|
|
72533
73393
|
}],
|
|
72534
73394
|
["kiro-ide", {
|
|
72535
|
-
class:
|
|
73395
|
+
class: KiroScopedPermissions,
|
|
72536
73396
|
meta: {
|
|
72537
73397
|
supportsProject: true,
|
|
72538
|
-
supportsGlobal:
|
|
73398
|
+
supportsGlobal: true,
|
|
72539
73399
|
supportsImport: true
|
|
72540
73400
|
}
|
|
72541
73401
|
}],
|
|
@@ -78522,6 +79382,24 @@ var KimiCodeSkill = class extends ToolSkill {
|
|
|
78522
79382
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
78523
79383
|
return targets.includes("*") || targets.includes("kimi-code");
|
|
78524
79384
|
}
|
|
79385
|
+
/**
|
|
79386
|
+
* Commands are emitted into the Kimi-specific skills tree as
|
|
79387
|
+
* `<slug>/SKILL.md` (see `KimiCodeCommand`), so a directory there matching a
|
|
79388
|
+
* current rulesync command slug (of a command that targets `kimi-code`) is
|
|
79389
|
+
* owned by the commands feature: it must not be imported as a skill nor
|
|
79390
|
+
* deleted as an orphan skill. A command aimed only at other tools never
|
|
79391
|
+
* reaches this tree, so it leaves a same-named skill alone. The import-only
|
|
79392
|
+
* `.agents/skills/` root never receives Kimi Code commands, so a same-named
|
|
79393
|
+
* skill in it stays a skill.
|
|
79394
|
+
*/
|
|
79395
|
+
static async isDirOwned({ relativeDirPath, dirName, inputRoots, global }) {
|
|
79396
|
+
if (relativeDirPath !== this.getSettablePaths({ global }).relativeDirPath) return true;
|
|
79397
|
+
return !await rulesyncCommandSlugExists({
|
|
79398
|
+
inputRoots,
|
|
79399
|
+
dirName,
|
|
79400
|
+
toolTarget: "kimi-code"
|
|
79401
|
+
});
|
|
79402
|
+
}
|
|
78525
79403
|
static async fromDir(params) {
|
|
78526
79404
|
const loaded = await this.loadSkillDirContent({
|
|
78527
79405
|
...params,
|
|
@@ -78595,6 +79473,14 @@ var KimiCodePluginSkill = class extends KimiCodeSkill {
|
|
|
78595
79473
|
return { relativeDirPath: KIMI_CODE_PLUGIN_SKILLS_DIR };
|
|
78596
79474
|
}
|
|
78597
79475
|
/**
|
|
79476
|
+
* Unlike `.kimi-code/skills/`, the plugin `skills/` tree is not shared with
|
|
79477
|
+
* the commands feature (`kimi-code-plugin` writes commands to `commands/`),
|
|
79478
|
+
* so every directory in it is a skill.
|
|
79479
|
+
*/
|
|
79480
|
+
static async isDirOwned() {
|
|
79481
|
+
return true;
|
|
79482
|
+
}
|
|
79483
|
+
/**
|
|
78598
79484
|
* The bundle is read from `--output-root <plugin>`, but the imported rulesync
|
|
78599
79485
|
* file belongs to the project's `.rulesync/`, not to the plugin root.
|
|
78600
79486
|
*/
|
|
@@ -78946,7 +79832,8 @@ const OpenCodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
78946
79832
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78947
79833
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78948
79834
|
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78949
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
79835
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
79836
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
78950
79837
|
});
|
|
78951
79838
|
var OpenCodeSkill = class extends ToolSkill {
|
|
78952
79839
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -79063,7 +79950,8 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79063
79950
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
79064
79951
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
79065
79952
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
79066
|
-
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
79953
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
|
|
79954
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
79067
79955
|
};
|
|
79068
79956
|
const { toolTarget } = this.constructor.layout;
|
|
79069
79957
|
const rulesyncFrontmatter = {
|
|
@@ -79098,13 +79986,18 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79098
79986
|
rootFrontmatter: rulesyncFrontmatter,
|
|
79099
79987
|
section: opencodeSection
|
|
79100
79988
|
});
|
|
79989
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
79990
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
79991
|
+
section: opencodeSection
|
|
79992
|
+
});
|
|
79101
79993
|
const opencodeFrontmatter = {
|
|
79102
79994
|
name: rulesyncFrontmatter.name,
|
|
79103
79995
|
description: rulesyncFrontmatter.description,
|
|
79104
79996
|
...license !== void 0 && { license },
|
|
79105
79997
|
...compatibility !== void 0 && { compatibility },
|
|
79106
79998
|
...metadata !== void 0 && { metadata },
|
|
79107
|
-
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] }
|
|
79999
|
+
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] },
|
|
80000
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
|
|
79108
80001
|
};
|
|
79109
80002
|
const settablePaths = this.getSettablePaths({ global });
|
|
79110
80003
|
return new this({
|
|
@@ -79127,11 +80020,13 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79127
80020
|
...params,
|
|
79128
80021
|
getSettablePaths: (options) => this.getSettablePaths(options)
|
|
79129
80022
|
});
|
|
79130
|
-
const
|
|
79131
|
-
|
|
79132
|
-
|
|
79133
|
-
|
|
79134
|
-
}
|
|
80023
|
+
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
80024
|
+
const frontmatter = normalizeDisableModelInvocation({
|
|
80025
|
+
frontmatter: loaded.frontmatter,
|
|
80026
|
+
skillFilePath: (0, node_path.join)(skillDirPath, SKILL_FILE_NAME)
|
|
80027
|
+
});
|
|
80028
|
+
const result = OpenCodeSkillFrontmatterSchema.safeParse(frontmatter);
|
|
80029
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
79135
80030
|
return new this({
|
|
79136
80031
|
outputRoot: loaded.outputRoot,
|
|
79137
80032
|
relativeDirPath: loaded.relativeDirPath,
|
|
@@ -79159,6 +80054,52 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79159
80054
|
});
|
|
79160
80055
|
}
|
|
79161
80056
|
};
|
|
80057
|
+
const DISABLE_MODEL_INVOCATION_KEY = "disable-model-invocation";
|
|
80058
|
+
/**
|
|
80059
|
+
* Reads a `disable-model-invocation` value the way OpenCode V2 does
|
|
80060
|
+
* (`booleanValue` in `packages/core/src/config/plugin/skill-file.ts`): a
|
|
80061
|
+
* boolean, the numbers `1` / `0`, or a `true`/`yes`/`on`/`1` /
|
|
80062
|
+
* `false`/`no`/`off`/`0` string compared after trimming and case-folding.
|
|
80063
|
+
* Anything else is `undefined`, which OpenCode ignores.
|
|
80064
|
+
*/
|
|
80065
|
+
function parseOpencodeBoolean(value) {
|
|
80066
|
+
if (typeof value === "boolean") return value;
|
|
80067
|
+
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : void 0;
|
|
80068
|
+
if (typeof value !== "string") return;
|
|
80069
|
+
const normalized = value.trim().toLowerCase();
|
|
80070
|
+
if ([
|
|
80071
|
+
"true",
|
|
80072
|
+
"yes",
|
|
80073
|
+
"on",
|
|
80074
|
+
"1"
|
|
80075
|
+
].includes(normalized)) return true;
|
|
80076
|
+
if ([
|
|
80077
|
+
"false",
|
|
80078
|
+
"no",
|
|
80079
|
+
"off",
|
|
80080
|
+
"0"
|
|
80081
|
+
].includes(normalized)) return false;
|
|
80082
|
+
}
|
|
80083
|
+
/**
|
|
80084
|
+
* Coerces an imported `disable-model-invocation` onto the boolean the schema
|
|
80085
|
+
* types. A spelling OpenCode cannot read is dropped with a warning instead of
|
|
80086
|
+
* failing the import, since OpenCode itself ignores it.
|
|
80087
|
+
*/
|
|
80088
|
+
function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
|
|
80089
|
+
if (!Object.hasOwn(frontmatter, DISABLE_MODEL_INVOCATION_KEY)) return frontmatter;
|
|
80090
|
+
const raw = frontmatter[DISABLE_MODEL_INVOCATION_KEY];
|
|
80091
|
+
const parsed = parseOpencodeBoolean(raw);
|
|
80092
|
+
if (parsed === raw) return frontmatter;
|
|
80093
|
+
const { [DISABLE_MODEL_INVOCATION_KEY]: _dropped, ...rest } = frontmatter;
|
|
80094
|
+
if (parsed === void 0) {
|
|
80095
|
+
warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ignoring unreadable "${DISABLE_MODEL_INVOCATION_KEY}" value ${stripControlCharacters(JSON.stringify(raw) ?? String(raw))}; OpenCode ignores it too.`);
|
|
80096
|
+
return rest;
|
|
80097
|
+
}
|
|
80098
|
+
return {
|
|
80099
|
+
...rest,
|
|
80100
|
+
[DISABLE_MODEL_INVOCATION_KEY]: parsed
|
|
80101
|
+
};
|
|
80102
|
+
}
|
|
79162
80103
|
//#endregion
|
|
79163
80104
|
//#region src/features/skills/mimocode-skill.ts
|
|
79164
80105
|
/**
|
|
@@ -100664,13 +101605,15 @@ const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
|
100664
101605
|
* The manifest declares only `name` and `description`, and rejects anything
|
|
100665
101606
|
* else, so the one required field is derived from the plugin directory's name.
|
|
100666
101607
|
* An existing manifest is never read or rewritten: it may carry a
|
|
100667
|
-
* hand-authored `name` or `description` that rulesync has no source for.
|
|
101608
|
+
* hand-authored `name` or `description` that rulesync has no source for. A
|
|
101609
|
+
* manifest kept in one of the manifest folders counts as present too, so no
|
|
101610
|
+
* root stub is written next to it.
|
|
100668
101611
|
*/
|
|
100669
101612
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
100670
101613
|
const paths = [];
|
|
100671
101614
|
for (const outputRoot of outputRoots) {
|
|
100672
101615
|
const filePath = (0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
100673
|
-
if (await
|
|
101616
|
+
if (await hasAntigravityPluginManifest(outputRoot)) continue;
|
|
100674
101617
|
if (await refusesWriteOutsideRoot({
|
|
100675
101618
|
logger,
|
|
100676
101619
|
rootPath: outputRoot,
|
|
@@ -100693,6 +101636,11 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
100693
101636
|
sourceLoadFailed: false
|
|
100694
101637
|
};
|
|
100695
101638
|
}
|
|
101639
|
+
async function hasAntigravityPluginManifest(outputRoot) {
|
|
101640
|
+
const candidates = [(0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => (0, node_path.join)(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
101641
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return true;
|
|
101642
|
+
return false;
|
|
101643
|
+
}
|
|
100696
101644
|
//#endregion
|
|
100697
101645
|
//#region src/features/shared/hermes-project-plugin-activation.ts
|
|
100698
101646
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|
|
@@ -103752,6 +104700,12 @@ Object.defineProperty(exports, "loadYaml", {
|
|
|
103752
104700
|
return loadYaml;
|
|
103753
104701
|
}
|
|
103754
104702
|
});
|
|
104703
|
+
Object.defineProperty(exports, "mapWithConcurrency", {
|
|
104704
|
+
enumerable: true,
|
|
104705
|
+
get: function() {
|
|
104706
|
+
return mapWithConcurrency;
|
|
104707
|
+
}
|
|
104708
|
+
});
|
|
103755
104709
|
Object.defineProperty(exports, "mergeInputRootConfigs", {
|
|
103756
104710
|
enumerable: true,
|
|
103757
104711
|
get: function() {
|