rulesync 9.7.0 → 10.0.0
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 +1 -1
- package/dist/cli/index.cjs +96 -31
- package/dist/cli/index.js +96 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DC9T1JlQ.js → import-B02AyWkQ.js} +960 -163
- package/dist/import-B02AyWkQ.js.map +1 -0
- package/dist/{import-D5f_X4-A.cjs → import-Dcj5HiOE.cjs} +998 -171
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-DC9T1JlQ.js.map +0 -1
|
@@ -114,28 +114,35 @@ function formatError(error) {
|
|
|
114
114
|
}
|
|
115
115
|
//#endregion
|
|
116
116
|
//#region src/constants/rulesync-paths.ts
|
|
117
|
-
const { join: join$
|
|
117
|
+
const { join: join$251 } = node_path.posix;
|
|
118
118
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
119
119
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
120
120
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
121
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
122
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
123
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
124
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
125
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
126
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
121
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
122
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
123
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
124
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
125
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
126
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
127
|
+
const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
128
|
+
const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
129
|
+
const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
127
130
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
128
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
131
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
129
132
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
130
133
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
131
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
132
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
134
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
135
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$251(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
133
136
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
134
137
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
135
138
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
136
139
|
const RULESYNC_PERMISSIONS_FILE_NAME = "permissions.json";
|
|
140
|
+
const RULESYNC_MCP_JSONC_FILE_NAME = "mcp.jsonc";
|
|
141
|
+
const RULESYNC_HOOKS_JSONC_FILE_NAME = "hooks.jsonc";
|
|
142
|
+
const RULESYNC_PERMISSIONS_JSONC_FILE_NAME = "permissions.jsonc";
|
|
137
143
|
const RULESYNC_CONFIG_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json";
|
|
138
144
|
const RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
|
|
145
|
+
const RULESYNC_PERMISSIONS_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/permissions-schema.json";
|
|
139
146
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
140
147
|
//#endregion
|
|
141
148
|
//#region src/types/tool-target-tuples.ts
|
|
@@ -347,7 +354,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
347
354
|
"junie",
|
|
348
355
|
"vibe",
|
|
349
356
|
"qwencode",
|
|
350
|
-
"reasonix"
|
|
357
|
+
"reasonix",
|
|
358
|
+
"grokcli"
|
|
351
359
|
];
|
|
352
360
|
const permissionsProcessorToolTargetTuple = [
|
|
353
361
|
"amp",
|
|
@@ -6295,10 +6303,13 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
6295
6303
|
* Hook events supported by Devin Local (native `.devin/` hooks).
|
|
6296
6304
|
*
|
|
6297
6305
|
* Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
|
|
6298
|
-
*
|
|
6299
|
-
* `UserPromptSubmit`, `Stop`, `SessionStart`,
|
|
6306
|
+
* eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
6307
|
+
* `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
|
|
6308
|
+
* `PostCompaction` (fires after context compaction; added in the Devin CLI
|
|
6309
|
+
* stable changelog — https://docs.devin.ai/cli/changelog/stable). The
|
|
6300
6310
|
* tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
|
|
6301
|
-
* a `matcher` (regex against `tool_name`); the session/turn events
|
|
6311
|
+
* a `matcher` (regex against `tool_name`); the session/turn/compaction events
|
|
6312
|
+
* do not.
|
|
6302
6313
|
*
|
|
6303
6314
|
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
6304
6315
|
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
@@ -6313,7 +6324,8 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
6313
6324
|
"postToolUse",
|
|
6314
6325
|
"beforeSubmitPrompt",
|
|
6315
6326
|
"stop",
|
|
6316
|
-
"permissionRequest"
|
|
6327
|
+
"permissionRequest",
|
|
6328
|
+
"postCompact"
|
|
6317
6329
|
];
|
|
6318
6330
|
/** Hook events supported by OpenCode. */
|
|
6319
6331
|
const OPENCODE_HOOK_EVENTS = [
|
|
@@ -6507,7 +6519,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
|
|
|
6507
6519
|
* Hook events supported by AugmentCode (Auggie CLI).
|
|
6508
6520
|
* Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
|
|
6509
6521
|
* PreToolUse / PostToolUse (tool events, matcher-aware) plus the
|
|
6510
|
-
* SessionStart / SessionEnd / Stop session events (no matcher).
|
|
6522
|
+
* SessionStart / SessionEnd / Stop / Notification session events (no matcher).
|
|
6511
6523
|
* @see https://docs.augmentcode.com/cli/hooks
|
|
6512
6524
|
*/
|
|
6513
6525
|
const AUGMENTCODE_HOOK_EVENTS = [
|
|
@@ -6515,7 +6527,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
|
|
|
6515
6527
|
"postToolUse",
|
|
6516
6528
|
"sessionStart",
|
|
6517
6529
|
"sessionEnd",
|
|
6518
|
-
"stop"
|
|
6530
|
+
"stop",
|
|
6531
|
+
"notification"
|
|
6519
6532
|
];
|
|
6520
6533
|
/**
|
|
6521
6534
|
* Hook events supported by Mistral Vibe (mistral-vibe).
|
|
@@ -6567,6 +6580,10 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
6567
6580
|
* The Qwen-specific events
|
|
6568
6581
|
* `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
|
|
6569
6582
|
* `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
|
|
6583
|
+
* Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
|
|
6584
|
+
* `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
|
|
6585
|
+
* `InstructionsLoaded`, which map to the canonical `postToolBatch`,
|
|
6586
|
+
* `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
|
|
6570
6587
|
* @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
|
|
6571
6588
|
*/
|
|
6572
6589
|
const QWENCODE_HOOK_EVENTS = [
|
|
@@ -6575,7 +6592,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6575
6592
|
"preToolUse",
|
|
6576
6593
|
"postToolUse",
|
|
6577
6594
|
"postToolUseFailure",
|
|
6595
|
+
"postToolBatch",
|
|
6578
6596
|
"beforeSubmitPrompt",
|
|
6597
|
+
"userPromptExpansion",
|
|
6579
6598
|
"stop",
|
|
6580
6599
|
"stopFailure",
|
|
6581
6600
|
"subagentStart",
|
|
@@ -6583,7 +6602,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6583
6602
|
"preCompact",
|
|
6584
6603
|
"postCompact",
|
|
6585
6604
|
"permissionRequest",
|
|
6605
|
+
"permissionDenied",
|
|
6586
6606
|
"notification",
|
|
6607
|
+
"instructionsLoaded",
|
|
6587
6608
|
"todoCreated",
|
|
6588
6609
|
"todoCompleted"
|
|
6589
6610
|
];
|
|
@@ -6593,11 +6614,11 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6593
6614
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
6594
6615
|
* (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
|
|
6595
6616
|
* `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
|
|
6596
|
-
* `SubagentStop`, `Notification`, `PreCompact`).
|
|
6597
|
-
*
|
|
6617
|
+
* `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
|
|
6618
|
+
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
6598
6619
|
* `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
|
|
6599
|
-
* `SessionEnd`, `SubagentStop`,
|
|
6600
|
-
*
|
|
6620
|
+
* `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
|
|
6621
|
+
* `Notification` ← `notification`, and `PreCompact` ← `preCompact`.
|
|
6601
6622
|
* `match` (Reasonix's matcher field name) is honored only on
|
|
6602
6623
|
* `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
|
|
6603
6624
|
* tool-event scoping used by other adapters.
|
|
@@ -6611,7 +6632,39 @@ const REASONIX_HOOK_EVENTS = [
|
|
|
6611
6632
|
"sessionStart",
|
|
6612
6633
|
"sessionEnd",
|
|
6613
6634
|
"subagentStop",
|
|
6614
|
-
"postModelInvocation"
|
|
6635
|
+
"postModelInvocation",
|
|
6636
|
+
"notification",
|
|
6637
|
+
"preCompact"
|
|
6638
|
+
];
|
|
6639
|
+
/**
|
|
6640
|
+
* Hook events supported by Grok CLI (xAI Grok Build).
|
|
6641
|
+
*
|
|
6642
|
+
* Grok Build documents a Claude-Code-compatible hooks surface with fourteen
|
|
6643
|
+
* PascalCase events, all of which map 1:1 onto an existing canonical arm:
|
|
6644
|
+
* `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
|
|
6645
|
+
* `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
|
|
6646
|
+
* `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
|
|
6647
|
+
* `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
|
|
6648
|
+
* name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
|
|
6649
|
+
* `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
|
|
6650
|
+
* the remaining lifecycle events are matcher-less.
|
|
6651
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
6652
|
+
*/
|
|
6653
|
+
const GROKCLI_HOOK_EVENTS = [
|
|
6654
|
+
"sessionStart",
|
|
6655
|
+
"sessionEnd",
|
|
6656
|
+
"beforeSubmitPrompt",
|
|
6657
|
+
"preToolUse",
|
|
6658
|
+
"postToolUse",
|
|
6659
|
+
"postToolUseFailure",
|
|
6660
|
+
"permissionDenied",
|
|
6661
|
+
"stop",
|
|
6662
|
+
"stopFailure",
|
|
6663
|
+
"notification",
|
|
6664
|
+
"subagentStart",
|
|
6665
|
+
"subagentStop",
|
|
6666
|
+
"preCompact",
|
|
6667
|
+
"postCompact"
|
|
6615
6668
|
];
|
|
6616
6669
|
/**
|
|
6617
6670
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
@@ -6685,6 +6738,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
6685
6738
|
junie: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6686
6739
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6687
6740
|
reasonix: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6741
|
+
grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6688
6742
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
6689
6743
|
hooks: zod_mini.z.optional(hooksRecordSchema),
|
|
6690
6744
|
disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -6743,7 +6797,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
|
6743
6797
|
postToolUse: "PostToolUse",
|
|
6744
6798
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6745
6799
|
stop: "Stop",
|
|
6746
|
-
permissionRequest: "PermissionRequest"
|
|
6800
|
+
permissionRequest: "PermissionRequest",
|
|
6801
|
+
postCompact: "PostCompaction"
|
|
6747
6802
|
};
|
|
6748
6803
|
/**
|
|
6749
6804
|
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
@@ -6758,7 +6813,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6758
6813
|
postToolUse: "PostToolUse",
|
|
6759
6814
|
sessionStart: "SessionStart",
|
|
6760
6815
|
sessionEnd: "SessionEnd",
|
|
6761
|
-
stop: "Stop"
|
|
6816
|
+
stop: "Stop",
|
|
6817
|
+
notification: "Notification"
|
|
6762
6818
|
};
|
|
6763
6819
|
/**
|
|
6764
6820
|
* Map AugmentCode PascalCase event names to canonical camelCase.
|
|
@@ -7028,7 +7084,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
7028
7084
|
preToolUse: "PreToolUse",
|
|
7029
7085
|
postToolUse: "PostToolUse",
|
|
7030
7086
|
postToolUseFailure: "PostToolUseFailure",
|
|
7087
|
+
postToolBatch: "PostToolBatch",
|
|
7031
7088
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7089
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
7032
7090
|
stop: "Stop",
|
|
7033
7091
|
subagentStart: "SubagentStart",
|
|
7034
7092
|
subagentStop: "SubagentStop",
|
|
@@ -7036,7 +7094,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
7036
7094
|
preCompact: "PreCompact",
|
|
7037
7095
|
postCompact: "PostCompact",
|
|
7038
7096
|
permissionRequest: "PermissionRequest",
|
|
7097
|
+
permissionDenied: "PermissionDenied",
|
|
7039
7098
|
notification: "Notification",
|
|
7099
|
+
instructionsLoaded: "InstructionsLoaded",
|
|
7040
7100
|
todoCreated: "TodoCreated",
|
|
7041
7101
|
todoCompleted: "TodoCompleted"
|
|
7042
7102
|
};
|
|
@@ -7058,12 +7118,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
7058
7118
|
sessionStart: "SessionStart",
|
|
7059
7119
|
sessionEnd: "SessionEnd",
|
|
7060
7120
|
subagentStop: "SubagentStop",
|
|
7061
|
-
postModelInvocation: "PostLLMCall"
|
|
7121
|
+
postModelInvocation: "PostLLMCall",
|
|
7122
|
+
notification: "Notification",
|
|
7123
|
+
preCompact: "PreCompact"
|
|
7062
7124
|
};
|
|
7063
7125
|
/**
|
|
7064
7126
|
* Map Reasonix PascalCase event names to canonical camelCase.
|
|
7065
7127
|
*/
|
|
7066
7128
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7129
|
+
/**
|
|
7130
|
+
* Map canonical camelCase event names to Grok CLI PascalCase.
|
|
7131
|
+
* Grok Build reuses the same Claude-style PascalCase event names, so each
|
|
7132
|
+
* canonical arm maps to its PascalCase equivalent.
|
|
7133
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
7134
|
+
*/
|
|
7135
|
+
const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
7136
|
+
sessionStart: "SessionStart",
|
|
7137
|
+
sessionEnd: "SessionEnd",
|
|
7138
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7139
|
+
preToolUse: "PreToolUse",
|
|
7140
|
+
postToolUse: "PostToolUse",
|
|
7141
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7142
|
+
permissionDenied: "PermissionDenied",
|
|
7143
|
+
stop: "Stop",
|
|
7144
|
+
stopFailure: "StopFailure",
|
|
7145
|
+
notification: "Notification",
|
|
7146
|
+
subagentStart: "SubagentStart",
|
|
7147
|
+
subagentStop: "SubagentStop",
|
|
7148
|
+
preCompact: "PreCompact",
|
|
7149
|
+
postCompact: "PostCompact"
|
|
7150
|
+
};
|
|
7151
|
+
/**
|
|
7152
|
+
* Map Grok CLI PascalCase event names to canonical camelCase.
|
|
7153
|
+
*/
|
|
7154
|
+
const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7067
7155
|
//#endregion
|
|
7068
7156
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
7069
7157
|
function isToolMatcherEntry(x) {
|
|
@@ -7262,12 +7350,55 @@ function toolHooksToCanonical({ hooks, converterConfig }) {
|
|
|
7262
7350
|
return canonical;
|
|
7263
7351
|
}
|
|
7264
7352
|
//#endregion
|
|
7353
|
+
//#region src/utils/jsonc.ts
|
|
7354
|
+
/**
|
|
7355
|
+
* Rebuild the parsed value from its own enumerable entries, dropping
|
|
7356
|
+
* prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
|
|
7357
|
+
* `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
|
|
7358
|
+
* literal `__proto__` key would replace the containing object's prototype
|
|
7359
|
+
* instead of becoming an own property; rebuilding gives every object a clean
|
|
7360
|
+
* `Object.prototype` again and severs that path.
|
|
7361
|
+
*/
|
|
7362
|
+
function deepSanitize(value) {
|
|
7363
|
+
if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
|
|
7364
|
+
if (value !== null && typeof value === "object") {
|
|
7365
|
+
const sanitized = {};
|
|
7366
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
7367
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
7368
|
+
sanitized[key] = deepSanitize(entry);
|
|
7369
|
+
}
|
|
7370
|
+
return sanitized;
|
|
7371
|
+
}
|
|
7372
|
+
return value;
|
|
7373
|
+
}
|
|
7374
|
+
/**
|
|
7375
|
+
* Parse a JSONC (JSON with Comments) document strictly.
|
|
7376
|
+
*
|
|
7377
|
+
* Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
|
|
7378
|
+
* errors and returns a best-effort value), this throws on any parse error so
|
|
7379
|
+
* a malformed source file fails loudly instead of generating half-empty tool
|
|
7380
|
+
* configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
|
|
7381
|
+
* `JSON.parse` on files that may contain comments or trailing commas.
|
|
7382
|
+
*/
|
|
7383
|
+
function parseJsonc$6(content) {
|
|
7384
|
+
const errors = [];
|
|
7385
|
+
const result = (0, jsonc_parser.parse)(content, errors, {
|
|
7386
|
+
allowTrailingComma: true,
|
|
7387
|
+
disallowComments: false
|
|
7388
|
+
});
|
|
7389
|
+
if (errors.length > 0) {
|
|
7390
|
+
const details = errors.map((e) => `${(0, jsonc_parser.printParseErrorCode)(e.error)} at offset ${e.offset}`).join(", ");
|
|
7391
|
+
throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
|
|
7392
|
+
}
|
|
7393
|
+
return deepSanitize(result);
|
|
7394
|
+
}
|
|
7395
|
+
//#endregion
|
|
7265
7396
|
//#region src/features/hooks/rulesync-hooks.ts
|
|
7266
7397
|
var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
7267
7398
|
json;
|
|
7268
7399
|
constructor(params) {
|
|
7269
7400
|
super({ ...params });
|
|
7270
|
-
this.json =
|
|
7401
|
+
this.json = parseJsonc$6(this.fileContent);
|
|
7271
7402
|
if (params.validate) {
|
|
7272
7403
|
const result = this.validate();
|
|
7273
7404
|
if (!result.success) throw result.error;
|
|
@@ -7276,7 +7407,11 @@ var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
|
7276
7407
|
static getSettablePaths() {
|
|
7277
7408
|
return {
|
|
7278
7409
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
7279
|
-
relativeFilePath:
|
|
7410
|
+
relativeFilePath: RULESYNC_HOOKS_FILE_NAME,
|
|
7411
|
+
jsonc: {
|
|
7412
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
7413
|
+
relativeFilePath: RULESYNC_HOOKS_JSONC_FILE_NAME
|
|
7414
|
+
}
|
|
7280
7415
|
};
|
|
7281
7416
|
}
|
|
7282
7417
|
validate() {
|
|
@@ -7292,16 +7427,23 @@ var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
|
7292
7427
|
}
|
|
7293
7428
|
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
7294
7429
|
const paths = RulesyncHooks.getSettablePaths();
|
|
7295
|
-
const
|
|
7296
|
-
if (!await fileExists(filePath)) throw new Error(`No ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} found.`);
|
|
7297
|
-
const fileContent = await readFileContent(filePath);
|
|
7298
|
-
return new RulesyncHooks({
|
|
7299
|
-
outputRoot,
|
|
7430
|
+
const candidates = [paths.jsonc, {
|
|
7300
7431
|
relativeDirPath: paths.relativeDirPath,
|
|
7301
|
-
relativeFilePath: paths.relativeFilePath
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7432
|
+
relativeFilePath: paths.relativeFilePath
|
|
7433
|
+
}];
|
|
7434
|
+
for (const candidate of candidates) {
|
|
7435
|
+
const filePath = (0, node_path.join)(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath);
|
|
7436
|
+
if (!await fileExists(filePath)) continue;
|
|
7437
|
+
const fileContent = await readFileContent(filePath);
|
|
7438
|
+
return new RulesyncHooks({
|
|
7439
|
+
outputRoot,
|
|
7440
|
+
relativeDirPath: candidate.relativeDirPath,
|
|
7441
|
+
relativeFilePath: candidate.relativeFilePath,
|
|
7442
|
+
fileContent,
|
|
7443
|
+
validate
|
|
7444
|
+
});
|
|
7445
|
+
}
|
|
7446
|
+
throw new Error(`No ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} or ${RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH} found.`);
|
|
7305
7447
|
}
|
|
7306
7448
|
getJson() {
|
|
7307
7449
|
return this.json;
|
|
@@ -7588,7 +7730,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
|
|
|
7588
7730
|
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
7589
7731
|
"sessionStart",
|
|
7590
7732
|
"sessionEnd",
|
|
7591
|
-
"stop"
|
|
7733
|
+
"stop",
|
|
7734
|
+
"notification"
|
|
7592
7735
|
])
|
|
7593
7736
|
};
|
|
7594
7737
|
/**
|
|
@@ -8687,7 +8830,8 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
8687
8830
|
"sessionStart",
|
|
8688
8831
|
"sessionEnd",
|
|
8689
8832
|
"stop",
|
|
8690
|
-
"beforeSubmitPrompt"
|
|
8833
|
+
"beforeSubmitPrompt",
|
|
8834
|
+
"postCompact"
|
|
8691
8835
|
])
|
|
8692
8836
|
};
|
|
8693
8837
|
/**
|
|
@@ -8996,6 +9140,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8996
9140
|
}
|
|
8997
9141
|
};
|
|
8998
9142
|
//#endregion
|
|
9143
|
+
//#region src/constants/grokcli-paths.ts
|
|
9144
|
+
/**
|
|
9145
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9146
|
+
*
|
|
9147
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9148
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9149
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9150
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9151
|
+
*
|
|
9152
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9153
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9154
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9155
|
+
* @see https://docs.x.ai/build/overview
|
|
9156
|
+
*/
|
|
9157
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9158
|
+
const GROKCLI_DIR = ".grok";
|
|
9159
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9160
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9161
|
+
/**
|
|
9162
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9163
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9164
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9165
|
+
*/
|
|
9166
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9167
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9168
|
+
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
9169
|
+
/**
|
|
9170
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9171
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9172
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9173
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9174
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9175
|
+
*/
|
|
9176
|
+
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
9177
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9178
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9179
|
+
/**
|
|
9180
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9181
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9182
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9183
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9184
|
+
*/
|
|
9185
|
+
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
9186
|
+
/**
|
|
9187
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9188
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9189
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9190
|
+
* MCP/skills/subagents adapters).
|
|
9191
|
+
*/
|
|
9192
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9193
|
+
//#endregion
|
|
9194
|
+
//#region src/features/hooks/grokcli-hooks.ts
|
|
9195
|
+
const GROKCLI_CONVERTER_CONFIG = {
|
|
9196
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
9197
|
+
canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
|
|
9198
|
+
toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
|
|
9199
|
+
projectDirVar: "",
|
|
9200
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
9201
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
9202
|
+
"sessionStart",
|
|
9203
|
+
"sessionEnd",
|
|
9204
|
+
"beforeSubmitPrompt",
|
|
9205
|
+
"stop",
|
|
9206
|
+
"stopFailure",
|
|
9207
|
+
"notification",
|
|
9208
|
+
"subagentStart",
|
|
9209
|
+
"subagentStop",
|
|
9210
|
+
"preCompact",
|
|
9211
|
+
"postCompact"
|
|
9212
|
+
])
|
|
9213
|
+
};
|
|
9214
|
+
/**
|
|
9215
|
+
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
9216
|
+
*
|
|
9217
|
+
* Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
|
|
9218
|
+
* maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
|
|
9219
|
+
* matcher groups under a top-level `hooks` key. rulesync writes all its hooks
|
|
9220
|
+
* into a single standalone `rulesync.json` file discovered from
|
|
9221
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
|
|
9222
|
+
* is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
|
|
9223
|
+
* as an orphan.
|
|
9224
|
+
*
|
|
9225
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9226
|
+
*/
|
|
9227
|
+
var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
9228
|
+
constructor(params) {
|
|
9229
|
+
super({
|
|
9230
|
+
...params,
|
|
9231
|
+
fileContent: params.fileContent ?? "{}"
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9234
|
+
static getSettablePaths(_options = {}) {
|
|
9235
|
+
return {
|
|
9236
|
+
relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
|
|
9237
|
+
relativeFilePath: GROKCLI_HOOKS_FILE_NAME
|
|
9238
|
+
};
|
|
9239
|
+
}
|
|
9240
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9241
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9242
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
9243
|
+
return new GrokcliHooks({
|
|
9244
|
+
outputRoot,
|
|
9245
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9246
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9247
|
+
fileContent,
|
|
9248
|
+
validate
|
|
9249
|
+
});
|
|
9250
|
+
}
|
|
9251
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
9252
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9253
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9254
|
+
const config = rulesyncHooks.getJson();
|
|
9255
|
+
const grokHooks = canonicalToToolHooks({
|
|
9256
|
+
config,
|
|
9257
|
+
toolOverrideHooks: config.grokcli?.hooks,
|
|
9258
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
9259
|
+
logger
|
|
9260
|
+
});
|
|
9261
|
+
await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
|
|
9262
|
+
const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
|
|
9263
|
+
return new GrokcliHooks({
|
|
9264
|
+
outputRoot,
|
|
9265
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9266
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9267
|
+
fileContent,
|
|
9268
|
+
validate
|
|
9269
|
+
});
|
|
9270
|
+
}
|
|
9271
|
+
toRulesyncHooks() {
|
|
9272
|
+
let parsed;
|
|
9273
|
+
try {
|
|
9274
|
+
parsed = JSON.parse(this.getFileContent());
|
|
9275
|
+
} catch (error) {
|
|
9276
|
+
throw new Error(`Failed to parse Grok hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9277
|
+
}
|
|
9278
|
+
const hooks = toolHooksToCanonical({
|
|
9279
|
+
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
9280
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
9281
|
+
});
|
|
9282
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9283
|
+
version: 1,
|
|
9284
|
+
hooks
|
|
9285
|
+
}, null, 2) });
|
|
9286
|
+
}
|
|
9287
|
+
validate() {
|
|
9288
|
+
return {
|
|
9289
|
+
success: true,
|
|
9290
|
+
error: null
|
|
9291
|
+
};
|
|
9292
|
+
}
|
|
9293
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9294
|
+
return new GrokcliHooks({
|
|
9295
|
+
outputRoot,
|
|
9296
|
+
relativeDirPath,
|
|
9297
|
+
relativeFilePath,
|
|
9298
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
9299
|
+
validate: false
|
|
9300
|
+
});
|
|
9301
|
+
}
|
|
9302
|
+
};
|
|
9303
|
+
//#endregion
|
|
8999
9304
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
9000
9305
|
/**
|
|
9001
9306
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -10202,9 +10507,9 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
10202
10507
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
10203
10508
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
10204
10509
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
10205
|
-
*
|
|
10510
|
+
* All ten upstream events have a clean canonical equivalent and are mapped:
|
|
10206
10511
|
* PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
|
|
10207
|
-
* SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
|
|
10512
|
+
* SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
10208
10513
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
10209
10514
|
*/
|
|
10210
10515
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -10792,6 +11097,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
10792
11097
|
supportedEvents: REASONIX_HOOK_EVENTS,
|
|
10793
11098
|
supportedHookTypes: ["command"],
|
|
10794
11099
|
supportsMatcher: true
|
|
11100
|
+
}],
|
|
11101
|
+
["grokcli", {
|
|
11102
|
+
class: GrokcliHooks,
|
|
11103
|
+
meta: {
|
|
11104
|
+
supportsProject: true,
|
|
11105
|
+
supportsGlobal: true,
|
|
11106
|
+
supportsImport: true
|
|
11107
|
+
},
|
|
11108
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
11109
|
+
supportedHookTypes: ["command"],
|
|
11110
|
+
supportsMatcher: true
|
|
10795
11111
|
}]
|
|
10796
11112
|
]);
|
|
10797
11113
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -12135,15 +12451,51 @@ const RulesyncMcpServerSchema = zod_mini.z.extend(McpServerSchema, {
|
|
|
12135
12451
|
exposed: zod_mini.z.optional(zod_mini.z.boolean())
|
|
12136
12452
|
});
|
|
12137
12453
|
const RulesyncMcpConfigSchema = zod_mini.z.object({ mcpServers: zod_mini.z.record(zod_mini.z.string(), RulesyncMcpServerSchema) });
|
|
12454
|
+
/**
|
|
12455
|
+
* Tool-scoped MCP block: servers that apply only to one tool. A named entry
|
|
12456
|
+
* replaces/adds the same-named shared server wholesale for that tool; `null`
|
|
12457
|
+
* removes the shared server for that tool. Mirrors `{toolname}.hooks` in
|
|
12458
|
+
* `.rulesync/hooks.json` and `{toolname}.permission` in
|
|
12459
|
+
* `.rulesync/permissions.json`.
|
|
12460
|
+
*/
|
|
12461
|
+
const toolScopedMcpSchema = zod_mini.z.looseObject({ mcpServers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.nullable(RulesyncMcpServerSchema))) });
|
|
12138
12462
|
const RulesyncMcpFileSchema = zod_mini.z.looseObject({
|
|
12139
12463
|
$schema: zod_mini.z.optional(zod_mini.z.string()),
|
|
12140
|
-
...RulesyncMcpConfigSchema.shape
|
|
12464
|
+
...RulesyncMcpConfigSchema.shape,
|
|
12465
|
+
amp: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12466
|
+
"antigravity-cli": zod_mini.z.optional(toolScopedMcpSchema),
|
|
12467
|
+
"antigravity-ide": zod_mini.z.optional(toolScopedMcpSchema),
|
|
12468
|
+
augmentcode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12469
|
+
claudecode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12470
|
+
cline: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12471
|
+
codexcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12472
|
+
copilot: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12473
|
+
copilotcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12474
|
+
cursor: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12475
|
+
deepagents: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12476
|
+
devin: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12477
|
+
factorydroid: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12478
|
+
goose: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12479
|
+
grokcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12480
|
+
hermesagent: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12481
|
+
junie: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12482
|
+
kilo: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12483
|
+
kiro: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12484
|
+
opencode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12485
|
+
qwencode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12486
|
+
reasonix: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12487
|
+
roo: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12488
|
+
rovodev: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12489
|
+
takt: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12490
|
+
vibe: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12491
|
+
warp: zod_mini.z.optional(toolScopedMcpSchema),
|
|
12492
|
+
zed: zod_mini.z.optional(toolScopedMcpSchema)
|
|
12141
12493
|
});
|
|
12142
12494
|
var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
12143
12495
|
json;
|
|
12144
12496
|
constructor(params) {
|
|
12145
12497
|
super(params);
|
|
12146
|
-
this.json =
|
|
12498
|
+
this.json = parseJsonc$6(this.fileContent);
|
|
12147
12499
|
if (params.validate) {
|
|
12148
12500
|
const result = this.validate();
|
|
12149
12501
|
if (!result.success) throw result.error;
|
|
@@ -12153,7 +12505,11 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12153
12505
|
return {
|
|
12154
12506
|
recommended: {
|
|
12155
12507
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
12156
|
-
relativeFilePath:
|
|
12508
|
+
relativeFilePath: RULESYNC_MCP_FILE_NAME
|
|
12509
|
+
},
|
|
12510
|
+
jsonc: {
|
|
12511
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
12512
|
+
relativeFilePath: RULESYNC_MCP_JSONC_FILE_NAME
|
|
12157
12513
|
},
|
|
12158
12514
|
legacy: {
|
|
12159
12515
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
@@ -12175,7 +12531,18 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12175
12531
|
static async fromFile({ outputRoot = process.cwd(), validate = true, logger }) {
|
|
12176
12532
|
const paths = this.getSettablePaths();
|
|
12177
12533
|
const recommendedPath = (0, node_path.join)(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
|
|
12534
|
+
const jsoncPath = (0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
|
|
12178
12535
|
const legacyPath = (0, node_path.join)(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
|
|
12536
|
+
if (await fileExists(jsoncPath)) {
|
|
12537
|
+
const fileContent = await readFileContent(jsoncPath);
|
|
12538
|
+
return new RulesyncMcp({
|
|
12539
|
+
outputRoot,
|
|
12540
|
+
relativeDirPath: paths.jsonc.relativeDirPath,
|
|
12541
|
+
relativeFilePath: paths.jsonc.relativeFilePath,
|
|
12542
|
+
fileContent,
|
|
12543
|
+
validate
|
|
12544
|
+
});
|
|
12545
|
+
}
|
|
12179
12546
|
if (await fileExists(recommendedPath)) {
|
|
12180
12547
|
const fileContent = await readFileContent(recommendedPath);
|
|
12181
12548
|
return new RulesyncMcp({
|
|
@@ -12235,7 +12602,141 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12235
12602
|
getJson() {
|
|
12236
12603
|
return this.json;
|
|
12237
12604
|
}
|
|
12238
|
-
|
|
12605
|
+
/**
|
|
12606
|
+
* Build the effective MCP config for one tool target:
|
|
12607
|
+
*
|
|
12608
|
+
* 1. Filter shared servers by the DEPRECATED per-server `targets` field
|
|
12609
|
+
* (missing/`["*"]` means every tool). A deprecation warning points at
|
|
12610
|
+
* the tool-scoped blocks that replace it.
|
|
12611
|
+
* 2. Overlay the tool-scoped `{toolname}.mcpServers` block(s): a named
|
|
12612
|
+
* entry replaces/adds the shared server wholesale for this tool; `null`
|
|
12613
|
+
* removes it.
|
|
12614
|
+
*
|
|
12615
|
+
* Targets that share one output file resolve identically so the shared
|
|
12616
|
+
* file's content never depends on which of them generates last — see
|
|
12617
|
+
* `resolveMcpTarget` for the alias groups (kiro trio, claudecode/-legacy,
|
|
12618
|
+
* and the Antigravity pair).
|
|
12619
|
+
*
|
|
12620
|
+
* Returns the same instance when neither mechanism is used.
|
|
12621
|
+
*/
|
|
12622
|
+
forTarget({ toolTarget, logger }) {
|
|
12623
|
+
const { blockKeys, acceptedTargetNames } = resolveMcpTarget({ toolTarget });
|
|
12624
|
+
const json = this.json;
|
|
12625
|
+
const sharedServers = this.json.mcpServers ?? {};
|
|
12626
|
+
const serverNamesWithTargets = Object.entries(sharedServers).filter(([, serverConfig]) => serverConfig.targets !== void 0).map(([serverName]) => serverName);
|
|
12627
|
+
if (serverNamesWithTargets.length > 0) this.warnTargetsDeprecationOnce({
|
|
12628
|
+
serverNamesWithTargets,
|
|
12629
|
+
logger
|
|
12630
|
+
});
|
|
12631
|
+
for (const ignoredKey of MCP_IGNORED_ALIAS_SOURCE_KEYS) {
|
|
12632
|
+
if (!isRecord(json[ignoredKey])) continue;
|
|
12633
|
+
this.warnOncePerFile(`alias:${ignoredKey}`, `The "${ignoredKey}" block in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${MCP_BLOCK_KEY_ALIASES[ignoredKey]}" key instead.`, logger);
|
|
12634
|
+
}
|
|
12635
|
+
const toolBlockKeys = Object.keys(json).filter((key) => MCP_TOOL_BLOCK_KEYS.has(key));
|
|
12636
|
+
if (serverNamesWithTargets.length === 0 && toolBlockKeys.length === 0) return this;
|
|
12637
|
+
const effectiveServers = Object.fromEntries(Object.entries(sharedServers).filter(([, serverConfig]) => {
|
|
12638
|
+
const targets = serverConfig.targets;
|
|
12639
|
+
if (targets === void 0) return true;
|
|
12640
|
+
return targets.some((target) => target === "*" || acceptedTargetNames.has(target));
|
|
12641
|
+
}));
|
|
12642
|
+
for (const blockKey of blockKeys) {
|
|
12643
|
+
const toolBlock = json[blockKey];
|
|
12644
|
+
const toolServers = isRecord(toolBlock) && isRecord(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
12645
|
+
for (const [serverName, serverConfig] of Object.entries(toolServers ?? {})) {
|
|
12646
|
+
if (isPrototypePollutionKey(serverName)) continue;
|
|
12647
|
+
if (serverConfig === null) delete effectiveServers[serverName];
|
|
12648
|
+
else effectiveServers[serverName] = serverConfig;
|
|
12649
|
+
}
|
|
12650
|
+
}
|
|
12651
|
+
const rest = Object.fromEntries(Object.entries(json).filter(([key]) => !MCP_TOOL_BLOCK_KEYS.has(key)));
|
|
12652
|
+
return new RulesyncMcp({
|
|
12653
|
+
outputRoot: this.outputRoot,
|
|
12654
|
+
relativeDirPath: this.relativeDirPath,
|
|
12655
|
+
relativeFilePath: this.relativeFilePath,
|
|
12656
|
+
fileContent: JSON.stringify({
|
|
12657
|
+
...rest,
|
|
12658
|
+
mcpServers: effectiveServers
|
|
12659
|
+
}, null, 2)
|
|
12660
|
+
});
|
|
12661
|
+
}
|
|
12662
|
+
/**
|
|
12663
|
+
* The deprecation warning would otherwise repeat once per generated tool
|
|
12664
|
+
* target (a full `--targets "*"` run creates one RulesyncMcp per target),
|
|
12665
|
+
* so it is deduplicated per source file path.
|
|
12666
|
+
*/
|
|
12667
|
+
warnTargetsDeprecationOnce({ serverNamesWithTargets, logger }) {
|
|
12668
|
+
this.warnOncePerFile("targets-deprecation", `The per-server "targets" field in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is deprecated (servers: ${serverNamesWithTargets.join(", ")}). Author tool-scoped "{toolname}.mcpServers" blocks instead.`, logger);
|
|
12669
|
+
}
|
|
12670
|
+
warnOncePerFile(kind, message, logger) {
|
|
12671
|
+
const dedupeKey = `${this.getFilePath()}#${kind}`;
|
|
12672
|
+
if (warnedOnceKeys.has(dedupeKey)) return;
|
|
12673
|
+
warnedOnceKeys.add(dedupeKey);
|
|
12674
|
+
logger?.warn(message);
|
|
12675
|
+
}
|
|
12676
|
+
};
|
|
12677
|
+
/**
|
|
12678
|
+
* All keys that may hold a tool-scoped `{toolname}.mcpServers` block. Derived
|
|
12679
|
+
* from the MCP processor's target tuple so a newly added MCP-capable tool is
|
|
12680
|
+
* covered automatically.
|
|
12681
|
+
*/
|
|
12682
|
+
const MCP_TOOL_BLOCK_KEYS = new Set(mcpProcessorToolTargetTuple);
|
|
12683
|
+
/**
|
|
12684
|
+
* Targets whose `{toolname}.mcpServers` block key is ALWAYS another target's
|
|
12685
|
+
* key (at every scope): `claudecode-legacy` is a deprecated alias of
|
|
12686
|
+
* `claudecode`, and the Kiro IDE/CLI targets share the `kiro` block because
|
|
12687
|
+
* all three write the same `.kiro/settings/mcp.json` at both scopes. Blocks
|
|
12688
|
+
* authored under these source names are never read (a warning is emitted).
|
|
12689
|
+
*/
|
|
12690
|
+
const MCP_BLOCK_KEY_ALIASES = {
|
|
12691
|
+
"claudecode-legacy": "claudecode",
|
|
12692
|
+
"kiro-cli": "kiro",
|
|
12693
|
+
"kiro-ide": "kiro"
|
|
12694
|
+
};
|
|
12695
|
+
const MCP_IGNORED_ALIAS_SOURCE_KEYS = Object.keys(MCP_BLOCK_KEY_ALIASES);
|
|
12696
|
+
/**
|
|
12697
|
+
* Resolve which tool-scoped blocks a target reads and which deprecated
|
|
12698
|
+
* `targets` names match it. Targets that share one output file must resolve
|
|
12699
|
+
* identically, otherwise the shared file's content would depend on which
|
|
12700
|
+
* target happened to generate last:
|
|
12701
|
+
*
|
|
12702
|
+
* - `claudecode-legacy` aliases `claudecode`; the Kiro trio shares `kiro`
|
|
12703
|
+
* (same output file at both scopes).
|
|
12704
|
+
* - `antigravity-ide` / `antigravity-cli` share their output file at BOTH
|
|
12705
|
+
* scopes — `.agents/mcp_config.json` (project) and
|
|
12706
|
+
* `~/.gemini/config/mcp_config.json` (global; both global subdirs are
|
|
12707
|
+
* `config`) — so both targets always apply both blocks in a fixed order
|
|
12708
|
+
* (`antigravity-ide` first, `antigravity-cli` second — the CLI block wins
|
|
12709
|
+
* per server on conflict).
|
|
12710
|
+
*/
|
|
12711
|
+
function resolveMcpTarget({ toolTarget }) {
|
|
12712
|
+
if (toolTarget === "claudecode" || toolTarget === "claudecode-legacy") return {
|
|
12713
|
+
blockKeys: ["claudecode"],
|
|
12714
|
+
acceptedTargetNames: /* @__PURE__ */ new Set(["claudecode", "claudecode-legacy"])
|
|
12715
|
+
};
|
|
12716
|
+
if (toolTarget === "kiro" || toolTarget === "kiro-cli" || toolTarget === "kiro-ide") return {
|
|
12717
|
+
blockKeys: ["kiro"],
|
|
12718
|
+
acceptedTargetNames: /* @__PURE__ */ new Set([
|
|
12719
|
+
"kiro",
|
|
12720
|
+
"kiro-cli",
|
|
12721
|
+
"kiro-ide"
|
|
12722
|
+
])
|
|
12723
|
+
};
|
|
12724
|
+
if (toolTarget === "antigravity-ide" || toolTarget === "antigravity-cli") return {
|
|
12725
|
+
blockKeys: ["antigravity-ide", "antigravity-cli"],
|
|
12726
|
+
acceptedTargetNames: /* @__PURE__ */ new Set(["antigravity-ide", "antigravity-cli"])
|
|
12727
|
+
};
|
|
12728
|
+
return {
|
|
12729
|
+
blockKeys: [toolTarget],
|
|
12730
|
+
acceptedTargetNames: /* @__PURE__ */ new Set([toolTarget])
|
|
12731
|
+
};
|
|
12732
|
+
}
|
|
12733
|
+
/**
|
|
12734
|
+
* Deduplication set for once-per-source-file warnings, keyed by
|
|
12735
|
+
* `<absolute file path>#<warning kind>`. Never cleared: rulesync CLI runs
|
|
12736
|
+
* are one-shot processes, and in a long-lived embedding (the rulesync MCP
|
|
12737
|
+
* server) repeating the same warning per generate would only add noise.
|
|
12738
|
+
*/
|
|
12739
|
+
const warnedOnceKeys = /* @__PURE__ */ new Set();
|
|
12239
12740
|
//#endregion
|
|
12240
12741
|
//#region src/features/mcp/tool-mcp.ts
|
|
12241
12742
|
var ToolMcp = class extends ToolFile {
|
|
@@ -13047,7 +13548,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
13047
13548
|
...rest,
|
|
13048
13549
|
validate: false
|
|
13049
13550
|
});
|
|
13050
|
-
|
|
13551
|
+
let toml;
|
|
13552
|
+
try {
|
|
13553
|
+
toml = smol_toml.parse(this.fileContent);
|
|
13554
|
+
} catch (error) {
|
|
13555
|
+
throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
|
|
13556
|
+
}
|
|
13557
|
+
this.toml = toml;
|
|
13051
13558
|
if (rest.validate) {
|
|
13052
13559
|
const result = this.validate();
|
|
13053
13560
|
if (!result.success) throw result.error;
|
|
@@ -13083,7 +13590,12 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
13083
13590
|
const paths = this.getSettablePaths({ global });
|
|
13084
13591
|
const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13085
13592
|
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13086
|
-
|
|
13593
|
+
let configToml;
|
|
13594
|
+
try {
|
|
13595
|
+
configToml = smol_toml.parse(configTomlFileContent || smol_toml.stringify({}));
|
|
13596
|
+
} catch (error) {
|
|
13597
|
+
throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
|
|
13598
|
+
}
|
|
13087
13599
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
13088
13600
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
13089
13601
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
@@ -14057,47 +14569,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
14057
14569
|
}
|
|
14058
14570
|
};
|
|
14059
14571
|
//#endregion
|
|
14060
|
-
//#region src/constants/grokcli-paths.ts
|
|
14061
|
-
/**
|
|
14062
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14063
|
-
*
|
|
14064
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14065
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14066
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14067
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14068
|
-
*
|
|
14069
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14070
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14071
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14072
|
-
* @see https://docs.x.ai/build/overview
|
|
14073
|
-
*/
|
|
14074
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14075
|
-
const GROKCLI_DIR = ".grok";
|
|
14076
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14077
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14078
|
-
/**
|
|
14079
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14080
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14081
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14082
|
-
*/
|
|
14083
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14084
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14085
|
-
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
14086
|
-
/**
|
|
14087
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14088
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14089
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14090
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14091
|
-
*/
|
|
14092
|
-
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
14093
|
-
/**
|
|
14094
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14095
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14096
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14097
|
-
* MCP/skills/subagents adapters).
|
|
14098
|
-
*/
|
|
14099
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14100
|
-
//#endregion
|
|
14101
14572
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
14102
14573
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
14103
14574
|
/**
|
|
@@ -14257,6 +14728,54 @@ function resolveHermesTimeout(config) {
|
|
|
14257
14728
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
14258
14729
|
}
|
|
14259
14730
|
/**
|
|
14731
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
14732
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
14733
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
14734
|
+
* `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
|
|
14735
|
+
* from `source` to `target`. Field names are identical on both sides (the
|
|
14736
|
+
* canonical `McpServerSchema` is a `looseObject`), so this serves export and
|
|
14737
|
+
* import alike. See the Hermes mcp-config-reference.
|
|
14738
|
+
*/
|
|
14739
|
+
function copyHermesAdvancedFields(source, target) {
|
|
14740
|
+
if (typeof source.auth === "string") target.auth = source.auth;
|
|
14741
|
+
if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
|
|
14742
|
+
if (typeof source.client_key === "string") target.client_key = source.client_key;
|
|
14743
|
+
if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
|
|
14744
|
+
if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
|
|
14745
|
+
}
|
|
14746
|
+
/**
|
|
14747
|
+
* Builds Hermes's per-server `tools` block from a canonical server config. The
|
|
14748
|
+
* canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
|
|
14749
|
+
* and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
|
|
14750
|
+
* `prompts`/`resources` capability flags. Returns an empty object when the
|
|
14751
|
+
* server has no tool scoping (the caller omits the block in that case).
|
|
14752
|
+
*
|
|
14753
|
+
* Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
|
|
14754
|
+
* than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
|
|
14755
|
+
* is reserved as a `string[]` (used by other tools) — reusing it for an object
|
|
14756
|
+
* would fail validation on the next `generate`.
|
|
14757
|
+
*/
|
|
14758
|
+
function buildHermesToolsBlock(config) {
|
|
14759
|
+
const tools = {};
|
|
14760
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
14761
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14762
|
+
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
14763
|
+
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
14764
|
+
return tools;
|
|
14765
|
+
}
|
|
14766
|
+
/**
|
|
14767
|
+
* Applies a Hermes per-server `tools` block back onto a canonical server config
|
|
14768
|
+
* (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
|
|
14769
|
+
* `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
|
|
14770
|
+
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
14771
|
+
*/
|
|
14772
|
+
function applyHermesToolsBlock(hermesTools, server) {
|
|
14773
|
+
if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
14774
|
+
if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
14775
|
+
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
14776
|
+
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
14777
|
+
}
|
|
14778
|
+
/**
|
|
14260
14779
|
* Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
|
|
14261
14780
|
*
|
|
14262
14781
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
@@ -14290,9 +14809,8 @@ function convertServerToHermes(config) {
|
|
|
14290
14809
|
if (config.disabled === true) out.enabled = false;
|
|
14291
14810
|
const timeout = resolveHermesTimeout(config);
|
|
14292
14811
|
if (timeout !== void 0) out.timeout = timeout;
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14812
|
+
copyHermesAdvancedFields(config, out);
|
|
14813
|
+
const tools = buildHermesToolsBlock(config);
|
|
14296
14814
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
14297
14815
|
return out;
|
|
14298
14816
|
}
|
|
@@ -14335,10 +14853,8 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
14335
14853
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
14336
14854
|
if (config.enabled === false) server.disabled = true;
|
|
14337
14855
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
14341
|
-
}
|
|
14856
|
+
copyHermesAdvancedFields(config, server);
|
|
14857
|
+
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
14342
14858
|
result[name] = server;
|
|
14343
14859
|
}
|
|
14344
14860
|
return result;
|
|
@@ -16470,10 +16986,14 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
16470
16986
|
if (!rulesyncMcp) throw new Error(`No ${RULESYNC_MCP_RELATIVE_FILE_PATH} found.`);
|
|
16471
16987
|
const factory = this.getFactory(this.toolTarget);
|
|
16472
16988
|
return await Promise.all([rulesyncMcp].map(async (mcp) => {
|
|
16989
|
+
const targetedRulesyncMcp = mcp.forTarget({
|
|
16990
|
+
toolTarget: this.toolTarget,
|
|
16991
|
+
logger: this.logger
|
|
16992
|
+
});
|
|
16473
16993
|
const fieldsToStrip = [];
|
|
16474
16994
|
if (!factory.meta.supportsEnabledTools) fieldsToStrip.push("enabledTools");
|
|
16475
16995
|
if (!factory.meta.supportsDisabledTools) fieldsToStrip.push("disabledTools");
|
|
16476
|
-
const filteredRulesyncMcp =
|
|
16996
|
+
const filteredRulesyncMcp = targetedRulesyncMcp.stripMcpServerFields(fieldsToStrip);
|
|
16477
16997
|
return await factory.class.fromRulesyncMcp({
|
|
16478
16998
|
outputRoot: this.outputRoot,
|
|
16479
16999
|
rulesyncMcp: filteredRulesyncMcp,
|
|
@@ -16523,6 +17043,25 @@ const PermissionActionSchema = zod_mini.z.enum([
|
|
|
16523
17043
|
*/
|
|
16524
17044
|
const PermissionRulesSchema = zod_mini.z.record(zod_mini.z.string(), PermissionActionSchema);
|
|
16525
17045
|
/**
|
|
17046
|
+
* Tool-scoped canonical `permission` block: the same shape as the shared
|
|
17047
|
+
* top-level `permission` record, but applied only to the tool whose override
|
|
17048
|
+
* key it appears under. During generation the categories placed here are
|
|
17049
|
+
* merged over the shared `permission` block per category (the tool-scoped
|
|
17050
|
+
* category replaces the shared one wholesale), mirroring how
|
|
17051
|
+
* `.rulesync/hooks.json` merges `{toolname}.hooks` per event.
|
|
17052
|
+
*
|
|
17053
|
+
* @example
|
|
17054
|
+
* { "claudecode": { "permission": { "bash": { "git push *": "deny" } } } }
|
|
17055
|
+
*/
|
|
17056
|
+
const ToolScopedPermissionSchema = zod_mini.z.record(zod_mini.z.string(), PermissionRulesSchema);
|
|
17057
|
+
/**
|
|
17058
|
+
* Generic tool-scoped override block for tools that have no tool-specific
|
|
17059
|
+
* override keys of their own; it carries only the canonical tool-scoped
|
|
17060
|
+
* `permission` block. Kept `looseObject` so future tool-specific keys can be
|
|
17061
|
+
* added without a schema break.
|
|
17062
|
+
*/
|
|
17063
|
+
const CanonicalPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(ToolScopedPermissionSchema) });
|
|
17064
|
+
/**
|
|
16526
17065
|
* OpenCode-specific permission value. Unlike the shared canonical block, which
|
|
16527
17066
|
* only accepts a pattern-to-action map, OpenCode also allows a bare action
|
|
16528
17067
|
* string that applies to the whole category (e.g. `"external_directory": "deny"`).
|
|
@@ -16552,7 +17091,7 @@ const OpencodePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: z
|
|
|
16552
17091
|
* @example
|
|
16553
17092
|
* { "approvals": { "mode": "smart" }, "security": { "allow_private_urls": false } }
|
|
16554
17093
|
*/
|
|
16555
|
-
const HermesPermissionsOverrideSchema = zod_mini.z.looseObject({});
|
|
17094
|
+
const HermesPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(ToolScopedPermissionSchema) });
|
|
16556
17095
|
/**
|
|
16557
17096
|
* Tool-scoped override block for Cline. Cline's `command-permissions.json`
|
|
16558
17097
|
* carries a single global `allowRedirects` boolean (gates shell redirection
|
|
@@ -16564,7 +17103,10 @@ const HermesPermissionsOverrideSchema = zod_mini.z.looseObject({});
|
|
|
16564
17103
|
* @example
|
|
16565
17104
|
* { "allowRedirects": true }
|
|
16566
17105
|
*/
|
|
16567
|
-
const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17106
|
+
const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17107
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17108
|
+
allowRedirects: zod_mini.z.optional(zod_mini.z.boolean())
|
|
17109
|
+
});
|
|
16568
17110
|
/**
|
|
16569
17111
|
* Tool-scoped override block for Kilo Code (an OpenCode fork). Kilo's permission
|
|
16570
17112
|
* object is a free-form record with tool-specific keys that have no canonical
|
|
@@ -16594,7 +17136,10 @@ const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_m
|
|
|
16594
17136
|
* @example
|
|
16595
17137
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
16596
17138
|
*/
|
|
16597
|
-
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17139
|
+
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17140
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17141
|
+
permissions: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
17142
|
+
});
|
|
16598
17143
|
/**
|
|
16599
17144
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
16600
17145
|
* carries a `sensitive_patterns` list — patterns that escalate to ASK even when
|
|
@@ -16626,6 +17171,7 @@ const VibePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_m
|
|
|
16626
17171
|
* { "approvalMode": "auto-review" }
|
|
16627
17172
|
*/
|
|
16628
17173
|
const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17174
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16629
17175
|
approvalMode: zod_mini.z.optional(zod_mini.z.string()),
|
|
16630
17176
|
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16631
17177
|
});
|
|
@@ -16633,18 +17179,28 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16633
17179
|
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
16634
17180
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
16635
17181
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
16636
|
-
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`).
|
|
16637
|
-
*
|
|
16638
|
-
*
|
|
16639
|
-
*
|
|
16640
|
-
*
|
|
17182
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
|
|
17183
|
+
* exposes `permissions.autoMode` (the Auto Mode classifier config:
|
|
17184
|
+
* `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
|
|
17185
|
+
* https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
|
|
17186
|
+
* likewise has no canonical category. Fields placed here are merged into the
|
|
17187
|
+
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
17188
|
+
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
17189
|
+
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
17190
|
+
* `tools`/`security`/`autoMode` key can be authored.
|
|
16641
17191
|
*
|
|
16642
17192
|
* @example
|
|
16643
|
-
* {
|
|
17193
|
+
* {
|
|
17194
|
+
* "tools": { "approvalMode": "auto-edit" },
|
|
17195
|
+
* "security": { "folderTrust": { "enabled": true } },
|
|
17196
|
+
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
17197
|
+
* }
|
|
16644
17198
|
*/
|
|
16645
17199
|
const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17200
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16646
17201
|
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16647
|
-
security: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
17202
|
+
security: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
17203
|
+
autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16648
17204
|
});
|
|
16649
17205
|
/**
|
|
16650
17206
|
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
@@ -16663,6 +17219,7 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16663
17219
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
16664
17220
|
*/
|
|
16665
17221
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17222
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16666
17223
|
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16667
17224
|
agent: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16668
17225
|
});
|
|
@@ -16671,16 +17228,21 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16671
17228
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
16672
17229
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
16673
17230
|
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
16674
|
-
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
17231
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
17232
|
+
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
16675
17233
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
16676
|
-
* `interactionMode`). Fields placed here are merged
|
|
17234
|
+
* `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
|
|
17235
|
+
* into `settings.json` and
|
|
16677
17236
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
16678
17237
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
16679
17238
|
*
|
|
16680
17239
|
* @example
|
|
16681
17240
|
* { "commandBlocklist": ["rm -rf /*"], "sandbox": { "enabled": true } }
|
|
16682
17241
|
*/
|
|
16683
|
-
const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17242
|
+
const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17243
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17244
|
+
commandBlocklist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
17245
|
+
});
|
|
16684
17246
|
/**
|
|
16685
17247
|
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
16686
17248
|
* file-read/read-only autonomy keys with no canonical per-command allow/ask/deny
|
|
@@ -16697,6 +17259,7 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({ commandBl
|
|
|
16697
17259
|
* { "agent_mode_coding_permissions": "always_allow_reading", "agent_mode_execute_readonly_commands": true }
|
|
16698
17260
|
*/
|
|
16699
17261
|
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17262
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16700
17263
|
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.string()),
|
|
16701
17264
|
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
16702
17265
|
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -16714,6 +17277,7 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16714
17277
|
* { "allowReadonlyCommands": true, "defaultBehavior": "ask" }
|
|
16715
17278
|
*/
|
|
16716
17279
|
const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17280
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16717
17281
|
allowReadonlyCommands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
16718
17282
|
defaultBehavior: zod_mini.z.optional(zod_mini.z.string())
|
|
16719
17283
|
});
|
|
@@ -16741,6 +17305,7 @@ const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16741
17305
|
* { "step_permission_overrides": { "ai_review": "readonly" }, "provider_options": { "codex": { "network_access": true } } }
|
|
16742
17306
|
*/
|
|
16743
17307
|
const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17308
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16744
17309
|
step_permission_overrides: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
16745
17310
|
provider_options: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16746
17311
|
});
|
|
@@ -16768,6 +17333,7 @@ const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16768
17333
|
* "permissions": [{ "tool": "Bash", "action": "delegate", "to": "approve.sh" }] }
|
|
16769
17334
|
*/
|
|
16770
17335
|
const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17336
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16771
17337
|
permissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
16772
17338
|
tool: zod_mini.z.string(),
|
|
16773
17339
|
action: zod_mini.z.string()
|
|
@@ -16796,6 +17362,7 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16796
17362
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
16797
17363
|
*/
|
|
16798
17364
|
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17365
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
16799
17366
|
toolPermission: zod_mini.z.optional(zod_mini.z.string()),
|
|
16800
17367
|
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16801
17368
|
});
|
|
@@ -16821,10 +17388,13 @@ const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16821
17388
|
* "permission": { "type": "webhook-policy", "webhookUrl": "https://api.example.com/validate" } },
|
|
16822
17389
|
* { "toolName": "view", "eventType": "tool-response", "permission": { "type": "allow" } } ] }
|
|
16823
17390
|
*/
|
|
16824
|
-
const AugmentcodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
17391
|
+
const AugmentcodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17392
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17393
|
+
toolPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
17394
|
+
toolName: zod_mini.z.string(),
|
|
17395
|
+
permission: zod_mini.z.looseObject({ type: zod_mini.z.string() })
|
|
17396
|
+
})))
|
|
17397
|
+
});
|
|
16828
17398
|
/**
|
|
16829
17399
|
* Tool-scoped override block for Kiro. Kiro's agent config (`.kiro/agents/<name>.json`)
|
|
16830
17400
|
* exposes per-tool `toolsSettings` knobs with no canonical allow/ask/deny
|
|
@@ -16850,21 +17420,59 @@ const AugmentcodePermissionsOverrideSchema = zod_mini.z.looseObject({ toolPermis
|
|
|
16850
17420
|
* "aws": { "allowedServices": ["s3"], "deniedServices": ["eks"] },
|
|
16851
17421
|
* "web_fetch": { "trusted": [".*github\\.com.*"] } } }
|
|
16852
17422
|
*/
|
|
16853
|
-
const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
17423
|
+
const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
17424
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17425
|
+
toolsSettings: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
17426
|
+
shell: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
17427
|
+
autoAllowReadonly: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
17428
|
+
denyByDefault: zod_mini.z.optional(zod_mini.z.boolean())
|
|
17429
|
+
})),
|
|
17430
|
+
aws: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
17431
|
+
allowedServices: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
17432
|
+
deniedServices: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
17433
|
+
autoAllowReadonly: zod_mini.z.optional(zod_mini.z.boolean())
|
|
17434
|
+
})),
|
|
17435
|
+
web_fetch: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
17436
|
+
trusted: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
17437
|
+
blocked: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
17438
|
+
}))
|
|
16866
17439
|
}))
|
|
16867
|
-
})
|
|
17440
|
+
});
|
|
17441
|
+
/**
|
|
17442
|
+
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
17443
|
+
* `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
|
|
17444
|
+
* Codex still accepts, so it is included so existing configs round-trip. The
|
|
17445
|
+
* granular table form (`{ granular = { … } }`) is modeled separately in the
|
|
17446
|
+
* override union.
|
|
17447
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17448
|
+
*/
|
|
17449
|
+
const CodexApprovalPolicySchema = zod_mini.z.enum([
|
|
17450
|
+
"untrusted",
|
|
17451
|
+
"on-request",
|
|
17452
|
+
"on-failure",
|
|
17453
|
+
"never"
|
|
17454
|
+
]);
|
|
17455
|
+
/**
|
|
17456
|
+
* Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
|
|
17457
|
+
* `.codex/config.toml`.
|
|
17458
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17459
|
+
*/
|
|
17460
|
+
const CodexSandboxModeSchema = zod_mini.z.enum([
|
|
17461
|
+
"read-only",
|
|
17462
|
+
"workspace-write",
|
|
17463
|
+
"danger-full-access"
|
|
17464
|
+
]);
|
|
17465
|
+
/**
|
|
17466
|
+
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
17467
|
+
* value Codex still accepts for backward compatibility, so it is included so
|
|
17468
|
+
* existing configs round-trip through the rulesync model.
|
|
17469
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17470
|
+
*/
|
|
17471
|
+
const CodexApprovalsReviewerSchema = zod_mini.z.enum([
|
|
17472
|
+
"user",
|
|
17473
|
+
"auto_review",
|
|
17474
|
+
"guardian_subagent"
|
|
17475
|
+
]);
|
|
16868
17476
|
/**
|
|
16869
17477
|
* Codex CLI-scoped permission override.
|
|
16870
17478
|
*
|
|
@@ -16874,15 +17482,16 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
|
|
|
16874
17482
|
* override whose fields are written verbatim as top-level `.codex/config.toml`
|
|
16875
17483
|
* keys (the override wins per key; existing sibling keys the user set directly
|
|
16876
17484
|
* are preserved):
|
|
16877
|
-
* - `approval_policy` — `untrusted` | `on-request`
|
|
16878
|
-
* `{ granular = { … } }` table (kept verbatim; the granular
|
|
16879
|
-
* required fields that are brittle to model as typed keys).
|
|
17485
|
+
* - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
|
|
17486
|
+
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
17487
|
+
* schema has required fields that are brittle to model as typed keys).
|
|
16880
17488
|
* - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
|
|
16881
17489
|
* with the sibling `sandbox_workspace_write` table (`network_access`,
|
|
16882
17490
|
* `writable_roots`, …).
|
|
16883
17491
|
* - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
|
|
16884
17492
|
* `.enabled`, `apps.<id>.default_tools_approval_mode`).
|
|
16885
|
-
* - `approvals_reviewer` — the reviewer-approval surface
|
|
17493
|
+
* - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
|
|
17494
|
+
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
16886
17495
|
*
|
|
16887
17496
|
* Two surfaces are deliberately NOT authorable here so the override can never
|
|
16888
17497
|
* clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
|
|
@@ -16900,11 +17509,12 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
|
|
|
16900
17509
|
* "sandbox_workspace_write": { "network_access": true } }
|
|
16901
17510
|
*/
|
|
16902
17511
|
const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16903
|
-
|
|
16904
|
-
|
|
17512
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
17513
|
+
approval_policy: zod_mini.z.optional(zod_mini.z.union([CodexApprovalPolicySchema, zod_mini.z.looseObject({})])),
|
|
17514
|
+
sandbox_mode: zod_mini.z.optional(CodexSandboxModeSchema),
|
|
16905
17515
|
sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16906
17516
|
apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16907
|
-
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([
|
|
17517
|
+
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})]))
|
|
16908
17518
|
});
|
|
16909
17519
|
/**
|
|
16910
17520
|
* Permissions configuration.
|
|
@@ -16918,6 +17528,15 @@ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16918
17528
|
* `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
|
|
16919
17529
|
* block and ignores them.
|
|
16920
17530
|
*
|
|
17531
|
+
* Additionally, every permissions-capable tool accepts a canonical tool-scoped
|
|
17532
|
+
* `permission` block under its override key (`{toolname}.permission`, same
|
|
17533
|
+
* shape as the shared `permission` record). Its categories apply only to that
|
|
17534
|
+
* tool, merged over the shared block per category — see
|
|
17535
|
+
* `RulesyncPermissions.forTarget`. `kiro-cli`/`kiro-ide` alias to the `kiro`
|
|
17536
|
+
* key and `hermesagent` to `hermes` (matching the shared output file each
|
|
17537
|
+
* writes). OpenCode/Kilo/Vibe keep their existing tool-native `permission`
|
|
17538
|
+
* override semantics (handled by their translators, not the central merge).
|
|
17539
|
+
*
|
|
16921
17540
|
* @example
|
|
16922
17541
|
* {
|
|
16923
17542
|
* "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
|
|
@@ -16943,7 +17562,13 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
16943
17562
|
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
16944
17563
|
augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
|
|
16945
17564
|
kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
|
|
16946
|
-
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema)
|
|
17565
|
+
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema),
|
|
17566
|
+
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
17567
|
+
devin: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
17568
|
+
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
17569
|
+
grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
17570
|
+
rovodev: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
17571
|
+
zed: zod_mini.z.optional(CanonicalPermissionsOverrideSchema)
|
|
16947
17572
|
});
|
|
16948
17573
|
/**
|
|
16949
17574
|
* Full permissions file schema including optional $schema field.
|
|
@@ -16958,7 +17583,7 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16958
17583
|
json;
|
|
16959
17584
|
constructor(params) {
|
|
16960
17585
|
super({ ...params });
|
|
16961
|
-
this.json =
|
|
17586
|
+
this.json = parseJsonc$6(this.fileContent);
|
|
16962
17587
|
if (params.validate) {
|
|
16963
17588
|
const result = this.validate();
|
|
16964
17589
|
if (!result.success) throw result.error;
|
|
@@ -16967,7 +17592,11 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16967
17592
|
static getSettablePaths() {
|
|
16968
17593
|
return {
|
|
16969
17594
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
16970
|
-
relativeFilePath: RULESYNC_PERMISSIONS_FILE_NAME
|
|
17595
|
+
relativeFilePath: RULESYNC_PERMISSIONS_FILE_NAME,
|
|
17596
|
+
jsonc: {
|
|
17597
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
17598
|
+
relativeFilePath: RULESYNC_PERMISSIONS_JSONC_FILE_NAME
|
|
17599
|
+
}
|
|
16971
17600
|
};
|
|
16972
17601
|
}
|
|
16973
17602
|
validate() {
|
|
@@ -16983,20 +17612,85 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16983
17612
|
}
|
|
16984
17613
|
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
16985
17614
|
const paths = RulesyncPermissions.getSettablePaths();
|
|
16986
|
-
const
|
|
16987
|
-
if (!await fileExists(filePath)) throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} found.`);
|
|
16988
|
-
const fileContent = await readFileContent(filePath);
|
|
16989
|
-
return new RulesyncPermissions({
|
|
16990
|
-
outputRoot,
|
|
17615
|
+
const candidates = [paths.jsonc, {
|
|
16991
17616
|
relativeDirPath: paths.relativeDirPath,
|
|
16992
|
-
relativeFilePath: paths.relativeFilePath
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
17617
|
+
relativeFilePath: paths.relativeFilePath
|
|
17618
|
+
}];
|
|
17619
|
+
for (const candidate of candidates) {
|
|
17620
|
+
const filePath = (0, node_path.join)(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath);
|
|
17621
|
+
if (!await fileExists(filePath)) continue;
|
|
17622
|
+
const fileContent = await readFileContent(filePath);
|
|
17623
|
+
return new RulesyncPermissions({
|
|
17624
|
+
outputRoot,
|
|
17625
|
+
relativeDirPath: candidate.relativeDirPath,
|
|
17626
|
+
relativeFilePath: candidate.relativeFilePath,
|
|
17627
|
+
fileContent,
|
|
17628
|
+
validate
|
|
17629
|
+
});
|
|
17630
|
+
}
|
|
17631
|
+
throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} or ${RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH} found.`);
|
|
16996
17632
|
}
|
|
16997
17633
|
getJson() {
|
|
16998
17634
|
return this.json;
|
|
16999
17635
|
}
|
|
17636
|
+
/**
|
|
17637
|
+
* Build the effective permissions config for one tool target by merging the
|
|
17638
|
+
* tool-scoped `{toolname}.permission` block over the shared `permission`
|
|
17639
|
+
* block, per category (the tool-scoped category replaces the shared one
|
|
17640
|
+
* wholesale — mirroring how `{toolname}.hooks` merges per event in
|
|
17641
|
+
* `.rulesync/hooks.json`). The consumed `permission` key is stripped from
|
|
17642
|
+
* the override block so verbatim-passthrough translators (e.g. the Hermes
|
|
17643
|
+
* deep merge or the Codex CLI top-level key whitelist) never see it.
|
|
17644
|
+
*
|
|
17645
|
+
* Returns the same instance when the target has no tool-scoped canonical
|
|
17646
|
+
* `permission` block, or when the target's translator natively consumes its
|
|
17647
|
+
* own `permission` override shape (OpenCode / Kilo / Vibe).
|
|
17648
|
+
*/
|
|
17649
|
+
forTarget({ toolTarget, logger }) {
|
|
17650
|
+
if (NATIVE_PERMISSION_OVERRIDE_TARGETS.has(toolTarget)) return this;
|
|
17651
|
+
const overrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
|
|
17652
|
+
const json = this.json;
|
|
17653
|
+
if (overrideKey !== toolTarget && isRecord(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
17654
|
+
const overrideBlock = json[overrideKey];
|
|
17655
|
+
if (!isRecord(overrideBlock) || !isRecord(overrideBlock.permission)) return this;
|
|
17656
|
+
const { permission: toolScopedPermission, ...restOverride } = overrideBlock;
|
|
17657
|
+
const merged = {
|
|
17658
|
+
...json,
|
|
17659
|
+
permission: {
|
|
17660
|
+
...this.json.permission,
|
|
17661
|
+
...toolScopedPermission
|
|
17662
|
+
}
|
|
17663
|
+
};
|
|
17664
|
+
if (Object.keys(restOverride).length > 0) merged[overrideKey] = restOverride;
|
|
17665
|
+
else delete merged[overrideKey];
|
|
17666
|
+
return new RulesyncPermissions({
|
|
17667
|
+
outputRoot: this.outputRoot,
|
|
17668
|
+
relativeDirPath: this.relativeDirPath,
|
|
17669
|
+
relativeFilePath: this.relativeFilePath,
|
|
17670
|
+
fileContent: JSON.stringify(merged, null, 2)
|
|
17671
|
+
});
|
|
17672
|
+
}
|
|
17673
|
+
};
|
|
17674
|
+
/**
|
|
17675
|
+
* Targets whose tool-scoped `permission` override uses tool-native semantics
|
|
17676
|
+
* consumed directly by their translator (bare action strings / tool-only
|
|
17677
|
+
* categories for OpenCode and Kilo, `sensitive_patterns` objects for Vibe).
|
|
17678
|
+
* The central canonical merge must not touch those blocks.
|
|
17679
|
+
*/
|
|
17680
|
+
const NATIVE_PERMISSION_OVERRIDE_TARGETS = /* @__PURE__ */ new Set([
|
|
17681
|
+
"opencode",
|
|
17682
|
+
"kilo",
|
|
17683
|
+
"vibe"
|
|
17684
|
+
]);
|
|
17685
|
+
/**
|
|
17686
|
+
* Targets that read their tool-scoped override from a differently named key:
|
|
17687
|
+
* Kiro IDE/CLI share the `kiro` block (they write the same agent config) and
|
|
17688
|
+
* Hermes Agent's established override key is `hermes`.
|
|
17689
|
+
*/
|
|
17690
|
+
const PERMISSION_OVERRIDE_KEY_ALIASES = {
|
|
17691
|
+
"kiro-cli": "kiro",
|
|
17692
|
+
"kiro-ide": "kiro",
|
|
17693
|
+
hermesagent: "hermes"
|
|
17000
17694
|
};
|
|
17001
17695
|
//#endregion
|
|
17002
17696
|
//#region src/features/permissions/tool-permissions.ts
|
|
@@ -18836,10 +19530,15 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18836
19530
|
existingDomainsHadUnknown,
|
|
18837
19531
|
logger
|
|
18838
19532
|
});
|
|
18839
|
-
|
|
19533
|
+
const profile = mergeWithExistingProfile({
|
|
18840
19534
|
newProfile,
|
|
18841
19535
|
existingProfile
|
|
18842
19536
|
});
|
|
19537
|
+
permissionsTable[RULESYNC_PROFILE_NAME] = preserveUnmanagedProfileKeys({
|
|
19538
|
+
rawExistingProfile: permissionsTable[RULESYNC_PROFILE_NAME],
|
|
19539
|
+
profile,
|
|
19540
|
+
logger
|
|
19541
|
+
});
|
|
18843
19542
|
const overridePatch = computeCodexcliOverridePatch({
|
|
18844
19543
|
existing,
|
|
18845
19544
|
override: rulesyncPermissions.getJson().codexcli,
|
|
@@ -19035,6 +19734,43 @@ function warnAboutPreservedProfileState({ existingProfile, newProfile, existingD
|
|
|
19035
19734
|
if (existingProfile?.network?.mode !== void 0) logger?.warn(`Preserving existing "network.mode" from config. Review this value manually as it may grant broader network access than the Rulesync-managed domain rules.`);
|
|
19036
19735
|
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19037
19736
|
}
|
|
19737
|
+
const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
19738
|
+
"description",
|
|
19739
|
+
"extends",
|
|
19740
|
+
"filesystem",
|
|
19741
|
+
"network"
|
|
19742
|
+
]);
|
|
19743
|
+
const MANAGED_NETWORK_KEYS = /* @__PURE__ */ new Set([
|
|
19744
|
+
"enabled",
|
|
19745
|
+
"mode",
|
|
19746
|
+
"domains",
|
|
19747
|
+
"unix_sockets"
|
|
19748
|
+
]);
|
|
19749
|
+
/**
|
|
19750
|
+
* Re-attach the keys of the existing rulesync profile that rulesync does not
|
|
19751
|
+
* manage. The managed keys always come from `profile` (the freshly computed
|
|
19752
|
+
* merge result); unmanaged siblings — at the profile level and inside the
|
|
19753
|
+
* `network` table — are preserved verbatim from the existing config so a
|
|
19754
|
+
* regeneration never deletes user-authored Codex settings.
|
|
19755
|
+
*/
|
|
19756
|
+
function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
|
|
19757
|
+
const rawProfileTable = toMutableTable(rawExistingProfile);
|
|
19758
|
+
const profileExtras = Object.fromEntries(Object.entries(rawProfileTable).filter(([key]) => !MANAGED_PROFILE_KEYS.has(key)));
|
|
19759
|
+
const rawNetworkTable = toMutableTable(rawProfileTable.network);
|
|
19760
|
+
const networkExtras = Object.fromEntries(Object.entries(rawNetworkTable).filter(([key]) => !MANAGED_NETWORK_KEYS.has(key)));
|
|
19761
|
+
const preservedKeyNames = [...Object.keys(profileExtras), ...Object.keys(networkExtras).map((key) => `network.${key}`)];
|
|
19762
|
+
if (preservedKeyNames.length > 0) logger?.warn(`Preserving unmanaged keys in the "${RULESYNC_PROFILE_NAME}" permissions profile: ${preservedKeyNames.join(", ")}. Review them manually; rulesync carries them forward verbatim.`);
|
|
19763
|
+
const result = {
|
|
19764
|
+
...profileExtras,
|
|
19765
|
+
...profile
|
|
19766
|
+
};
|
|
19767
|
+
const mergedNetwork = {
|
|
19768
|
+
...networkExtras,
|
|
19769
|
+
...profile.network
|
|
19770
|
+
};
|
|
19771
|
+
if (Object.keys(mergedNetwork).length > 0) result.network = mergedNetwork;
|
|
19772
|
+
return result;
|
|
19773
|
+
}
|
|
19038
19774
|
function mergeWithExistingProfile({ newProfile, existingProfile }) {
|
|
19039
19775
|
if (!existingProfile) return newProfile;
|
|
19040
19776
|
const mergedNetwork = { ...newProfile.network };
|
|
@@ -19765,9 +20501,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
19765
20501
|
"networkPolicy",
|
|
19766
20502
|
"sandbox",
|
|
19767
20503
|
"mcpPolicy",
|
|
20504
|
+
"mcpAutonomyOverrides",
|
|
19768
20505
|
"enableDroidShield",
|
|
19769
20506
|
"sessionDefaultSettings",
|
|
19770
20507
|
"maxAutonomyLevel",
|
|
20508
|
+
"subagentAutonomyLevel",
|
|
19771
20509
|
"interactionMode"
|
|
19772
20510
|
];
|
|
19773
20511
|
/**
|
|
@@ -20125,7 +20863,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
20125
20863
|
}
|
|
20126
20864
|
//#endregion
|
|
20127
20865
|
//#region src/features/permissions/grokcli-permissions.ts
|
|
20128
|
-
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
20129
20866
|
const GROKCLI_UI_KEY = "ui";
|
|
20130
20867
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
20131
20868
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
@@ -20227,13 +20964,19 @@ function parseGrokEntry(entry) {
|
|
|
20227
20964
|
* compatibility with older Grok versions: `always-approve` when the config is
|
|
20228
20965
|
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
20229
20966
|
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
20230
|
-
* arrays).
|
|
20231
|
-
*
|
|
20232
|
-
*
|
|
20233
|
-
*
|
|
20234
|
-
*
|
|
20967
|
+
* arrays). It is a user-level UI setting, so it is only written in global scope
|
|
20968
|
+
* (see below).
|
|
20969
|
+
*
|
|
20970
|
+
* Both scopes are supported: the adapter syncs the project-level
|
|
20971
|
+
* `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
|
|
20972
|
+
* (with `--global`). Grok documents that "Project configs are limited to MCP
|
|
20973
|
+
* servers, plugins, and permission rules, not full user configs"
|
|
20974
|
+
* (https://docs.x.ai/build/settings), so the project config carries only the
|
|
20975
|
+
* fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
|
|
20976
|
+
* is written in global scope only. The shared config is merged in
|
|
20235
20977
|
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
20236
|
-
* tools it models and the `[ui] permission_mode` value, while
|
|
20978
|
+
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
20979
|
+
* every other key
|
|
20237
20980
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
20238
20981
|
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
20239
20982
|
* file is never deleted.
|
|
@@ -20255,7 +20998,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20255
20998
|
};
|
|
20256
20999
|
}
|
|
20257
21000
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20258
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
20259
21001
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
20260
21002
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
20261
21003
|
return new GrokcliPermissions({
|
|
@@ -20264,11 +21006,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20264
21006
|
relativeFilePath: paths.relativeFilePath,
|
|
20265
21007
|
fileContent,
|
|
20266
21008
|
validate,
|
|
20267
|
-
global
|
|
21009
|
+
global
|
|
20268
21010
|
});
|
|
20269
21011
|
}
|
|
20270
21012
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
20271
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
20272
21013
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
20273
21014
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20274
21015
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -20287,11 +21028,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20287
21028
|
deny: buckets.deny,
|
|
20288
21029
|
ask: buckets.ask
|
|
20289
21030
|
};
|
|
20290
|
-
const
|
|
20291
|
-
const ui = {
|
|
21031
|
+
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
20292
21032
|
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
20293
|
-
[GROKCLI_PERMISSION_MODE_KEY]:
|
|
20294
|
-
};
|
|
21033
|
+
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
21034
|
+
} } : {};
|
|
20295
21035
|
return new GrokcliPermissions({
|
|
20296
21036
|
outputRoot,
|
|
20297
21037
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -20302,12 +21042,12 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20302
21042
|
existingContent,
|
|
20303
21043
|
patch: {
|
|
20304
21044
|
[GROKCLI_PERMISSION_KEY]: permission,
|
|
20305
|
-
|
|
21045
|
+
...uiPatch
|
|
20306
21046
|
},
|
|
20307
21047
|
filePath
|
|
20308
21048
|
}),
|
|
20309
21049
|
validate: true,
|
|
20310
|
-
global
|
|
21050
|
+
global
|
|
20311
21051
|
});
|
|
20312
21052
|
}
|
|
20313
21053
|
toRulesyncPermissions() {
|
|
@@ -20328,14 +21068,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20328
21068
|
error: null
|
|
20329
21069
|
};
|
|
20330
21070
|
}
|
|
20331
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
21071
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
20332
21072
|
return new GrokcliPermissions({
|
|
20333
21073
|
outputRoot,
|
|
20334
21074
|
relativeDirPath,
|
|
20335
21075
|
relativeFilePath,
|
|
20336
21076
|
fileContent: "",
|
|
20337
21077
|
validate: false,
|
|
20338
|
-
global
|
|
21078
|
+
global
|
|
20339
21079
|
});
|
|
20340
21080
|
}
|
|
20341
21081
|
};
|
|
@@ -21250,6 +21990,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
21250
21990
|
"notebookedit",
|
|
21251
21991
|
"agent"
|
|
21252
21992
|
]);
|
|
21993
|
+
/**
|
|
21994
|
+
* Translate between rulesync's canonical permission category names and
|
|
21995
|
+
* OpenCode's native permission keys. OpenCode has no `agent` key — subagent
|
|
21996
|
+
* launches are gated by the `task` key (see the documented key list at
|
|
21997
|
+
* https://opencode.ai/docs/permissions/). Without this translation a canonical
|
|
21998
|
+
* `agent: deny` would be written verbatim into `opencode.json` and silently
|
|
21999
|
+
* ignored by OpenCode. Unknown names pass through unchanged.
|
|
22000
|
+
*/
|
|
22001
|
+
const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
|
|
22002
|
+
const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
|
|
22003
|
+
function toOpencodePermissionKey(canonical) {
|
|
22004
|
+
return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
|
|
22005
|
+
}
|
|
22006
|
+
function toCanonicalPermissionKey(opencodeKey) {
|
|
22007
|
+
return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
|
|
22008
|
+
}
|
|
21253
22009
|
function isSharedPermissionCategory(category) {
|
|
21254
22010
|
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
21255
22011
|
}
|
|
@@ -21316,6 +22072,8 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21316
22072
|
}
|
|
21317
22073
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
21318
22074
|
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
22075
|
+
const sharedPermission = {};
|
|
22076
|
+
for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
|
|
21319
22077
|
return new OpencodePermissions({
|
|
21320
22078
|
outputRoot,
|
|
21321
22079
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -21325,7 +22083,7 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21325
22083
|
feature: "permissions",
|
|
21326
22084
|
existingContent: fileContent ?? "",
|
|
21327
22085
|
patch: { permission: {
|
|
21328
|
-
...
|
|
22086
|
+
...sharedPermission,
|
|
21329
22087
|
...overridePermission
|
|
21330
22088
|
} },
|
|
21331
22089
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
@@ -21341,8 +22099,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21341
22099
|
}
|
|
21342
22100
|
const shared = {};
|
|
21343
22101
|
const overrideOnly = {};
|
|
21344
|
-
for (const [category, value] of Object.entries(rawPermission))
|
|
21345
|
-
|
|
22102
|
+
for (const [category, value] of Object.entries(rawPermission)) {
|
|
22103
|
+
const canonicalCategory = toCanonicalPermissionKey(category);
|
|
22104
|
+
if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
|
|
22105
|
+
else overrideOnly[category] = value;
|
|
22106
|
+
}
|
|
21346
22107
|
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
21347
22108
|
permission: shared,
|
|
21348
22109
|
opencode: { permission: overrideOnly }
|
|
@@ -21467,6 +22228,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
21467
22228
|
"disabled"
|
|
21468
22229
|
];
|
|
21469
22230
|
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
22231
|
+
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
21470
22232
|
function asPlainRecord(value) {
|
|
21471
22233
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21472
22234
|
}
|
|
@@ -21538,8 +22300,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21538
22300
|
else delete mergedPermissions.ask;
|
|
21539
22301
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21540
22302
|
else delete mergedPermissions.deny;
|
|
21541
|
-
const patch = { permissions: mergedPermissions };
|
|
21542
22303
|
const override = config.qwencode;
|
|
22304
|
+
if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
|
|
22305
|
+
const patch = { permissions: mergedPermissions };
|
|
21543
22306
|
if (override?.tools !== void 0) patch.tools = {
|
|
21544
22307
|
...asPlainRecord(settings.tools),
|
|
21545
22308
|
...asPlainRecord(override.tools)
|
|
@@ -21581,9 +22344,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21581
22344
|
});
|
|
21582
22345
|
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
21583
22346
|
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
22347
|
+
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
21584
22348
|
const qwencodeOverride = {};
|
|
21585
22349
|
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
21586
22350
|
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
22351
|
+
if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
|
|
21587
22352
|
const result = { ...config };
|
|
21588
22353
|
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
21589
22354
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -23092,7 +23857,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
23092
23857
|
["grokcli", {
|
|
23093
23858
|
class: GrokcliPermissions,
|
|
23094
23859
|
meta: {
|
|
23095
|
-
supportsProject:
|
|
23860
|
+
supportsProject: true,
|
|
23096
23861
|
supportsGlobal: true,
|
|
23097
23862
|
supportsImport: true
|
|
23098
23863
|
}
|
|
@@ -23267,16 +24032,20 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
23267
24032
|
if (!rulesyncPermissions) throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} found.`);
|
|
23268
24033
|
const factory = toolPermissionsFactories.get(this.toolTarget);
|
|
23269
24034
|
if (!factory) throw new Error(`Unsupported tool target: ${this.toolTarget}`);
|
|
24035
|
+
const effectivePermissions = rulesyncPermissions.forTarget({
|
|
24036
|
+
toolTarget: this.toolTarget,
|
|
24037
|
+
logger: this.logger
|
|
24038
|
+
});
|
|
23270
24039
|
const toolPermissions = await factory.class.fromRulesyncPermissions({
|
|
23271
24040
|
outputRoot: this.outputRoot,
|
|
23272
|
-
rulesyncPermissions,
|
|
24041
|
+
rulesyncPermissions: effectivePermissions,
|
|
23273
24042
|
logger: this.logger,
|
|
23274
24043
|
global: this.global
|
|
23275
24044
|
});
|
|
23276
24045
|
if (this.toolTarget !== "codexcli") return [toolPermissions];
|
|
23277
24046
|
return [toolPermissions, createCodexcliBashRulesFile({
|
|
23278
24047
|
outputRoot: this.outputRoot,
|
|
23279
|
-
config:
|
|
24048
|
+
config: effectivePermissions.getJson()
|
|
23280
24049
|
})];
|
|
23281
24050
|
}
|
|
23282
24051
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
@@ -39372,6 +40141,16 @@ async function generatePermissionsCore(params) {
|
|
|
39372
40141
|
//#endregion
|
|
39373
40142
|
//#region src/lib/import.ts
|
|
39374
40143
|
/**
|
|
40144
|
+
* Import always writes the `.json` variant of a fixed-path source file, but a
|
|
40145
|
+
* sibling `.jsonc` variant takes precedence at read time — so an import into a
|
|
40146
|
+
* project that authors the `.jsonc` variant would be silently shadowed. Warn
|
|
40147
|
+
* so the user merges (or removes) one of the two by hand.
|
|
40148
|
+
*/
|
|
40149
|
+
async function warnIfShadowedByJsonc(params) {
|
|
40150
|
+
const { outputRoot, jsonRelativePath, jsoncRelativePath, logger } = params;
|
|
40151
|
+
if (await fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) logger.warn(`${jsoncRelativePath} exists and takes precedence over the imported ${jsonRelativePath}. Merge the imported content into ${jsoncRelativePath} (or remove it) so the import takes effect.`);
|
|
40152
|
+
}
|
|
40153
|
+
/**
|
|
39375
40154
|
* Import configuration files from AI tools.
|
|
39376
40155
|
*/
|
|
39377
40156
|
async function importFromTool(params) {
|
|
@@ -39483,6 +40262,12 @@ async function importMcpCore(params) {
|
|
|
39483
40262
|
}
|
|
39484
40263
|
const rulesyncFiles = await mcpProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39485
40264
|
const { count: writtenCount } = await mcpProcessor.writeAiFiles(rulesyncFiles);
|
|
40265
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40266
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40267
|
+
jsonRelativePath: RULESYNC_MCP_RELATIVE_FILE_PATH,
|
|
40268
|
+
jsoncRelativePath: RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH,
|
|
40269
|
+
logger
|
|
40270
|
+
});
|
|
39486
40271
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} MCP files`);
|
|
39487
40272
|
return writtenCount;
|
|
39488
40273
|
}
|
|
@@ -39582,6 +40367,12 @@ async function importHooksCore(params) {
|
|
|
39582
40367
|
}
|
|
39583
40368
|
const rulesyncFiles = await hooksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39584
40369
|
const { count: writtenCount } = await hooksProcessor.writeAiFiles(rulesyncFiles);
|
|
40370
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40371
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40372
|
+
jsonRelativePath: RULESYNC_HOOKS_RELATIVE_FILE_PATH,
|
|
40373
|
+
jsoncRelativePath: RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH,
|
|
40374
|
+
logger
|
|
40375
|
+
});
|
|
39585
40376
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} hooks file(s)`);
|
|
39586
40377
|
return writtenCount;
|
|
39587
40378
|
}
|
|
@@ -39611,6 +40402,12 @@ async function importPermissionsCore(params) {
|
|
|
39611
40402
|
}
|
|
39612
40403
|
const rulesyncFiles = await permissionsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39613
40404
|
const { count: writtenCount } = await permissionsProcessor.writeAiFiles(rulesyncFiles);
|
|
40405
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40406
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40407
|
+
jsonRelativePath: RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH,
|
|
40408
|
+
jsoncRelativePath: RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH,
|
|
40409
|
+
logger
|
|
40410
|
+
});
|
|
39614
40411
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} permissions file(s)`);
|
|
39615
40412
|
return writtenCount;
|
|
39616
40413
|
}
|
|
@@ -39675,6 +40472,12 @@ Object.defineProperty(exports, "CLIError", {
|
|
|
39675
40472
|
return CLIError;
|
|
39676
40473
|
}
|
|
39677
40474
|
});
|
|
40475
|
+
Object.defineProperty(exports, "CODEXCLI_DIR", {
|
|
40476
|
+
enumerable: true,
|
|
40477
|
+
get: function() {
|
|
40478
|
+
return CODEXCLI_DIR;
|
|
40479
|
+
}
|
|
40480
|
+
});
|
|
39678
40481
|
Object.defineProperty(exports, "CommandsProcessor", {
|
|
39679
40482
|
enumerable: true,
|
|
39680
40483
|
get: function() {
|
|
@@ -39771,6 +40574,12 @@ Object.defineProperty(exports, "RULESYNC_HOOKS_FILE_NAME", {
|
|
|
39771
40574
|
return RULESYNC_HOOKS_FILE_NAME;
|
|
39772
40575
|
}
|
|
39773
40576
|
});
|
|
40577
|
+
Object.defineProperty(exports, "RULESYNC_HOOKS_JSONC_FILE_NAME", {
|
|
40578
|
+
enumerable: true,
|
|
40579
|
+
get: function() {
|
|
40580
|
+
return RULESYNC_HOOKS_JSONC_FILE_NAME;
|
|
40581
|
+
}
|
|
40582
|
+
});
|
|
39774
40583
|
Object.defineProperty(exports, "RULESYNC_HOOKS_RELATIVE_FILE_PATH", {
|
|
39775
40584
|
enumerable: true,
|
|
39776
40585
|
get: function() {
|
|
@@ -39795,6 +40604,12 @@ Object.defineProperty(exports, "RULESYNC_MCP_FILE_NAME", {
|
|
|
39795
40604
|
return RULESYNC_MCP_FILE_NAME;
|
|
39796
40605
|
}
|
|
39797
40606
|
});
|
|
40607
|
+
Object.defineProperty(exports, "RULESYNC_MCP_JSONC_FILE_NAME", {
|
|
40608
|
+
enumerable: true,
|
|
40609
|
+
get: function() {
|
|
40610
|
+
return RULESYNC_MCP_JSONC_FILE_NAME;
|
|
40611
|
+
}
|
|
40612
|
+
});
|
|
39798
40613
|
Object.defineProperty(exports, "RULESYNC_MCP_RELATIVE_FILE_PATH", {
|
|
39799
40614
|
enumerable: true,
|
|
39800
40615
|
get: function() {
|
|
@@ -39819,12 +40634,24 @@ Object.defineProperty(exports, "RULESYNC_PERMISSIONS_FILE_NAME", {
|
|
|
39819
40634
|
return RULESYNC_PERMISSIONS_FILE_NAME;
|
|
39820
40635
|
}
|
|
39821
40636
|
});
|
|
40637
|
+
Object.defineProperty(exports, "RULESYNC_PERMISSIONS_JSONC_FILE_NAME", {
|
|
40638
|
+
enumerable: true,
|
|
40639
|
+
get: function() {
|
|
40640
|
+
return RULESYNC_PERMISSIONS_JSONC_FILE_NAME;
|
|
40641
|
+
}
|
|
40642
|
+
});
|
|
39822
40643
|
Object.defineProperty(exports, "RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH", {
|
|
39823
40644
|
enumerable: true,
|
|
39824
40645
|
get: function() {
|
|
39825
40646
|
return RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH;
|
|
39826
40647
|
}
|
|
39827
40648
|
});
|
|
40649
|
+
Object.defineProperty(exports, "RULESYNC_PERMISSIONS_SCHEMA_URL", {
|
|
40650
|
+
enumerable: true,
|
|
40651
|
+
get: function() {
|
|
40652
|
+
return RULESYNC_PERMISSIONS_SCHEMA_URL;
|
|
40653
|
+
}
|
|
40654
|
+
});
|
|
39828
40655
|
Object.defineProperty(exports, "RULESYNC_RELATIVE_DIR_PATH", {
|
|
39829
40656
|
enumerable: true,
|
|
39830
40657
|
get: function() {
|