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
|
@@ -99,6 +99,9 @@ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH,
|
|
|
99
99
|
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
100
100
|
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
101
101
|
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
102
|
+
const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
103
|
+
const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
104
|
+
const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
102
105
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
103
106
|
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
104
107
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
@@ -109,8 +112,12 @@ const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
|
109
112
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
110
113
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
111
114
|
const RULESYNC_PERMISSIONS_FILE_NAME = "permissions.json";
|
|
115
|
+
const RULESYNC_MCP_JSONC_FILE_NAME = "mcp.jsonc";
|
|
116
|
+
const RULESYNC_HOOKS_JSONC_FILE_NAME = "hooks.jsonc";
|
|
117
|
+
const RULESYNC_PERMISSIONS_JSONC_FILE_NAME = "permissions.jsonc";
|
|
112
118
|
const RULESYNC_CONFIG_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json";
|
|
113
119
|
const RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
|
|
120
|
+
const RULESYNC_PERMISSIONS_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/permissions-schema.json";
|
|
114
121
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
115
122
|
//#endregion
|
|
116
123
|
//#region src/types/tool-target-tuples.ts
|
|
@@ -322,7 +329,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
322
329
|
"junie",
|
|
323
330
|
"vibe",
|
|
324
331
|
"qwencode",
|
|
325
|
-
"reasonix"
|
|
332
|
+
"reasonix",
|
|
333
|
+
"grokcli"
|
|
326
334
|
];
|
|
327
335
|
const permissionsProcessorToolTargetTuple = [
|
|
328
336
|
"amp",
|
|
@@ -6269,10 +6277,13 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
6269
6277
|
* Hook events supported by Devin Local (native `.devin/` hooks).
|
|
6270
6278
|
*
|
|
6271
6279
|
* Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
|
|
6272
|
-
*
|
|
6273
|
-
* `UserPromptSubmit`, `Stop`, `SessionStart`,
|
|
6280
|
+
* eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
6281
|
+
* `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
|
|
6282
|
+
* `PostCompaction` (fires after context compaction; added in the Devin CLI
|
|
6283
|
+
* stable changelog — https://docs.devin.ai/cli/changelog/stable). The
|
|
6274
6284
|
* tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
|
|
6275
|
-
* a `matcher` (regex against `tool_name`); the session/turn events
|
|
6285
|
+
* a `matcher` (regex against `tool_name`); the session/turn/compaction events
|
|
6286
|
+
* do not.
|
|
6276
6287
|
*
|
|
6277
6288
|
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
6278
6289
|
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
@@ -6287,7 +6298,8 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
6287
6298
|
"postToolUse",
|
|
6288
6299
|
"beforeSubmitPrompt",
|
|
6289
6300
|
"stop",
|
|
6290
|
-
"permissionRequest"
|
|
6301
|
+
"permissionRequest",
|
|
6302
|
+
"postCompact"
|
|
6291
6303
|
];
|
|
6292
6304
|
/** Hook events supported by OpenCode. */
|
|
6293
6305
|
const OPENCODE_HOOK_EVENTS = [
|
|
@@ -6481,7 +6493,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
|
|
|
6481
6493
|
* Hook events supported by AugmentCode (Auggie CLI).
|
|
6482
6494
|
* Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
|
|
6483
6495
|
* PreToolUse / PostToolUse (tool events, matcher-aware) plus the
|
|
6484
|
-
* SessionStart / SessionEnd / Stop session events (no matcher).
|
|
6496
|
+
* SessionStart / SessionEnd / Stop / Notification session events (no matcher).
|
|
6485
6497
|
* @see https://docs.augmentcode.com/cli/hooks
|
|
6486
6498
|
*/
|
|
6487
6499
|
const AUGMENTCODE_HOOK_EVENTS = [
|
|
@@ -6489,7 +6501,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
|
|
|
6489
6501
|
"postToolUse",
|
|
6490
6502
|
"sessionStart",
|
|
6491
6503
|
"sessionEnd",
|
|
6492
|
-
"stop"
|
|
6504
|
+
"stop",
|
|
6505
|
+
"notification"
|
|
6493
6506
|
];
|
|
6494
6507
|
/**
|
|
6495
6508
|
* Hook events supported by Mistral Vibe (mistral-vibe).
|
|
@@ -6541,6 +6554,10 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
6541
6554
|
* The Qwen-specific events
|
|
6542
6555
|
* `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
|
|
6543
6556
|
* `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
|
|
6557
|
+
* Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
|
|
6558
|
+
* `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
|
|
6559
|
+
* `InstructionsLoaded`, which map to the canonical `postToolBatch`,
|
|
6560
|
+
* `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
|
|
6544
6561
|
* @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
|
|
6545
6562
|
*/
|
|
6546
6563
|
const QWENCODE_HOOK_EVENTS = [
|
|
@@ -6549,7 +6566,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6549
6566
|
"preToolUse",
|
|
6550
6567
|
"postToolUse",
|
|
6551
6568
|
"postToolUseFailure",
|
|
6569
|
+
"postToolBatch",
|
|
6552
6570
|
"beforeSubmitPrompt",
|
|
6571
|
+
"userPromptExpansion",
|
|
6553
6572
|
"stop",
|
|
6554
6573
|
"stopFailure",
|
|
6555
6574
|
"subagentStart",
|
|
@@ -6557,7 +6576,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6557
6576
|
"preCompact",
|
|
6558
6577
|
"postCompact",
|
|
6559
6578
|
"permissionRequest",
|
|
6579
|
+
"permissionDenied",
|
|
6560
6580
|
"notification",
|
|
6581
|
+
"instructionsLoaded",
|
|
6561
6582
|
"todoCreated",
|
|
6562
6583
|
"todoCompleted"
|
|
6563
6584
|
];
|
|
@@ -6567,11 +6588,11 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6567
6588
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
6568
6589
|
* (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
|
|
6569
6590
|
* `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
|
|
6570
|
-
* `SubagentStop`, `Notification`, `PreCompact`).
|
|
6571
|
-
*
|
|
6591
|
+
* `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
|
|
6592
|
+
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
6572
6593
|
* `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
|
|
6573
|
-
* `SessionEnd`, `SubagentStop`,
|
|
6574
|
-
*
|
|
6594
|
+
* `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
|
|
6595
|
+
* `Notification` ← `notification`, and `PreCompact` ← `preCompact`.
|
|
6575
6596
|
* `match` (Reasonix's matcher field name) is honored only on
|
|
6576
6597
|
* `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
|
|
6577
6598
|
* tool-event scoping used by other adapters.
|
|
@@ -6585,7 +6606,39 @@ const REASONIX_HOOK_EVENTS = [
|
|
|
6585
6606
|
"sessionStart",
|
|
6586
6607
|
"sessionEnd",
|
|
6587
6608
|
"subagentStop",
|
|
6588
|
-
"postModelInvocation"
|
|
6609
|
+
"postModelInvocation",
|
|
6610
|
+
"notification",
|
|
6611
|
+
"preCompact"
|
|
6612
|
+
];
|
|
6613
|
+
/**
|
|
6614
|
+
* Hook events supported by Grok CLI (xAI Grok Build).
|
|
6615
|
+
*
|
|
6616
|
+
* Grok Build documents a Claude-Code-compatible hooks surface with fourteen
|
|
6617
|
+
* PascalCase events, all of which map 1:1 onto an existing canonical arm:
|
|
6618
|
+
* `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
|
|
6619
|
+
* `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
|
|
6620
|
+
* `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
|
|
6621
|
+
* `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
|
|
6622
|
+
* name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
|
|
6623
|
+
* `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
|
|
6624
|
+
* the remaining lifecycle events are matcher-less.
|
|
6625
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
6626
|
+
*/
|
|
6627
|
+
const GROKCLI_HOOK_EVENTS = [
|
|
6628
|
+
"sessionStart",
|
|
6629
|
+
"sessionEnd",
|
|
6630
|
+
"beforeSubmitPrompt",
|
|
6631
|
+
"preToolUse",
|
|
6632
|
+
"postToolUse",
|
|
6633
|
+
"postToolUseFailure",
|
|
6634
|
+
"permissionDenied",
|
|
6635
|
+
"stop",
|
|
6636
|
+
"stopFailure",
|
|
6637
|
+
"notification",
|
|
6638
|
+
"subagentStart",
|
|
6639
|
+
"subagentStop",
|
|
6640
|
+
"preCompact",
|
|
6641
|
+
"postCompact"
|
|
6589
6642
|
];
|
|
6590
6643
|
/**
|
|
6591
6644
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
@@ -6659,6 +6712,7 @@ const HooksConfigSchema = z.looseObject({
|
|
|
6659
6712
|
junie: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6660
6713
|
vibe: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6661
6714
|
reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6715
|
+
grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6662
6716
|
qwencode: z.optional(z.looseObject({
|
|
6663
6717
|
hooks: z.optional(hooksRecordSchema),
|
|
6664
6718
|
disableAllHooks: z.optional(z.boolean())
|
|
@@ -6717,7 +6771,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
|
6717
6771
|
postToolUse: "PostToolUse",
|
|
6718
6772
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6719
6773
|
stop: "Stop",
|
|
6720
|
-
permissionRequest: "PermissionRequest"
|
|
6774
|
+
permissionRequest: "PermissionRequest",
|
|
6775
|
+
postCompact: "PostCompaction"
|
|
6721
6776
|
};
|
|
6722
6777
|
/**
|
|
6723
6778
|
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
@@ -6732,7 +6787,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6732
6787
|
postToolUse: "PostToolUse",
|
|
6733
6788
|
sessionStart: "SessionStart",
|
|
6734
6789
|
sessionEnd: "SessionEnd",
|
|
6735
|
-
stop: "Stop"
|
|
6790
|
+
stop: "Stop",
|
|
6791
|
+
notification: "Notification"
|
|
6736
6792
|
};
|
|
6737
6793
|
/**
|
|
6738
6794
|
* Map AugmentCode PascalCase event names to canonical camelCase.
|
|
@@ -7002,7 +7058,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
7002
7058
|
preToolUse: "PreToolUse",
|
|
7003
7059
|
postToolUse: "PostToolUse",
|
|
7004
7060
|
postToolUseFailure: "PostToolUseFailure",
|
|
7061
|
+
postToolBatch: "PostToolBatch",
|
|
7005
7062
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7063
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
7006
7064
|
stop: "Stop",
|
|
7007
7065
|
subagentStart: "SubagentStart",
|
|
7008
7066
|
subagentStop: "SubagentStop",
|
|
@@ -7010,7 +7068,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
7010
7068
|
preCompact: "PreCompact",
|
|
7011
7069
|
postCompact: "PostCompact",
|
|
7012
7070
|
permissionRequest: "PermissionRequest",
|
|
7071
|
+
permissionDenied: "PermissionDenied",
|
|
7013
7072
|
notification: "Notification",
|
|
7073
|
+
instructionsLoaded: "InstructionsLoaded",
|
|
7014
7074
|
todoCreated: "TodoCreated",
|
|
7015
7075
|
todoCompleted: "TodoCompleted"
|
|
7016
7076
|
};
|
|
@@ -7032,12 +7092,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
7032
7092
|
sessionStart: "SessionStart",
|
|
7033
7093
|
sessionEnd: "SessionEnd",
|
|
7034
7094
|
subagentStop: "SubagentStop",
|
|
7035
|
-
postModelInvocation: "PostLLMCall"
|
|
7095
|
+
postModelInvocation: "PostLLMCall",
|
|
7096
|
+
notification: "Notification",
|
|
7097
|
+
preCompact: "PreCompact"
|
|
7036
7098
|
};
|
|
7037
7099
|
/**
|
|
7038
7100
|
* Map Reasonix PascalCase event names to canonical camelCase.
|
|
7039
7101
|
*/
|
|
7040
7102
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7103
|
+
/**
|
|
7104
|
+
* Map canonical camelCase event names to Grok CLI PascalCase.
|
|
7105
|
+
* Grok Build reuses the same Claude-style PascalCase event names, so each
|
|
7106
|
+
* canonical arm maps to its PascalCase equivalent.
|
|
7107
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
7108
|
+
*/
|
|
7109
|
+
const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
7110
|
+
sessionStart: "SessionStart",
|
|
7111
|
+
sessionEnd: "SessionEnd",
|
|
7112
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7113
|
+
preToolUse: "PreToolUse",
|
|
7114
|
+
postToolUse: "PostToolUse",
|
|
7115
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7116
|
+
permissionDenied: "PermissionDenied",
|
|
7117
|
+
stop: "Stop",
|
|
7118
|
+
stopFailure: "StopFailure",
|
|
7119
|
+
notification: "Notification",
|
|
7120
|
+
subagentStart: "SubagentStart",
|
|
7121
|
+
subagentStop: "SubagentStop",
|
|
7122
|
+
preCompact: "PreCompact",
|
|
7123
|
+
postCompact: "PostCompact"
|
|
7124
|
+
};
|
|
7125
|
+
/**
|
|
7126
|
+
* Map Grok CLI PascalCase event names to canonical camelCase.
|
|
7127
|
+
*/
|
|
7128
|
+
const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7041
7129
|
//#endregion
|
|
7042
7130
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
7043
7131
|
function isToolMatcherEntry(x) {
|
|
@@ -7236,12 +7324,55 @@ function toolHooksToCanonical({ hooks, converterConfig }) {
|
|
|
7236
7324
|
return canonical;
|
|
7237
7325
|
}
|
|
7238
7326
|
//#endregion
|
|
7327
|
+
//#region src/utils/jsonc.ts
|
|
7328
|
+
/**
|
|
7329
|
+
* Rebuild the parsed value from its own enumerable entries, dropping
|
|
7330
|
+
* prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
|
|
7331
|
+
* `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
|
|
7332
|
+
* literal `__proto__` key would replace the containing object's prototype
|
|
7333
|
+
* instead of becoming an own property; rebuilding gives every object a clean
|
|
7334
|
+
* `Object.prototype` again and severs that path.
|
|
7335
|
+
*/
|
|
7336
|
+
function deepSanitize(value) {
|
|
7337
|
+
if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
|
|
7338
|
+
if (value !== null && typeof value === "object") {
|
|
7339
|
+
const sanitized = {};
|
|
7340
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
7341
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
7342
|
+
sanitized[key] = deepSanitize(entry);
|
|
7343
|
+
}
|
|
7344
|
+
return sanitized;
|
|
7345
|
+
}
|
|
7346
|
+
return value;
|
|
7347
|
+
}
|
|
7348
|
+
/**
|
|
7349
|
+
* Parse a JSONC (JSON with Comments) document strictly.
|
|
7350
|
+
*
|
|
7351
|
+
* Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
|
|
7352
|
+
* errors and returns a best-effort value), this throws on any parse error so
|
|
7353
|
+
* a malformed source file fails loudly instead of generating half-empty tool
|
|
7354
|
+
* configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
|
|
7355
|
+
* `JSON.parse` on files that may contain comments or trailing commas.
|
|
7356
|
+
*/
|
|
7357
|
+
function parseJsonc(content) {
|
|
7358
|
+
const errors = [];
|
|
7359
|
+
const result = parse(content, errors, {
|
|
7360
|
+
allowTrailingComma: true,
|
|
7361
|
+
disallowComments: false
|
|
7362
|
+
});
|
|
7363
|
+
if (errors.length > 0) {
|
|
7364
|
+
const details = errors.map((e) => `${printParseErrorCode(e.error)} at offset ${e.offset}`).join(", ");
|
|
7365
|
+
throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
|
|
7366
|
+
}
|
|
7367
|
+
return deepSanitize(result);
|
|
7368
|
+
}
|
|
7369
|
+
//#endregion
|
|
7239
7370
|
//#region src/features/hooks/rulesync-hooks.ts
|
|
7240
7371
|
var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
7241
7372
|
json;
|
|
7242
7373
|
constructor(params) {
|
|
7243
7374
|
super({ ...params });
|
|
7244
|
-
this.json =
|
|
7375
|
+
this.json = parseJsonc(this.fileContent);
|
|
7245
7376
|
if (params.validate) {
|
|
7246
7377
|
const result = this.validate();
|
|
7247
7378
|
if (!result.success) throw result.error;
|
|
@@ -7250,7 +7381,11 @@ var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
|
7250
7381
|
static getSettablePaths() {
|
|
7251
7382
|
return {
|
|
7252
7383
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
7253
|
-
relativeFilePath:
|
|
7384
|
+
relativeFilePath: RULESYNC_HOOKS_FILE_NAME,
|
|
7385
|
+
jsonc: {
|
|
7386
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
7387
|
+
relativeFilePath: RULESYNC_HOOKS_JSONC_FILE_NAME
|
|
7388
|
+
}
|
|
7254
7389
|
};
|
|
7255
7390
|
}
|
|
7256
7391
|
validate() {
|
|
@@ -7266,16 +7401,23 @@ var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
|
7266
7401
|
}
|
|
7267
7402
|
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
7268
7403
|
const paths = RulesyncHooks.getSettablePaths();
|
|
7269
|
-
const
|
|
7270
|
-
if (!await fileExists(filePath)) throw new Error(`No ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} found.`);
|
|
7271
|
-
const fileContent = await readFileContent(filePath);
|
|
7272
|
-
return new RulesyncHooks({
|
|
7273
|
-
outputRoot,
|
|
7404
|
+
const candidates = [paths.jsonc, {
|
|
7274
7405
|
relativeDirPath: paths.relativeDirPath,
|
|
7275
|
-
relativeFilePath: paths.relativeFilePath
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7406
|
+
relativeFilePath: paths.relativeFilePath
|
|
7407
|
+
}];
|
|
7408
|
+
for (const candidate of candidates) {
|
|
7409
|
+
const filePath = join(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath);
|
|
7410
|
+
if (!await fileExists(filePath)) continue;
|
|
7411
|
+
const fileContent = await readFileContent(filePath);
|
|
7412
|
+
return new RulesyncHooks({
|
|
7413
|
+
outputRoot,
|
|
7414
|
+
relativeDirPath: candidate.relativeDirPath,
|
|
7415
|
+
relativeFilePath: candidate.relativeFilePath,
|
|
7416
|
+
fileContent,
|
|
7417
|
+
validate
|
|
7418
|
+
});
|
|
7419
|
+
}
|
|
7420
|
+
throw new Error(`No ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} or ${RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH} found.`);
|
|
7279
7421
|
}
|
|
7280
7422
|
getJson() {
|
|
7281
7423
|
return this.json;
|
|
@@ -7562,7 +7704,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
|
|
|
7562
7704
|
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
7563
7705
|
"sessionStart",
|
|
7564
7706
|
"sessionEnd",
|
|
7565
|
-
"stop"
|
|
7707
|
+
"stop",
|
|
7708
|
+
"notification"
|
|
7566
7709
|
])
|
|
7567
7710
|
};
|
|
7568
7711
|
/**
|
|
@@ -8661,7 +8804,8 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
8661
8804
|
"sessionStart",
|
|
8662
8805
|
"sessionEnd",
|
|
8663
8806
|
"stop",
|
|
8664
|
-
"beforeSubmitPrompt"
|
|
8807
|
+
"beforeSubmitPrompt",
|
|
8808
|
+
"postCompact"
|
|
8665
8809
|
])
|
|
8666
8810
|
};
|
|
8667
8811
|
/**
|
|
@@ -8970,6 +9114,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8970
9114
|
}
|
|
8971
9115
|
};
|
|
8972
9116
|
//#endregion
|
|
9117
|
+
//#region src/constants/grokcli-paths.ts
|
|
9118
|
+
/**
|
|
9119
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9120
|
+
*
|
|
9121
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9122
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9123
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9124
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9125
|
+
*
|
|
9126
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9127
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9128
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9129
|
+
* @see https://docs.x.ai/build/overview
|
|
9130
|
+
*/
|
|
9131
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9132
|
+
const GROKCLI_DIR = ".grok";
|
|
9133
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9134
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9135
|
+
/**
|
|
9136
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9137
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9138
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9139
|
+
*/
|
|
9140
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9141
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9142
|
+
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
9143
|
+
/**
|
|
9144
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9145
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9146
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9147
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9148
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9149
|
+
*/
|
|
9150
|
+
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
9151
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9152
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9153
|
+
/**
|
|
9154
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9155
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9156
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9157
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9158
|
+
*/
|
|
9159
|
+
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
9160
|
+
/**
|
|
9161
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9162
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9163
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9164
|
+
* MCP/skills/subagents adapters).
|
|
9165
|
+
*/
|
|
9166
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9167
|
+
//#endregion
|
|
9168
|
+
//#region src/features/hooks/grokcli-hooks.ts
|
|
9169
|
+
const GROKCLI_CONVERTER_CONFIG = {
|
|
9170
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
9171
|
+
canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
|
|
9172
|
+
toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
|
|
9173
|
+
projectDirVar: "",
|
|
9174
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
9175
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
9176
|
+
"sessionStart",
|
|
9177
|
+
"sessionEnd",
|
|
9178
|
+
"beforeSubmitPrompt",
|
|
9179
|
+
"stop",
|
|
9180
|
+
"stopFailure",
|
|
9181
|
+
"notification",
|
|
9182
|
+
"subagentStart",
|
|
9183
|
+
"subagentStop",
|
|
9184
|
+
"preCompact",
|
|
9185
|
+
"postCompact"
|
|
9186
|
+
])
|
|
9187
|
+
};
|
|
9188
|
+
/**
|
|
9189
|
+
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
9190
|
+
*
|
|
9191
|
+
* Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
|
|
9192
|
+
* maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
|
|
9193
|
+
* matcher groups under a top-level `hooks` key. rulesync writes all its hooks
|
|
9194
|
+
* into a single standalone `rulesync.json` file discovered from
|
|
9195
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
|
|
9196
|
+
* is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
|
|
9197
|
+
* as an orphan.
|
|
9198
|
+
*
|
|
9199
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9200
|
+
*/
|
|
9201
|
+
var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
9202
|
+
constructor(params) {
|
|
9203
|
+
super({
|
|
9204
|
+
...params,
|
|
9205
|
+
fileContent: params.fileContent ?? "{}"
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
static getSettablePaths(_options = {}) {
|
|
9209
|
+
return {
|
|
9210
|
+
relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
|
|
9211
|
+
relativeFilePath: GROKCLI_HOOKS_FILE_NAME
|
|
9212
|
+
};
|
|
9213
|
+
}
|
|
9214
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9215
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9216
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
9217
|
+
return new GrokcliHooks({
|
|
9218
|
+
outputRoot,
|
|
9219
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9220
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9221
|
+
fileContent,
|
|
9222
|
+
validate
|
|
9223
|
+
});
|
|
9224
|
+
}
|
|
9225
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
9226
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9227
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9228
|
+
const config = rulesyncHooks.getJson();
|
|
9229
|
+
const grokHooks = canonicalToToolHooks({
|
|
9230
|
+
config,
|
|
9231
|
+
toolOverrideHooks: config.grokcli?.hooks,
|
|
9232
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
9233
|
+
logger
|
|
9234
|
+
});
|
|
9235
|
+
await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
|
|
9236
|
+
const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
|
|
9237
|
+
return new GrokcliHooks({
|
|
9238
|
+
outputRoot,
|
|
9239
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9240
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9241
|
+
fileContent,
|
|
9242
|
+
validate
|
|
9243
|
+
});
|
|
9244
|
+
}
|
|
9245
|
+
toRulesyncHooks() {
|
|
9246
|
+
let parsed;
|
|
9247
|
+
try {
|
|
9248
|
+
parsed = JSON.parse(this.getFileContent());
|
|
9249
|
+
} catch (error) {
|
|
9250
|
+
throw new Error(`Failed to parse Grok hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9251
|
+
}
|
|
9252
|
+
const hooks = toolHooksToCanonical({
|
|
9253
|
+
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
9254
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
9255
|
+
});
|
|
9256
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9257
|
+
version: 1,
|
|
9258
|
+
hooks
|
|
9259
|
+
}, null, 2) });
|
|
9260
|
+
}
|
|
9261
|
+
validate() {
|
|
9262
|
+
return {
|
|
9263
|
+
success: true,
|
|
9264
|
+
error: null
|
|
9265
|
+
};
|
|
9266
|
+
}
|
|
9267
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9268
|
+
return new GrokcliHooks({
|
|
9269
|
+
outputRoot,
|
|
9270
|
+
relativeDirPath,
|
|
9271
|
+
relativeFilePath,
|
|
9272
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
9273
|
+
validate: false
|
|
9274
|
+
});
|
|
9275
|
+
}
|
|
9276
|
+
};
|
|
9277
|
+
//#endregion
|
|
8973
9278
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8974
9279
|
/**
|
|
8975
9280
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -10176,9 +10481,9 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
10176
10481
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
10177
10482
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
10178
10483
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
10179
|
-
*
|
|
10484
|
+
* All ten upstream events have a clean canonical equivalent and are mapped:
|
|
10180
10485
|
* PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
|
|
10181
|
-
* SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
|
|
10486
|
+
* SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
10182
10487
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
10183
10488
|
*/
|
|
10184
10489
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -10766,6 +11071,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
10766
11071
|
supportedEvents: REASONIX_HOOK_EVENTS,
|
|
10767
11072
|
supportedHookTypes: ["command"],
|
|
10768
11073
|
supportsMatcher: true
|
|
11074
|
+
}],
|
|
11075
|
+
["grokcli", {
|
|
11076
|
+
class: GrokcliHooks,
|
|
11077
|
+
meta: {
|
|
11078
|
+
supportsProject: true,
|
|
11079
|
+
supportsGlobal: true,
|
|
11080
|
+
supportsImport: true
|
|
11081
|
+
},
|
|
11082
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
11083
|
+
supportedHookTypes: ["command"],
|
|
11084
|
+
supportsMatcher: true
|
|
10769
11085
|
}]
|
|
10770
11086
|
]);
|
|
10771
11087
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -12109,15 +12425,51 @@ const RulesyncMcpServerSchema = z.extend(McpServerSchema, {
|
|
|
12109
12425
|
exposed: z.optional(z.boolean())
|
|
12110
12426
|
});
|
|
12111
12427
|
const RulesyncMcpConfigSchema = z.object({ mcpServers: z.record(z.string(), RulesyncMcpServerSchema) });
|
|
12428
|
+
/**
|
|
12429
|
+
* Tool-scoped MCP block: servers that apply only to one tool. A named entry
|
|
12430
|
+
* replaces/adds the same-named shared server wholesale for that tool; `null`
|
|
12431
|
+
* removes the shared server for that tool. Mirrors `{toolname}.hooks` in
|
|
12432
|
+
* `.rulesync/hooks.json` and `{toolname}.permission` in
|
|
12433
|
+
* `.rulesync/permissions.json`.
|
|
12434
|
+
*/
|
|
12435
|
+
const toolScopedMcpSchema = z.looseObject({ mcpServers: z.optional(z.record(z.string(), z.nullable(RulesyncMcpServerSchema))) });
|
|
12112
12436
|
const RulesyncMcpFileSchema = z.looseObject({
|
|
12113
12437
|
$schema: z.optional(z.string()),
|
|
12114
|
-
...RulesyncMcpConfigSchema.shape
|
|
12438
|
+
...RulesyncMcpConfigSchema.shape,
|
|
12439
|
+
amp: z.optional(toolScopedMcpSchema),
|
|
12440
|
+
"antigravity-cli": z.optional(toolScopedMcpSchema),
|
|
12441
|
+
"antigravity-ide": z.optional(toolScopedMcpSchema),
|
|
12442
|
+
augmentcode: z.optional(toolScopedMcpSchema),
|
|
12443
|
+
claudecode: z.optional(toolScopedMcpSchema),
|
|
12444
|
+
cline: z.optional(toolScopedMcpSchema),
|
|
12445
|
+
codexcli: z.optional(toolScopedMcpSchema),
|
|
12446
|
+
copilot: z.optional(toolScopedMcpSchema),
|
|
12447
|
+
copilotcli: z.optional(toolScopedMcpSchema),
|
|
12448
|
+
cursor: z.optional(toolScopedMcpSchema),
|
|
12449
|
+
deepagents: z.optional(toolScopedMcpSchema),
|
|
12450
|
+
devin: z.optional(toolScopedMcpSchema),
|
|
12451
|
+
factorydroid: z.optional(toolScopedMcpSchema),
|
|
12452
|
+
goose: z.optional(toolScopedMcpSchema),
|
|
12453
|
+
grokcli: z.optional(toolScopedMcpSchema),
|
|
12454
|
+
hermesagent: z.optional(toolScopedMcpSchema),
|
|
12455
|
+
junie: z.optional(toolScopedMcpSchema),
|
|
12456
|
+
kilo: z.optional(toolScopedMcpSchema),
|
|
12457
|
+
kiro: z.optional(toolScopedMcpSchema),
|
|
12458
|
+
opencode: z.optional(toolScopedMcpSchema),
|
|
12459
|
+
qwencode: z.optional(toolScopedMcpSchema),
|
|
12460
|
+
reasonix: z.optional(toolScopedMcpSchema),
|
|
12461
|
+
roo: z.optional(toolScopedMcpSchema),
|
|
12462
|
+
rovodev: z.optional(toolScopedMcpSchema),
|
|
12463
|
+
takt: z.optional(toolScopedMcpSchema),
|
|
12464
|
+
vibe: z.optional(toolScopedMcpSchema),
|
|
12465
|
+
warp: z.optional(toolScopedMcpSchema),
|
|
12466
|
+
zed: z.optional(toolScopedMcpSchema)
|
|
12115
12467
|
});
|
|
12116
12468
|
var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
12117
12469
|
json;
|
|
12118
12470
|
constructor(params) {
|
|
12119
12471
|
super(params);
|
|
12120
|
-
this.json =
|
|
12472
|
+
this.json = parseJsonc(this.fileContent);
|
|
12121
12473
|
if (params.validate) {
|
|
12122
12474
|
const result = this.validate();
|
|
12123
12475
|
if (!result.success) throw result.error;
|
|
@@ -12127,7 +12479,11 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12127
12479
|
return {
|
|
12128
12480
|
recommended: {
|
|
12129
12481
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
12130
|
-
relativeFilePath:
|
|
12482
|
+
relativeFilePath: RULESYNC_MCP_FILE_NAME
|
|
12483
|
+
},
|
|
12484
|
+
jsonc: {
|
|
12485
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
12486
|
+
relativeFilePath: RULESYNC_MCP_JSONC_FILE_NAME
|
|
12131
12487
|
},
|
|
12132
12488
|
legacy: {
|
|
12133
12489
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
@@ -12149,7 +12505,18 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12149
12505
|
static async fromFile({ outputRoot = process.cwd(), validate = true, logger }) {
|
|
12150
12506
|
const paths = this.getSettablePaths();
|
|
12151
12507
|
const recommendedPath = join(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
|
|
12508
|
+
const jsoncPath = join(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
|
|
12152
12509
|
const legacyPath = join(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
|
|
12510
|
+
if (await fileExists(jsoncPath)) {
|
|
12511
|
+
const fileContent = await readFileContent(jsoncPath);
|
|
12512
|
+
return new RulesyncMcp({
|
|
12513
|
+
outputRoot,
|
|
12514
|
+
relativeDirPath: paths.jsonc.relativeDirPath,
|
|
12515
|
+
relativeFilePath: paths.jsonc.relativeFilePath,
|
|
12516
|
+
fileContent,
|
|
12517
|
+
validate
|
|
12518
|
+
});
|
|
12519
|
+
}
|
|
12153
12520
|
if (await fileExists(recommendedPath)) {
|
|
12154
12521
|
const fileContent = await readFileContent(recommendedPath);
|
|
12155
12522
|
return new RulesyncMcp({
|
|
@@ -12209,7 +12576,141 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
12209
12576
|
getJson() {
|
|
12210
12577
|
return this.json;
|
|
12211
12578
|
}
|
|
12212
|
-
|
|
12579
|
+
/**
|
|
12580
|
+
* Build the effective MCP config for one tool target:
|
|
12581
|
+
*
|
|
12582
|
+
* 1. Filter shared servers by the DEPRECATED per-server `targets` field
|
|
12583
|
+
* (missing/`["*"]` means every tool). A deprecation warning points at
|
|
12584
|
+
* the tool-scoped blocks that replace it.
|
|
12585
|
+
* 2. Overlay the tool-scoped `{toolname}.mcpServers` block(s): a named
|
|
12586
|
+
* entry replaces/adds the shared server wholesale for this tool; `null`
|
|
12587
|
+
* removes it.
|
|
12588
|
+
*
|
|
12589
|
+
* Targets that share one output file resolve identically so the shared
|
|
12590
|
+
* file's content never depends on which of them generates last — see
|
|
12591
|
+
* `resolveMcpTarget` for the alias groups (kiro trio, claudecode/-legacy,
|
|
12592
|
+
* and the Antigravity pair).
|
|
12593
|
+
*
|
|
12594
|
+
* Returns the same instance when neither mechanism is used.
|
|
12595
|
+
*/
|
|
12596
|
+
forTarget({ toolTarget, logger }) {
|
|
12597
|
+
const { blockKeys, acceptedTargetNames } = resolveMcpTarget({ toolTarget });
|
|
12598
|
+
const json = this.json;
|
|
12599
|
+
const sharedServers = this.json.mcpServers ?? {};
|
|
12600
|
+
const serverNamesWithTargets = Object.entries(sharedServers).filter(([, serverConfig]) => serverConfig.targets !== void 0).map(([serverName]) => serverName);
|
|
12601
|
+
if (serverNamesWithTargets.length > 0) this.warnTargetsDeprecationOnce({
|
|
12602
|
+
serverNamesWithTargets,
|
|
12603
|
+
logger
|
|
12604
|
+
});
|
|
12605
|
+
for (const ignoredKey of MCP_IGNORED_ALIAS_SOURCE_KEYS) {
|
|
12606
|
+
if (!isRecord(json[ignoredKey])) continue;
|
|
12607
|
+
this.warnOncePerFile(`alias:${ignoredKey}`, `The "${ignoredKey}" block in ${join(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${MCP_BLOCK_KEY_ALIASES[ignoredKey]}" key instead.`, logger);
|
|
12608
|
+
}
|
|
12609
|
+
const toolBlockKeys = Object.keys(json).filter((key) => MCP_TOOL_BLOCK_KEYS.has(key));
|
|
12610
|
+
if (serverNamesWithTargets.length === 0 && toolBlockKeys.length === 0) return this;
|
|
12611
|
+
const effectiveServers = Object.fromEntries(Object.entries(sharedServers).filter(([, serverConfig]) => {
|
|
12612
|
+
const targets = serverConfig.targets;
|
|
12613
|
+
if (targets === void 0) return true;
|
|
12614
|
+
return targets.some((target) => target === "*" || acceptedTargetNames.has(target));
|
|
12615
|
+
}));
|
|
12616
|
+
for (const blockKey of blockKeys) {
|
|
12617
|
+
const toolBlock = json[blockKey];
|
|
12618
|
+
const toolServers = isRecord(toolBlock) && isRecord(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
12619
|
+
for (const [serverName, serverConfig] of Object.entries(toolServers ?? {})) {
|
|
12620
|
+
if (isPrototypePollutionKey(serverName)) continue;
|
|
12621
|
+
if (serverConfig === null) delete effectiveServers[serverName];
|
|
12622
|
+
else effectiveServers[serverName] = serverConfig;
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
const rest = Object.fromEntries(Object.entries(json).filter(([key]) => !MCP_TOOL_BLOCK_KEYS.has(key)));
|
|
12626
|
+
return new RulesyncMcp({
|
|
12627
|
+
outputRoot: this.outputRoot,
|
|
12628
|
+
relativeDirPath: this.relativeDirPath,
|
|
12629
|
+
relativeFilePath: this.relativeFilePath,
|
|
12630
|
+
fileContent: JSON.stringify({
|
|
12631
|
+
...rest,
|
|
12632
|
+
mcpServers: effectiveServers
|
|
12633
|
+
}, null, 2)
|
|
12634
|
+
});
|
|
12635
|
+
}
|
|
12636
|
+
/**
|
|
12637
|
+
* The deprecation warning would otherwise repeat once per generated tool
|
|
12638
|
+
* target (a full `--targets "*"` run creates one RulesyncMcp per target),
|
|
12639
|
+
* so it is deduplicated per source file path.
|
|
12640
|
+
*/
|
|
12641
|
+
warnTargetsDeprecationOnce({ serverNamesWithTargets, logger }) {
|
|
12642
|
+
this.warnOncePerFile("targets-deprecation", `The per-server "targets" field in ${join(this.relativeDirPath, this.relativeFilePath)} is deprecated (servers: ${serverNamesWithTargets.join(", ")}). Author tool-scoped "{toolname}.mcpServers" blocks instead.`, logger);
|
|
12643
|
+
}
|
|
12644
|
+
warnOncePerFile(kind, message, logger) {
|
|
12645
|
+
const dedupeKey = `${this.getFilePath()}#${kind}`;
|
|
12646
|
+
if (warnedOnceKeys.has(dedupeKey)) return;
|
|
12647
|
+
warnedOnceKeys.add(dedupeKey);
|
|
12648
|
+
logger?.warn(message);
|
|
12649
|
+
}
|
|
12650
|
+
};
|
|
12651
|
+
/**
|
|
12652
|
+
* All keys that may hold a tool-scoped `{toolname}.mcpServers` block. Derived
|
|
12653
|
+
* from the MCP processor's target tuple so a newly added MCP-capable tool is
|
|
12654
|
+
* covered automatically.
|
|
12655
|
+
*/
|
|
12656
|
+
const MCP_TOOL_BLOCK_KEYS = new Set(mcpProcessorToolTargetTuple);
|
|
12657
|
+
/**
|
|
12658
|
+
* Targets whose `{toolname}.mcpServers` block key is ALWAYS another target's
|
|
12659
|
+
* key (at every scope): `claudecode-legacy` is a deprecated alias of
|
|
12660
|
+
* `claudecode`, and the Kiro IDE/CLI targets share the `kiro` block because
|
|
12661
|
+
* all three write the same `.kiro/settings/mcp.json` at both scopes. Blocks
|
|
12662
|
+
* authored under these source names are never read (a warning is emitted).
|
|
12663
|
+
*/
|
|
12664
|
+
const MCP_BLOCK_KEY_ALIASES = {
|
|
12665
|
+
"claudecode-legacy": "claudecode",
|
|
12666
|
+
"kiro-cli": "kiro",
|
|
12667
|
+
"kiro-ide": "kiro"
|
|
12668
|
+
};
|
|
12669
|
+
const MCP_IGNORED_ALIAS_SOURCE_KEYS = Object.keys(MCP_BLOCK_KEY_ALIASES);
|
|
12670
|
+
/**
|
|
12671
|
+
* Resolve which tool-scoped blocks a target reads and which deprecated
|
|
12672
|
+
* `targets` names match it. Targets that share one output file must resolve
|
|
12673
|
+
* identically, otherwise the shared file's content would depend on which
|
|
12674
|
+
* target happened to generate last:
|
|
12675
|
+
*
|
|
12676
|
+
* - `claudecode-legacy` aliases `claudecode`; the Kiro trio shares `kiro`
|
|
12677
|
+
* (same output file at both scopes).
|
|
12678
|
+
* - `antigravity-ide` / `antigravity-cli` share their output file at BOTH
|
|
12679
|
+
* scopes — `.agents/mcp_config.json` (project) and
|
|
12680
|
+
* `~/.gemini/config/mcp_config.json` (global; both global subdirs are
|
|
12681
|
+
* `config`) — so both targets always apply both blocks in a fixed order
|
|
12682
|
+
* (`antigravity-ide` first, `antigravity-cli` second — the CLI block wins
|
|
12683
|
+
* per server on conflict).
|
|
12684
|
+
*/
|
|
12685
|
+
function resolveMcpTarget({ toolTarget }) {
|
|
12686
|
+
if (toolTarget === "claudecode" || toolTarget === "claudecode-legacy") return {
|
|
12687
|
+
blockKeys: ["claudecode"],
|
|
12688
|
+
acceptedTargetNames: /* @__PURE__ */ new Set(["claudecode", "claudecode-legacy"])
|
|
12689
|
+
};
|
|
12690
|
+
if (toolTarget === "kiro" || toolTarget === "kiro-cli" || toolTarget === "kiro-ide") return {
|
|
12691
|
+
blockKeys: ["kiro"],
|
|
12692
|
+
acceptedTargetNames: /* @__PURE__ */ new Set([
|
|
12693
|
+
"kiro",
|
|
12694
|
+
"kiro-cli",
|
|
12695
|
+
"kiro-ide"
|
|
12696
|
+
])
|
|
12697
|
+
};
|
|
12698
|
+
if (toolTarget === "antigravity-ide" || toolTarget === "antigravity-cli") return {
|
|
12699
|
+
blockKeys: ["antigravity-ide", "antigravity-cli"],
|
|
12700
|
+
acceptedTargetNames: /* @__PURE__ */ new Set(["antigravity-ide", "antigravity-cli"])
|
|
12701
|
+
};
|
|
12702
|
+
return {
|
|
12703
|
+
blockKeys: [toolTarget],
|
|
12704
|
+
acceptedTargetNames: /* @__PURE__ */ new Set([toolTarget])
|
|
12705
|
+
};
|
|
12706
|
+
}
|
|
12707
|
+
/**
|
|
12708
|
+
* Deduplication set for once-per-source-file warnings, keyed by
|
|
12709
|
+
* `<absolute file path>#<warning kind>`. Never cleared: rulesync CLI runs
|
|
12710
|
+
* are one-shot processes, and in a long-lived embedding (the rulesync MCP
|
|
12711
|
+
* server) repeating the same warning per generate would only add noise.
|
|
12712
|
+
*/
|
|
12713
|
+
const warnedOnceKeys = /* @__PURE__ */ new Set();
|
|
12213
12714
|
//#endregion
|
|
12214
12715
|
//#region src/features/mcp/tool-mcp.ts
|
|
12215
12716
|
var ToolMcp = class extends ToolFile {
|
|
@@ -13021,7 +13522,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
13021
13522
|
...rest,
|
|
13022
13523
|
validate: false
|
|
13023
13524
|
});
|
|
13024
|
-
|
|
13525
|
+
let toml;
|
|
13526
|
+
try {
|
|
13527
|
+
toml = smolToml.parse(this.fileContent);
|
|
13528
|
+
} catch (error) {
|
|
13529
|
+
throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
|
|
13530
|
+
}
|
|
13531
|
+
this.toml = toml;
|
|
13025
13532
|
if (rest.validate) {
|
|
13026
13533
|
const result = this.validate();
|
|
13027
13534
|
if (!result.success) throw result.error;
|
|
@@ -13057,7 +13564,12 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
13057
13564
|
const paths = this.getSettablePaths({ global });
|
|
13058
13565
|
const configTomlFilePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13059
13566
|
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13060
|
-
|
|
13567
|
+
let configToml;
|
|
13568
|
+
try {
|
|
13569
|
+
configToml = smolToml.parse(configTomlFileContent || smolToml.stringify({}));
|
|
13570
|
+
} catch (error) {
|
|
13571
|
+
throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
|
|
13572
|
+
}
|
|
13061
13573
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
13062
13574
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
13063
13575
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
@@ -14031,47 +14543,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
14031
14543
|
}
|
|
14032
14544
|
};
|
|
14033
14545
|
//#endregion
|
|
14034
|
-
//#region src/constants/grokcli-paths.ts
|
|
14035
|
-
/**
|
|
14036
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14037
|
-
*
|
|
14038
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14039
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14040
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14041
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14042
|
-
*
|
|
14043
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14044
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14045
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14046
|
-
* @see https://docs.x.ai/build/overview
|
|
14047
|
-
*/
|
|
14048
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14049
|
-
const GROKCLI_DIR = ".grok";
|
|
14050
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14051
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14052
|
-
/**
|
|
14053
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14054
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14055
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14056
|
-
*/
|
|
14057
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14058
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14059
|
-
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
14060
|
-
/**
|
|
14061
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14062
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14063
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14064
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14065
|
-
*/
|
|
14066
|
-
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
14067
|
-
/**
|
|
14068
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14069
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14070
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14071
|
-
* MCP/skills/subagents adapters).
|
|
14072
|
-
*/
|
|
14073
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14074
|
-
//#endregion
|
|
14075
14546
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
14076
14547
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
14077
14548
|
/**
|
|
@@ -14231,6 +14702,54 @@ function resolveHermesTimeout(config) {
|
|
|
14231
14702
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
14232
14703
|
}
|
|
14233
14704
|
/**
|
|
14705
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
14706
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
14707
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
14708
|
+
* `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
|
|
14709
|
+
* from `source` to `target`. Field names are identical on both sides (the
|
|
14710
|
+
* canonical `McpServerSchema` is a `looseObject`), so this serves export and
|
|
14711
|
+
* import alike. See the Hermes mcp-config-reference.
|
|
14712
|
+
*/
|
|
14713
|
+
function copyHermesAdvancedFields(source, target) {
|
|
14714
|
+
if (typeof source.auth === "string") target.auth = source.auth;
|
|
14715
|
+
if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
|
|
14716
|
+
if (typeof source.client_key === "string") target.client_key = source.client_key;
|
|
14717
|
+
if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
|
|
14718
|
+
if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
|
|
14719
|
+
}
|
|
14720
|
+
/**
|
|
14721
|
+
* Builds Hermes's per-server `tools` block from a canonical server config. The
|
|
14722
|
+
* canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
|
|
14723
|
+
* and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
|
|
14724
|
+
* `prompts`/`resources` capability flags. Returns an empty object when the
|
|
14725
|
+
* server has no tool scoping (the caller omits the block in that case).
|
|
14726
|
+
*
|
|
14727
|
+
* Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
|
|
14728
|
+
* than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
|
|
14729
|
+
* is reserved as a `string[]` (used by other tools) — reusing it for an object
|
|
14730
|
+
* would fail validation on the next `generate`.
|
|
14731
|
+
*/
|
|
14732
|
+
function buildHermesToolsBlock(config) {
|
|
14733
|
+
const tools = {};
|
|
14734
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
14735
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14736
|
+
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
14737
|
+
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
14738
|
+
return tools;
|
|
14739
|
+
}
|
|
14740
|
+
/**
|
|
14741
|
+
* Applies a Hermes per-server `tools` block back onto a canonical server config
|
|
14742
|
+
* (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
|
|
14743
|
+
* `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
|
|
14744
|
+
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
14745
|
+
*/
|
|
14746
|
+
function applyHermesToolsBlock(hermesTools, server) {
|
|
14747
|
+
if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
14748
|
+
if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
14749
|
+
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
14750
|
+
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
14751
|
+
}
|
|
14752
|
+
/**
|
|
14234
14753
|
* Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
|
|
14235
14754
|
*
|
|
14236
14755
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
@@ -14264,9 +14783,8 @@ function convertServerToHermes(config) {
|
|
|
14264
14783
|
if (config.disabled === true) out.enabled = false;
|
|
14265
14784
|
const timeout = resolveHermesTimeout(config);
|
|
14266
14785
|
if (timeout !== void 0) out.timeout = timeout;
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14786
|
+
copyHermesAdvancedFields(config, out);
|
|
14787
|
+
const tools = buildHermesToolsBlock(config);
|
|
14270
14788
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
14271
14789
|
return out;
|
|
14272
14790
|
}
|
|
@@ -14309,10 +14827,8 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
14309
14827
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
14310
14828
|
if (config.enabled === false) server.disabled = true;
|
|
14311
14829
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
14315
|
-
}
|
|
14830
|
+
copyHermesAdvancedFields(config, server);
|
|
14831
|
+
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
14316
14832
|
result[name] = server;
|
|
14317
14833
|
}
|
|
14318
14834
|
return result;
|
|
@@ -16444,10 +16960,14 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
16444
16960
|
if (!rulesyncMcp) throw new Error(`No ${RULESYNC_MCP_RELATIVE_FILE_PATH} found.`);
|
|
16445
16961
|
const factory = this.getFactory(this.toolTarget);
|
|
16446
16962
|
return await Promise.all([rulesyncMcp].map(async (mcp) => {
|
|
16963
|
+
const targetedRulesyncMcp = mcp.forTarget({
|
|
16964
|
+
toolTarget: this.toolTarget,
|
|
16965
|
+
logger: this.logger
|
|
16966
|
+
});
|
|
16447
16967
|
const fieldsToStrip = [];
|
|
16448
16968
|
if (!factory.meta.supportsEnabledTools) fieldsToStrip.push("enabledTools");
|
|
16449
16969
|
if (!factory.meta.supportsDisabledTools) fieldsToStrip.push("disabledTools");
|
|
16450
|
-
const filteredRulesyncMcp =
|
|
16970
|
+
const filteredRulesyncMcp = targetedRulesyncMcp.stripMcpServerFields(fieldsToStrip);
|
|
16451
16971
|
return await factory.class.fromRulesyncMcp({
|
|
16452
16972
|
outputRoot: this.outputRoot,
|
|
16453
16973
|
rulesyncMcp: filteredRulesyncMcp,
|
|
@@ -16497,6 +17017,25 @@ const PermissionActionSchema = z.enum([
|
|
|
16497
17017
|
*/
|
|
16498
17018
|
const PermissionRulesSchema = z.record(z.string(), PermissionActionSchema);
|
|
16499
17019
|
/**
|
|
17020
|
+
* Tool-scoped canonical `permission` block: the same shape as the shared
|
|
17021
|
+
* top-level `permission` record, but applied only to the tool whose override
|
|
17022
|
+
* key it appears under. During generation the categories placed here are
|
|
17023
|
+
* merged over the shared `permission` block per category (the tool-scoped
|
|
17024
|
+
* category replaces the shared one wholesale), mirroring how
|
|
17025
|
+
* `.rulesync/hooks.json` merges `{toolname}.hooks` per event.
|
|
17026
|
+
*
|
|
17027
|
+
* @example
|
|
17028
|
+
* { "claudecode": { "permission": { "bash": { "git push *": "deny" } } } }
|
|
17029
|
+
*/
|
|
17030
|
+
const ToolScopedPermissionSchema = z.record(z.string(), PermissionRulesSchema);
|
|
17031
|
+
/**
|
|
17032
|
+
* Generic tool-scoped override block for tools that have no tool-specific
|
|
17033
|
+
* override keys of their own; it carries only the canonical tool-scoped
|
|
17034
|
+
* `permission` block. Kept `looseObject` so future tool-specific keys can be
|
|
17035
|
+
* added without a schema break.
|
|
17036
|
+
*/
|
|
17037
|
+
const CanonicalPermissionsOverrideSchema = z.looseObject({ permission: z.optional(ToolScopedPermissionSchema) });
|
|
17038
|
+
/**
|
|
16500
17039
|
* OpenCode-specific permission value. Unlike the shared canonical block, which
|
|
16501
17040
|
* only accepts a pattern-to-action map, OpenCode also allows a bare action
|
|
16502
17041
|
* string that applies to the whole category (e.g. `"external_directory": "deny"`).
|
|
@@ -16526,7 +17065,7 @@ const OpencodePermissionsOverrideSchema = z.looseObject({ permission: z.optional
|
|
|
16526
17065
|
* @example
|
|
16527
17066
|
* { "approvals": { "mode": "smart" }, "security": { "allow_private_urls": false } }
|
|
16528
17067
|
*/
|
|
16529
|
-
const HermesPermissionsOverrideSchema = z.looseObject({});
|
|
17068
|
+
const HermesPermissionsOverrideSchema = z.looseObject({ permission: z.optional(ToolScopedPermissionSchema) });
|
|
16530
17069
|
/**
|
|
16531
17070
|
* Tool-scoped override block for Cline. Cline's `command-permissions.json`
|
|
16532
17071
|
* carries a single global `allowRedirects` boolean (gates shell redirection
|
|
@@ -16538,7 +17077,10 @@ const HermesPermissionsOverrideSchema = z.looseObject({});
|
|
|
16538
17077
|
* @example
|
|
16539
17078
|
* { "allowRedirects": true }
|
|
16540
17079
|
*/
|
|
16541
|
-
const ClinePermissionsOverrideSchema = z.looseObject({
|
|
17080
|
+
const ClinePermissionsOverrideSchema = z.looseObject({
|
|
17081
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17082
|
+
allowRedirects: z.optional(z.boolean())
|
|
17083
|
+
});
|
|
16542
17084
|
/**
|
|
16543
17085
|
* Tool-scoped override block for Kilo Code (an OpenCode fork). Kilo's permission
|
|
16544
17086
|
* object is a free-form record with tool-specific keys that have no canonical
|
|
@@ -16568,7 +17110,10 @@ const KiloPermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
|
|
|
16568
17110
|
* @example
|
|
16569
17111
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
16570
17112
|
*/
|
|
16571
|
-
const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
17113
|
+
const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
17114
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17115
|
+
permissions: z.optional(z.looseObject({}))
|
|
17116
|
+
});
|
|
16572
17117
|
/**
|
|
16573
17118
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
16574
17119
|
* carries a `sensitive_patterns` list — patterns that escalate to ASK even when
|
|
@@ -16600,6 +17145,7 @@ const VibePermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
|
|
|
16600
17145
|
* { "approvalMode": "auto-review" }
|
|
16601
17146
|
*/
|
|
16602
17147
|
const CursorPermissionsOverrideSchema = z.looseObject({
|
|
17148
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16603
17149
|
approvalMode: z.optional(z.string()),
|
|
16604
17150
|
sandbox: z.optional(z.looseObject({}))
|
|
16605
17151
|
});
|
|
@@ -16607,18 +17153,28 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
16607
17153
|
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
16608
17154
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
16609
17155
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
16610
|
-
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`).
|
|
16611
|
-
*
|
|
16612
|
-
*
|
|
16613
|
-
*
|
|
16614
|
-
*
|
|
17156
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
|
|
17157
|
+
* exposes `permissions.autoMode` (the Auto Mode classifier config:
|
|
17158
|
+
* `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
|
|
17159
|
+
* https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
|
|
17160
|
+
* likewise has no canonical category. Fields placed here are merged into the
|
|
17161
|
+
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
17162
|
+
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
17163
|
+
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
17164
|
+
* `tools`/`security`/`autoMode` key can be authored.
|
|
16615
17165
|
*
|
|
16616
17166
|
* @example
|
|
16617
|
-
* {
|
|
17167
|
+
* {
|
|
17168
|
+
* "tools": { "approvalMode": "auto-edit" },
|
|
17169
|
+
* "security": { "folderTrust": { "enabled": true } },
|
|
17170
|
+
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
17171
|
+
* }
|
|
16618
17172
|
*/
|
|
16619
17173
|
const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
17174
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16620
17175
|
tools: z.optional(z.looseObject({})),
|
|
16621
|
-
security: z.optional(z.looseObject({}))
|
|
17176
|
+
security: z.optional(z.looseObject({})),
|
|
17177
|
+
autoMode: z.optional(z.looseObject({}))
|
|
16622
17178
|
});
|
|
16623
17179
|
/**
|
|
16624
17180
|
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
@@ -16637,6 +17193,7 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
|
16637
17193
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
16638
17194
|
*/
|
|
16639
17195
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
17196
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16640
17197
|
sandbox: z.optional(z.looseObject({})),
|
|
16641
17198
|
agent: z.optional(z.looseObject({}))
|
|
16642
17199
|
});
|
|
@@ -16645,16 +17202,21 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
16645
17202
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
16646
17203
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
16647
17204
|
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
16648
|
-
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
17205
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
17206
|
+
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
16649
17207
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
16650
|
-
* `interactionMode`). Fields placed here are merged
|
|
17208
|
+
* `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
|
|
17209
|
+
* into `settings.json` and
|
|
16651
17210
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
16652
17211
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
16653
17212
|
*
|
|
16654
17213
|
* @example
|
|
16655
17214
|
* { "commandBlocklist": ["rm -rf /*"], "sandbox": { "enabled": true } }
|
|
16656
17215
|
*/
|
|
16657
|
-
const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
17216
|
+
const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
17217
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17218
|
+
commandBlocklist: z.optional(z.array(z.string()))
|
|
17219
|
+
});
|
|
16658
17220
|
/**
|
|
16659
17221
|
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
16660
17222
|
* file-read/read-only autonomy keys with no canonical per-command allow/ask/deny
|
|
@@ -16671,6 +17233,7 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({ commandBlocklist:
|
|
|
16671
17233
|
* { "agent_mode_coding_permissions": "always_allow_reading", "agent_mode_execute_readonly_commands": true }
|
|
16672
17234
|
*/
|
|
16673
17235
|
const WarpPermissionsOverrideSchema = z.looseObject({
|
|
17236
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16674
17237
|
agent_mode_coding_permissions: z.optional(z.string()),
|
|
16675
17238
|
agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
|
|
16676
17239
|
agent_mode_execute_readonly_commands: z.optional(z.boolean())
|
|
@@ -16688,6 +17251,7 @@ const WarpPermissionsOverrideSchema = z.looseObject({
|
|
|
16688
17251
|
* { "allowReadonlyCommands": true, "defaultBehavior": "ask" }
|
|
16689
17252
|
*/
|
|
16690
17253
|
const JuniePermissionsOverrideSchema = z.looseObject({
|
|
17254
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16691
17255
|
allowReadonlyCommands: z.optional(z.boolean()),
|
|
16692
17256
|
defaultBehavior: z.optional(z.string())
|
|
16693
17257
|
});
|
|
@@ -16715,6 +17279,7 @@ const JuniePermissionsOverrideSchema = z.looseObject({
|
|
|
16715
17279
|
* { "step_permission_overrides": { "ai_review": "readonly" }, "provider_options": { "codex": { "network_access": true } } }
|
|
16716
17280
|
*/
|
|
16717
17281
|
const TaktPermissionsOverrideSchema = z.looseObject({
|
|
17282
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16718
17283
|
step_permission_overrides: z.optional(z.record(z.string(), z.string())),
|
|
16719
17284
|
provider_options: z.optional(z.looseObject({}))
|
|
16720
17285
|
});
|
|
@@ -16742,6 +17307,7 @@ const TaktPermissionsOverrideSchema = z.looseObject({
|
|
|
16742
17307
|
* "permissions": [{ "tool": "Bash", "action": "delegate", "to": "approve.sh" }] }
|
|
16743
17308
|
*/
|
|
16744
17309
|
const AmpPermissionsOverrideSchema = z.looseObject({
|
|
17310
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16745
17311
|
permissions: z.optional(z.array(z.looseObject({
|
|
16746
17312
|
tool: z.string(),
|
|
16747
17313
|
action: z.string()
|
|
@@ -16770,6 +17336,7 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
16770
17336
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
16771
17337
|
*/
|
|
16772
17338
|
const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
17339
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
16773
17340
|
toolPermission: z.optional(z.string()),
|
|
16774
17341
|
enableTerminalSandbox: z.optional(z.boolean())
|
|
16775
17342
|
});
|
|
@@ -16795,10 +17362,13 @@ const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
|
16795
17362
|
* "permission": { "type": "webhook-policy", "webhookUrl": "https://api.example.com/validate" } },
|
|
16796
17363
|
* { "toolName": "view", "eventType": "tool-response", "permission": { "type": "allow" } } ] }
|
|
16797
17364
|
*/
|
|
16798
|
-
const AugmentcodePermissionsOverrideSchema = z.looseObject({
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
17365
|
+
const AugmentcodePermissionsOverrideSchema = z.looseObject({
|
|
17366
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17367
|
+
toolPermissions: z.optional(z.array(z.looseObject({
|
|
17368
|
+
toolName: z.string(),
|
|
17369
|
+
permission: z.looseObject({ type: z.string() })
|
|
17370
|
+
})))
|
|
17371
|
+
});
|
|
16802
17372
|
/**
|
|
16803
17373
|
* Tool-scoped override block for Kiro. Kiro's agent config (`.kiro/agents/<name>.json`)
|
|
16804
17374
|
* exposes per-tool `toolsSettings` knobs with no canonical allow/ask/deny
|
|
@@ -16824,21 +17394,59 @@ const AugmentcodePermissionsOverrideSchema = z.looseObject({ toolPermissions: z.
|
|
|
16824
17394
|
* "aws": { "allowedServices": ["s3"], "deniedServices": ["eks"] },
|
|
16825
17395
|
* "web_fetch": { "trusted": [".*github\\.com.*"] } } }
|
|
16826
17396
|
*/
|
|
16827
|
-
const KiroPermissionsOverrideSchema = z.looseObject({
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
17397
|
+
const KiroPermissionsOverrideSchema = z.looseObject({
|
|
17398
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17399
|
+
toolsSettings: z.optional(z.looseObject({
|
|
17400
|
+
shell: z.optional(z.looseObject({
|
|
17401
|
+
autoAllowReadonly: z.optional(z.boolean()),
|
|
17402
|
+
denyByDefault: z.optional(z.boolean())
|
|
17403
|
+
})),
|
|
17404
|
+
aws: z.optional(z.looseObject({
|
|
17405
|
+
allowedServices: z.optional(z.array(z.string())),
|
|
17406
|
+
deniedServices: z.optional(z.array(z.string())),
|
|
17407
|
+
autoAllowReadonly: z.optional(z.boolean())
|
|
17408
|
+
})),
|
|
17409
|
+
web_fetch: z.optional(z.looseObject({
|
|
17410
|
+
trusted: z.optional(z.array(z.string())),
|
|
17411
|
+
blocked: z.optional(z.array(z.string()))
|
|
17412
|
+
}))
|
|
16840
17413
|
}))
|
|
16841
|
-
})
|
|
17414
|
+
});
|
|
17415
|
+
/**
|
|
17416
|
+
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
17417
|
+
* `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
|
|
17418
|
+
* Codex still accepts, so it is included so existing configs round-trip. The
|
|
17419
|
+
* granular table form (`{ granular = { … } }`) is modeled separately in the
|
|
17420
|
+
* override union.
|
|
17421
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17422
|
+
*/
|
|
17423
|
+
const CodexApprovalPolicySchema = z.enum([
|
|
17424
|
+
"untrusted",
|
|
17425
|
+
"on-request",
|
|
17426
|
+
"on-failure",
|
|
17427
|
+
"never"
|
|
17428
|
+
]);
|
|
17429
|
+
/**
|
|
17430
|
+
* Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
|
|
17431
|
+
* `.codex/config.toml`.
|
|
17432
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17433
|
+
*/
|
|
17434
|
+
const CodexSandboxModeSchema = z.enum([
|
|
17435
|
+
"read-only",
|
|
17436
|
+
"workspace-write",
|
|
17437
|
+
"danger-full-access"
|
|
17438
|
+
]);
|
|
17439
|
+
/**
|
|
17440
|
+
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
17441
|
+
* value Codex still accepts for backward compatibility, so it is included so
|
|
17442
|
+
* existing configs round-trip through the rulesync model.
|
|
17443
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17444
|
+
*/
|
|
17445
|
+
const CodexApprovalsReviewerSchema = z.enum([
|
|
17446
|
+
"user",
|
|
17447
|
+
"auto_review",
|
|
17448
|
+
"guardian_subagent"
|
|
17449
|
+
]);
|
|
16842
17450
|
/**
|
|
16843
17451
|
* Codex CLI-scoped permission override.
|
|
16844
17452
|
*
|
|
@@ -16848,15 +17456,16 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
|
|
|
16848
17456
|
* override whose fields are written verbatim as top-level `.codex/config.toml`
|
|
16849
17457
|
* keys (the override wins per key; existing sibling keys the user set directly
|
|
16850
17458
|
* are preserved):
|
|
16851
|
-
* - `approval_policy` — `untrusted` | `on-request`
|
|
16852
|
-
* `{ granular = { … } }` table (kept verbatim; the granular
|
|
16853
|
-
* required fields that are brittle to model as typed keys).
|
|
17459
|
+
* - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
|
|
17460
|
+
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
17461
|
+
* schema has required fields that are brittle to model as typed keys).
|
|
16854
17462
|
* - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
|
|
16855
17463
|
* with the sibling `sandbox_workspace_write` table (`network_access`,
|
|
16856
17464
|
* `writable_roots`, …).
|
|
16857
17465
|
* - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
|
|
16858
17466
|
* `.enabled`, `apps.<id>.default_tools_approval_mode`).
|
|
16859
|
-
* - `approvals_reviewer` — the reviewer-approval surface
|
|
17467
|
+
* - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
|
|
17468
|
+
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
16860
17469
|
*
|
|
16861
17470
|
* Two surfaces are deliberately NOT authorable here so the override can never
|
|
16862
17471
|
* clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
|
|
@@ -16874,11 +17483,12 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
|
|
|
16874
17483
|
* "sandbox_workspace_write": { "network_access": true } }
|
|
16875
17484
|
*/
|
|
16876
17485
|
const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
16877
|
-
|
|
16878
|
-
|
|
17486
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
17487
|
+
approval_policy: z.optional(z.union([CodexApprovalPolicySchema, z.looseObject({})])),
|
|
17488
|
+
sandbox_mode: z.optional(CodexSandboxModeSchema),
|
|
16879
17489
|
sandbox_workspace_write: z.optional(z.looseObject({})),
|
|
16880
17490
|
apps: z.optional(z.looseObject({})),
|
|
16881
|
-
approvals_reviewer: z.optional(z.union([
|
|
17491
|
+
approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})]))
|
|
16882
17492
|
});
|
|
16883
17493
|
/**
|
|
16884
17494
|
* Permissions configuration.
|
|
@@ -16892,6 +17502,15 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
|
16892
17502
|
* `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
|
|
16893
17503
|
* block and ignores them.
|
|
16894
17504
|
*
|
|
17505
|
+
* Additionally, every permissions-capable tool accepts a canonical tool-scoped
|
|
17506
|
+
* `permission` block under its override key (`{toolname}.permission`, same
|
|
17507
|
+
* shape as the shared `permission` record). Its categories apply only to that
|
|
17508
|
+
* tool, merged over the shared block per category — see
|
|
17509
|
+
* `RulesyncPermissions.forTarget`. `kiro-cli`/`kiro-ide` alias to the `kiro`
|
|
17510
|
+
* key and `hermesagent` to `hermes` (matching the shared output file each
|
|
17511
|
+
* writes). OpenCode/Kilo/Vibe keep their existing tool-native `permission`
|
|
17512
|
+
* override semantics (handled by their translators, not the central merge).
|
|
17513
|
+
*
|
|
16895
17514
|
* @example
|
|
16896
17515
|
* {
|
|
16897
17516
|
* "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
|
|
@@ -16917,7 +17536,13 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
16917
17536
|
"antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
16918
17537
|
augmentcode: z.optional(AugmentcodePermissionsOverrideSchema),
|
|
16919
17538
|
kiro: z.optional(KiroPermissionsOverrideSchema),
|
|
16920
|
-
codexcli: z.optional(CodexcliPermissionsOverrideSchema)
|
|
17539
|
+
codexcli: z.optional(CodexcliPermissionsOverrideSchema),
|
|
17540
|
+
"antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
|
|
17541
|
+
devin: z.optional(CanonicalPermissionsOverrideSchema),
|
|
17542
|
+
goose: z.optional(CanonicalPermissionsOverrideSchema),
|
|
17543
|
+
grokcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
17544
|
+
rovodev: z.optional(CanonicalPermissionsOverrideSchema),
|
|
17545
|
+
zed: z.optional(CanonicalPermissionsOverrideSchema)
|
|
16921
17546
|
});
|
|
16922
17547
|
/**
|
|
16923
17548
|
* Full permissions file schema including optional $schema field.
|
|
@@ -16932,7 +17557,7 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16932
17557
|
json;
|
|
16933
17558
|
constructor(params) {
|
|
16934
17559
|
super({ ...params });
|
|
16935
|
-
this.json =
|
|
17560
|
+
this.json = parseJsonc(this.fileContent);
|
|
16936
17561
|
if (params.validate) {
|
|
16937
17562
|
const result = this.validate();
|
|
16938
17563
|
if (!result.success) throw result.error;
|
|
@@ -16941,7 +17566,11 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16941
17566
|
static getSettablePaths() {
|
|
16942
17567
|
return {
|
|
16943
17568
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
16944
|
-
relativeFilePath: RULESYNC_PERMISSIONS_FILE_NAME
|
|
17569
|
+
relativeFilePath: RULESYNC_PERMISSIONS_FILE_NAME,
|
|
17570
|
+
jsonc: {
|
|
17571
|
+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
17572
|
+
relativeFilePath: RULESYNC_PERMISSIONS_JSONC_FILE_NAME
|
|
17573
|
+
}
|
|
16945
17574
|
};
|
|
16946
17575
|
}
|
|
16947
17576
|
validate() {
|
|
@@ -16957,20 +17586,85 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
16957
17586
|
}
|
|
16958
17587
|
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
16959
17588
|
const paths = RulesyncPermissions.getSettablePaths();
|
|
16960
|
-
const
|
|
16961
|
-
if (!await fileExists(filePath)) throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} found.`);
|
|
16962
|
-
const fileContent = await readFileContent(filePath);
|
|
16963
|
-
return new RulesyncPermissions({
|
|
16964
|
-
outputRoot,
|
|
17589
|
+
const candidates = [paths.jsonc, {
|
|
16965
17590
|
relativeDirPath: paths.relativeDirPath,
|
|
16966
|
-
relativeFilePath: paths.relativeFilePath
|
|
16967
|
-
|
|
16968
|
-
|
|
16969
|
-
|
|
17591
|
+
relativeFilePath: paths.relativeFilePath
|
|
17592
|
+
}];
|
|
17593
|
+
for (const candidate of candidates) {
|
|
17594
|
+
const filePath = join(outputRoot, candidate.relativeDirPath, candidate.relativeFilePath);
|
|
17595
|
+
if (!await fileExists(filePath)) continue;
|
|
17596
|
+
const fileContent = await readFileContent(filePath);
|
|
17597
|
+
return new RulesyncPermissions({
|
|
17598
|
+
outputRoot,
|
|
17599
|
+
relativeDirPath: candidate.relativeDirPath,
|
|
17600
|
+
relativeFilePath: candidate.relativeFilePath,
|
|
17601
|
+
fileContent,
|
|
17602
|
+
validate
|
|
17603
|
+
});
|
|
17604
|
+
}
|
|
17605
|
+
throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} or ${RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH} found.`);
|
|
16970
17606
|
}
|
|
16971
17607
|
getJson() {
|
|
16972
17608
|
return this.json;
|
|
16973
17609
|
}
|
|
17610
|
+
/**
|
|
17611
|
+
* Build the effective permissions config for one tool target by merging the
|
|
17612
|
+
* tool-scoped `{toolname}.permission` block over the shared `permission`
|
|
17613
|
+
* block, per category (the tool-scoped category replaces the shared one
|
|
17614
|
+
* wholesale — mirroring how `{toolname}.hooks` merges per event in
|
|
17615
|
+
* `.rulesync/hooks.json`). The consumed `permission` key is stripped from
|
|
17616
|
+
* the override block so verbatim-passthrough translators (e.g. the Hermes
|
|
17617
|
+
* deep merge or the Codex CLI top-level key whitelist) never see it.
|
|
17618
|
+
*
|
|
17619
|
+
* Returns the same instance when the target has no tool-scoped canonical
|
|
17620
|
+
* `permission` block, or when the target's translator natively consumes its
|
|
17621
|
+
* own `permission` override shape (OpenCode / Kilo / Vibe).
|
|
17622
|
+
*/
|
|
17623
|
+
forTarget({ toolTarget, logger }) {
|
|
17624
|
+
if (NATIVE_PERMISSION_OVERRIDE_TARGETS.has(toolTarget)) return this;
|
|
17625
|
+
const overrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
|
|
17626
|
+
const json = this.json;
|
|
17627
|
+
if (overrideKey !== toolTarget && isRecord(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${join(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
17628
|
+
const overrideBlock = json[overrideKey];
|
|
17629
|
+
if (!isRecord(overrideBlock) || !isRecord(overrideBlock.permission)) return this;
|
|
17630
|
+
const { permission: toolScopedPermission, ...restOverride } = overrideBlock;
|
|
17631
|
+
const merged = {
|
|
17632
|
+
...json,
|
|
17633
|
+
permission: {
|
|
17634
|
+
...this.json.permission,
|
|
17635
|
+
...toolScopedPermission
|
|
17636
|
+
}
|
|
17637
|
+
};
|
|
17638
|
+
if (Object.keys(restOverride).length > 0) merged[overrideKey] = restOverride;
|
|
17639
|
+
else delete merged[overrideKey];
|
|
17640
|
+
return new RulesyncPermissions({
|
|
17641
|
+
outputRoot: this.outputRoot,
|
|
17642
|
+
relativeDirPath: this.relativeDirPath,
|
|
17643
|
+
relativeFilePath: this.relativeFilePath,
|
|
17644
|
+
fileContent: JSON.stringify(merged, null, 2)
|
|
17645
|
+
});
|
|
17646
|
+
}
|
|
17647
|
+
};
|
|
17648
|
+
/**
|
|
17649
|
+
* Targets whose tool-scoped `permission` override uses tool-native semantics
|
|
17650
|
+
* consumed directly by their translator (bare action strings / tool-only
|
|
17651
|
+
* categories for OpenCode and Kilo, `sensitive_patterns` objects for Vibe).
|
|
17652
|
+
* The central canonical merge must not touch those blocks.
|
|
17653
|
+
*/
|
|
17654
|
+
const NATIVE_PERMISSION_OVERRIDE_TARGETS = /* @__PURE__ */ new Set([
|
|
17655
|
+
"opencode",
|
|
17656
|
+
"kilo",
|
|
17657
|
+
"vibe"
|
|
17658
|
+
]);
|
|
17659
|
+
/**
|
|
17660
|
+
* Targets that read their tool-scoped override from a differently named key:
|
|
17661
|
+
* Kiro IDE/CLI share the `kiro` block (they write the same agent config) and
|
|
17662
|
+
* Hermes Agent's established override key is `hermes`.
|
|
17663
|
+
*/
|
|
17664
|
+
const PERMISSION_OVERRIDE_KEY_ALIASES = {
|
|
17665
|
+
"kiro-cli": "kiro",
|
|
17666
|
+
"kiro-ide": "kiro",
|
|
17667
|
+
hermesagent: "hermes"
|
|
16974
17668
|
};
|
|
16975
17669
|
//#endregion
|
|
16976
17670
|
//#region src/features/permissions/tool-permissions.ts
|
|
@@ -18810,10 +19504,15 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18810
19504
|
existingDomainsHadUnknown,
|
|
18811
19505
|
logger
|
|
18812
19506
|
});
|
|
18813
|
-
|
|
19507
|
+
const profile = mergeWithExistingProfile({
|
|
18814
19508
|
newProfile,
|
|
18815
19509
|
existingProfile
|
|
18816
19510
|
});
|
|
19511
|
+
permissionsTable[RULESYNC_PROFILE_NAME] = preserveUnmanagedProfileKeys({
|
|
19512
|
+
rawExistingProfile: permissionsTable[RULESYNC_PROFILE_NAME],
|
|
19513
|
+
profile,
|
|
19514
|
+
logger
|
|
19515
|
+
});
|
|
18817
19516
|
const overridePatch = computeCodexcliOverridePatch({
|
|
18818
19517
|
existing,
|
|
18819
19518
|
override: rulesyncPermissions.getJson().codexcli,
|
|
@@ -19009,6 +19708,43 @@ function warnAboutPreservedProfileState({ existingProfile, newProfile, existingD
|
|
|
19009
19708
|
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.`);
|
|
19010
19709
|
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19011
19710
|
}
|
|
19711
|
+
const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
19712
|
+
"description",
|
|
19713
|
+
"extends",
|
|
19714
|
+
"filesystem",
|
|
19715
|
+
"network"
|
|
19716
|
+
]);
|
|
19717
|
+
const MANAGED_NETWORK_KEYS = /* @__PURE__ */ new Set([
|
|
19718
|
+
"enabled",
|
|
19719
|
+
"mode",
|
|
19720
|
+
"domains",
|
|
19721
|
+
"unix_sockets"
|
|
19722
|
+
]);
|
|
19723
|
+
/**
|
|
19724
|
+
* Re-attach the keys of the existing rulesync profile that rulesync does not
|
|
19725
|
+
* manage. The managed keys always come from `profile` (the freshly computed
|
|
19726
|
+
* merge result); unmanaged siblings — at the profile level and inside the
|
|
19727
|
+
* `network` table — are preserved verbatim from the existing config so a
|
|
19728
|
+
* regeneration never deletes user-authored Codex settings.
|
|
19729
|
+
*/
|
|
19730
|
+
function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
|
|
19731
|
+
const rawProfileTable = toMutableTable(rawExistingProfile);
|
|
19732
|
+
const profileExtras = Object.fromEntries(Object.entries(rawProfileTable).filter(([key]) => !MANAGED_PROFILE_KEYS.has(key)));
|
|
19733
|
+
const rawNetworkTable = toMutableTable(rawProfileTable.network);
|
|
19734
|
+
const networkExtras = Object.fromEntries(Object.entries(rawNetworkTable).filter(([key]) => !MANAGED_NETWORK_KEYS.has(key)));
|
|
19735
|
+
const preservedKeyNames = [...Object.keys(profileExtras), ...Object.keys(networkExtras).map((key) => `network.${key}`)];
|
|
19736
|
+
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.`);
|
|
19737
|
+
const result = {
|
|
19738
|
+
...profileExtras,
|
|
19739
|
+
...profile
|
|
19740
|
+
};
|
|
19741
|
+
const mergedNetwork = {
|
|
19742
|
+
...networkExtras,
|
|
19743
|
+
...profile.network
|
|
19744
|
+
};
|
|
19745
|
+
if (Object.keys(mergedNetwork).length > 0) result.network = mergedNetwork;
|
|
19746
|
+
return result;
|
|
19747
|
+
}
|
|
19012
19748
|
function mergeWithExistingProfile({ newProfile, existingProfile }) {
|
|
19013
19749
|
if (!existingProfile) return newProfile;
|
|
19014
19750
|
const mergedNetwork = { ...newProfile.network };
|
|
@@ -19739,9 +20475,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
19739
20475
|
"networkPolicy",
|
|
19740
20476
|
"sandbox",
|
|
19741
20477
|
"mcpPolicy",
|
|
20478
|
+
"mcpAutonomyOverrides",
|
|
19742
20479
|
"enableDroidShield",
|
|
19743
20480
|
"sessionDefaultSettings",
|
|
19744
20481
|
"maxAutonomyLevel",
|
|
20482
|
+
"subagentAutonomyLevel",
|
|
19745
20483
|
"interactionMode"
|
|
19746
20484
|
];
|
|
19747
20485
|
/**
|
|
@@ -20099,7 +20837,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
20099
20837
|
}
|
|
20100
20838
|
//#endregion
|
|
20101
20839
|
//#region src/features/permissions/grokcli-permissions.ts
|
|
20102
|
-
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
20103
20840
|
const GROKCLI_UI_KEY = "ui";
|
|
20104
20841
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
20105
20842
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
@@ -20201,13 +20938,19 @@ function parseGrokEntry(entry) {
|
|
|
20201
20938
|
* compatibility with older Grok versions: `always-approve` when the config is
|
|
20202
20939
|
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
20203
20940
|
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
20204
|
-
* arrays).
|
|
20205
|
-
*
|
|
20206
|
-
*
|
|
20207
|
-
*
|
|
20208
|
-
*
|
|
20941
|
+
* arrays). It is a user-level UI setting, so it is only written in global scope
|
|
20942
|
+
* (see below).
|
|
20943
|
+
*
|
|
20944
|
+
* Both scopes are supported: the adapter syncs the project-level
|
|
20945
|
+
* `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
|
|
20946
|
+
* (with `--global`). Grok documents that "Project configs are limited to MCP
|
|
20947
|
+
* servers, plugins, and permission rules, not full user configs"
|
|
20948
|
+
* (https://docs.x.ai/build/settings), so the project config carries only the
|
|
20949
|
+
* fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
|
|
20950
|
+
* is written in global scope only. The shared config is merged in
|
|
20209
20951
|
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
20210
|
-
* tools it models and the `[ui] permission_mode` value, while
|
|
20952
|
+
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
20953
|
+
* every other key
|
|
20211
20954
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
20212
20955
|
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
20213
20956
|
* file is never deleted.
|
|
@@ -20229,7 +20972,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20229
20972
|
};
|
|
20230
20973
|
}
|
|
20231
20974
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20232
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
20233
20975
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
20234
20976
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
20235
20977
|
return new GrokcliPermissions({
|
|
@@ -20238,11 +20980,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20238
20980
|
relativeFilePath: paths.relativeFilePath,
|
|
20239
20981
|
fileContent,
|
|
20240
20982
|
validate,
|
|
20241
|
-
global
|
|
20983
|
+
global
|
|
20242
20984
|
});
|
|
20243
20985
|
}
|
|
20244
20986
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
20245
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
20246
20987
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
20247
20988
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20248
20989
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -20261,11 +21002,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20261
21002
|
deny: buckets.deny,
|
|
20262
21003
|
ask: buckets.ask
|
|
20263
21004
|
};
|
|
20264
|
-
const
|
|
20265
|
-
const ui = {
|
|
21005
|
+
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
20266
21006
|
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
20267
|
-
[GROKCLI_PERMISSION_MODE_KEY]:
|
|
20268
|
-
};
|
|
21007
|
+
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
21008
|
+
} } : {};
|
|
20269
21009
|
return new GrokcliPermissions({
|
|
20270
21010
|
outputRoot,
|
|
20271
21011
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -20276,12 +21016,12 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20276
21016
|
existingContent,
|
|
20277
21017
|
patch: {
|
|
20278
21018
|
[GROKCLI_PERMISSION_KEY]: permission,
|
|
20279
|
-
|
|
21019
|
+
...uiPatch
|
|
20280
21020
|
},
|
|
20281
21021
|
filePath
|
|
20282
21022
|
}),
|
|
20283
21023
|
validate: true,
|
|
20284
|
-
global
|
|
21024
|
+
global
|
|
20285
21025
|
});
|
|
20286
21026
|
}
|
|
20287
21027
|
toRulesyncPermissions() {
|
|
@@ -20302,14 +21042,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
20302
21042
|
error: null
|
|
20303
21043
|
};
|
|
20304
21044
|
}
|
|
20305
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
21045
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
20306
21046
|
return new GrokcliPermissions({
|
|
20307
21047
|
outputRoot,
|
|
20308
21048
|
relativeDirPath,
|
|
20309
21049
|
relativeFilePath,
|
|
20310
21050
|
fileContent: "",
|
|
20311
21051
|
validate: false,
|
|
20312
|
-
global
|
|
21052
|
+
global
|
|
20313
21053
|
});
|
|
20314
21054
|
}
|
|
20315
21055
|
};
|
|
@@ -21224,6 +21964,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
21224
21964
|
"notebookedit",
|
|
21225
21965
|
"agent"
|
|
21226
21966
|
]);
|
|
21967
|
+
/**
|
|
21968
|
+
* Translate between rulesync's canonical permission category names and
|
|
21969
|
+
* OpenCode's native permission keys. OpenCode has no `agent` key — subagent
|
|
21970
|
+
* launches are gated by the `task` key (see the documented key list at
|
|
21971
|
+
* https://opencode.ai/docs/permissions/). Without this translation a canonical
|
|
21972
|
+
* `agent: deny` would be written verbatim into `opencode.json` and silently
|
|
21973
|
+
* ignored by OpenCode. Unknown names pass through unchanged.
|
|
21974
|
+
*/
|
|
21975
|
+
const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
|
|
21976
|
+
const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
|
|
21977
|
+
function toOpencodePermissionKey(canonical) {
|
|
21978
|
+
return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
|
|
21979
|
+
}
|
|
21980
|
+
function toCanonicalPermissionKey(opencodeKey) {
|
|
21981
|
+
return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
|
|
21982
|
+
}
|
|
21227
21983
|
function isSharedPermissionCategory(category) {
|
|
21228
21984
|
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
21229
21985
|
}
|
|
@@ -21290,6 +22046,8 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21290
22046
|
}
|
|
21291
22047
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
21292
22048
|
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
22049
|
+
const sharedPermission = {};
|
|
22050
|
+
for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
|
|
21293
22051
|
return new OpencodePermissions({
|
|
21294
22052
|
outputRoot,
|
|
21295
22053
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -21299,7 +22057,7 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21299
22057
|
feature: "permissions",
|
|
21300
22058
|
existingContent: fileContent ?? "",
|
|
21301
22059
|
patch: { permission: {
|
|
21302
|
-
...
|
|
22060
|
+
...sharedPermission,
|
|
21303
22061
|
...overridePermission
|
|
21304
22062
|
} },
|
|
21305
22063
|
filePath: join(jsonDir, relativeFilePath)
|
|
@@ -21315,8 +22073,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
21315
22073
|
}
|
|
21316
22074
|
const shared = {};
|
|
21317
22075
|
const overrideOnly = {};
|
|
21318
|
-
for (const [category, value] of Object.entries(rawPermission))
|
|
21319
|
-
|
|
22076
|
+
for (const [category, value] of Object.entries(rawPermission)) {
|
|
22077
|
+
const canonicalCategory = toCanonicalPermissionKey(category);
|
|
22078
|
+
if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
|
|
22079
|
+
else overrideOnly[category] = value;
|
|
22080
|
+
}
|
|
21320
22081
|
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
21321
22082
|
permission: shared,
|
|
21322
22083
|
opencode: { permission: overrideOnly }
|
|
@@ -21441,6 +22202,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
21441
22202
|
"disabled"
|
|
21442
22203
|
];
|
|
21443
22204
|
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
22205
|
+
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
21444
22206
|
function asPlainRecord(value) {
|
|
21445
22207
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21446
22208
|
}
|
|
@@ -21512,8 +22274,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21512
22274
|
else delete mergedPermissions.ask;
|
|
21513
22275
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21514
22276
|
else delete mergedPermissions.deny;
|
|
21515
|
-
const patch = { permissions: mergedPermissions };
|
|
21516
22277
|
const override = config.qwencode;
|
|
22278
|
+
if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
|
|
22279
|
+
const patch = { permissions: mergedPermissions };
|
|
21517
22280
|
if (override?.tools !== void 0) patch.tools = {
|
|
21518
22281
|
...asPlainRecord(settings.tools),
|
|
21519
22282
|
...asPlainRecord(override.tools)
|
|
@@ -21555,9 +22318,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21555
22318
|
});
|
|
21556
22319
|
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
21557
22320
|
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
22321
|
+
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
21558
22322
|
const qwencodeOverride = {};
|
|
21559
22323
|
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
21560
22324
|
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
22325
|
+
if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
|
|
21561
22326
|
const result = { ...config };
|
|
21562
22327
|
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
21563
22328
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -23066,7 +23831,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
23066
23831
|
["grokcli", {
|
|
23067
23832
|
class: GrokcliPermissions,
|
|
23068
23833
|
meta: {
|
|
23069
|
-
supportsProject:
|
|
23834
|
+
supportsProject: true,
|
|
23070
23835
|
supportsGlobal: true,
|
|
23071
23836
|
supportsImport: true
|
|
23072
23837
|
}
|
|
@@ -23241,16 +24006,20 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
23241
24006
|
if (!rulesyncPermissions) throw new Error(`No ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} found.`);
|
|
23242
24007
|
const factory = toolPermissionsFactories.get(this.toolTarget);
|
|
23243
24008
|
if (!factory) throw new Error(`Unsupported tool target: ${this.toolTarget}`);
|
|
24009
|
+
const effectivePermissions = rulesyncPermissions.forTarget({
|
|
24010
|
+
toolTarget: this.toolTarget,
|
|
24011
|
+
logger: this.logger
|
|
24012
|
+
});
|
|
23244
24013
|
const toolPermissions = await factory.class.fromRulesyncPermissions({
|
|
23245
24014
|
outputRoot: this.outputRoot,
|
|
23246
|
-
rulesyncPermissions,
|
|
24015
|
+
rulesyncPermissions: effectivePermissions,
|
|
23247
24016
|
logger: this.logger,
|
|
23248
24017
|
global: this.global
|
|
23249
24018
|
});
|
|
23250
24019
|
if (this.toolTarget !== "codexcli") return [toolPermissions];
|
|
23251
24020
|
return [toolPermissions, createCodexcliBashRulesFile({
|
|
23252
24021
|
outputRoot: this.outputRoot,
|
|
23253
|
-
config:
|
|
24022
|
+
config: effectivePermissions.getJson()
|
|
23254
24023
|
})];
|
|
23255
24024
|
}
|
|
23256
24025
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
@@ -39346,6 +40115,16 @@ async function generatePermissionsCore(params) {
|
|
|
39346
40115
|
//#endregion
|
|
39347
40116
|
//#region src/lib/import.ts
|
|
39348
40117
|
/**
|
|
40118
|
+
* Import always writes the `.json` variant of a fixed-path source file, but a
|
|
40119
|
+
* sibling `.jsonc` variant takes precedence at read time — so an import into a
|
|
40120
|
+
* project that authors the `.jsonc` variant would be silently shadowed. Warn
|
|
40121
|
+
* so the user merges (or removes) one of the two by hand.
|
|
40122
|
+
*/
|
|
40123
|
+
async function warnIfShadowedByJsonc(params) {
|
|
40124
|
+
const { outputRoot, jsonRelativePath, jsoncRelativePath, logger } = params;
|
|
40125
|
+
if (await fileExists(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.`);
|
|
40126
|
+
}
|
|
40127
|
+
/**
|
|
39349
40128
|
* Import configuration files from AI tools.
|
|
39350
40129
|
*/
|
|
39351
40130
|
async function importFromTool(params) {
|
|
@@ -39457,6 +40236,12 @@ async function importMcpCore(params) {
|
|
|
39457
40236
|
}
|
|
39458
40237
|
const rulesyncFiles = await mcpProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39459
40238
|
const { count: writtenCount } = await mcpProcessor.writeAiFiles(rulesyncFiles);
|
|
40239
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40240
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40241
|
+
jsonRelativePath: RULESYNC_MCP_RELATIVE_FILE_PATH,
|
|
40242
|
+
jsoncRelativePath: RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH,
|
|
40243
|
+
logger
|
|
40244
|
+
});
|
|
39460
40245
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} MCP files`);
|
|
39461
40246
|
return writtenCount;
|
|
39462
40247
|
}
|
|
@@ -39556,6 +40341,12 @@ async function importHooksCore(params) {
|
|
|
39556
40341
|
}
|
|
39557
40342
|
const rulesyncFiles = await hooksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39558
40343
|
const { count: writtenCount } = await hooksProcessor.writeAiFiles(rulesyncFiles);
|
|
40344
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40345
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40346
|
+
jsonRelativePath: RULESYNC_HOOKS_RELATIVE_FILE_PATH,
|
|
40347
|
+
jsoncRelativePath: RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH,
|
|
40348
|
+
logger
|
|
40349
|
+
});
|
|
39559
40350
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} hooks file(s)`);
|
|
39560
40351
|
return writtenCount;
|
|
39561
40352
|
}
|
|
@@ -39585,10 +40376,16 @@ async function importPermissionsCore(params) {
|
|
|
39585
40376
|
}
|
|
39586
40377
|
const rulesyncFiles = await permissionsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
39587
40378
|
const { count: writtenCount } = await permissionsProcessor.writeAiFiles(rulesyncFiles);
|
|
40379
|
+
if (writtenCount > 0) await warnIfShadowedByJsonc({
|
|
40380
|
+
outputRoot: config.getOutputRoots()[0] ?? ".",
|
|
40381
|
+
jsonRelativePath: RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH,
|
|
40382
|
+
jsoncRelativePath: RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH,
|
|
40383
|
+
logger
|
|
40384
|
+
});
|
|
39588
40385
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} permissions file(s)`);
|
|
39589
40386
|
return writtenCount;
|
|
39590
40387
|
}
|
|
39591
40388
|
//#endregion
|
|
39592
|
-
export {
|
|
40389
|
+
export { removeFile as $, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as At, ErrorCodes as B, RulesyncHooks as C, RULESYNC_PERMISSIONS_FILE_NAME as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_RELATIVE_DIR_PATH as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_SCHEMA_URL as Et, ConfigResolver as F, fileExists as G, createTempDirectory as H, findControlCharacter as I, getHomeDirectory as J, findFilesByGlobs as K, ConsoleLogger as L, RulesyncCommandFrontmatterSchema as M, formatError as Mt, stringifyFrontmatter as N, ALL_FEATURES as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RULES_RELATIVE_DIR_PATH as Ot, loadYaml as P, ALL_FEATURES_WITH_WILDCARD as Pt, removeDirectory as Q, JsonLogger as R, HooksProcessor as S, RULESYNC_OVERVIEW_FILE_NAME as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Tt, directoryExists as U, checkPathTraversal as V, ensureDir as W, listDirectoryFiles as X, isSymlink as Y, readFileContent as Z, RulesyncPermissions as _, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as _t, convertFromTool as a, ToolTargetSchema as at, IgnoreProcessor as b, RULESYNC_MCP_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CONFIG_SCHEMA_URL as dt, removeTempDirectory as et, SkillsProcessor as f, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as ft, SKILL_FILE_NAME as g, RULESYNC_IGNORE_RELATIVE_FILE_PATH as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_HOOKS_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ALL_TOOL_TARGETS_WITH_WILDCARD as it, RulesyncCommand as j, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_SKILLS_RELATIVE_DIR_PATH as kt, SubagentsProcessor as l, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as lt, RulesyncSkill as m, RULESYNC_HOOKS_JSONC_FILE_NAME as mt, checkRulesyncDirExists as n, writeFileContent as nt, RulesProcessor as o, MAX_FILE_SIZE as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_FILE_NAME as pt, getFileSize as q, generate as r, ALL_TOOL_TARGETS as rt, RulesyncRule as s, RULESYNC_AIIGNORE_FILE_NAME as st, importFromTool as t, toPosixPath as tt, RulesyncSubagent as u, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ut, McpProcessor as v, RULESYNC_MCP_FILE_NAME as vt, CommandsProcessor as w, RULESYNC_PERMISSIONS_JSONC_FILE_NAME as wt, RulesyncIgnore as x, RULESYNC_MCP_SCHEMA_URL as xt, RulesyncMcp as y, RULESYNC_MCP_JSONC_FILE_NAME as yt, CLIError as z };
|
|
39593
40390
|
|
|
39594
|
-
//# sourceMappingURL=import-
|
|
40391
|
+
//# sourceMappingURL=import-B02AyWkQ.js.map
|