rulesync 9.7.0 → 9.8.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.
@@ -114,22 +114,22 @@ function formatError(error) {
114
114
  }
115
115
  //#endregion
116
116
  //#region src/constants/rulesync-paths.ts
117
- const { join: join$249 } = node_path.posix;
117
+ const { join: join$250 } = node_path.posix;
118
118
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
119
119
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
120
120
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
121
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "rules");
122
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "commands");
123
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "subagents");
124
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
125
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
126
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
121
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "rules");
122
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "commands");
123
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "subagents");
124
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
125
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
126
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
127
127
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
128
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
128
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
129
129
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
130
130
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
131
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "skills");
132
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$249(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
131
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "skills");
132
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$250(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
133
133
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
134
134
  const RULESYNC_MCP_FILE_NAME = "mcp.json";
135
135
  const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
@@ -347,7 +347,8 @@ const hooksProcessorToolTargetTuple = [
347
347
  "junie",
348
348
  "vibe",
349
349
  "qwencode",
350
- "reasonix"
350
+ "reasonix",
351
+ "grokcli"
351
352
  ];
352
353
  const permissionsProcessorToolTargetTuple = [
353
354
  "amp",
@@ -6295,10 +6296,13 @@ const CLAUDE_HOOK_EVENTS = [
6295
6296
  * Hook events supported by Devin Local (native `.devin/` hooks).
6296
6297
  *
6297
6298
  * Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
6298
- * seven events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
6299
- * `UserPromptSubmit`, `Stop`, `SessionStart`, and `SessionEnd`. The
6299
+ * eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
6300
+ * `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
6301
+ * `PostCompaction` (fires after context compaction; added in the Devin CLI
6302
+ * stable changelog — https://docs.devin.ai/cli/changelog/stable). The
6300
6303
  * tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
6301
- * a `matcher` (regex against `tool_name`); the session/turn events do not.
6304
+ * a `matcher` (regex against `tool_name`); the session/turn/compaction events
6305
+ * do not.
6302
6306
  *
6303
6307
  * Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
6304
6308
  * is the entire file) or under the `"hooks"` key of `.devin/config.json` /
@@ -6313,7 +6317,8 @@ const DEVIN_HOOK_EVENTS = [
6313
6317
  "postToolUse",
6314
6318
  "beforeSubmitPrompt",
6315
6319
  "stop",
6316
- "permissionRequest"
6320
+ "permissionRequest",
6321
+ "postCompact"
6317
6322
  ];
6318
6323
  /** Hook events supported by OpenCode. */
6319
6324
  const OPENCODE_HOOK_EVENTS = [
@@ -6507,7 +6512,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
6507
6512
  * Hook events supported by AugmentCode (Auggie CLI).
6508
6513
  * Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
6509
6514
  * PreToolUse / PostToolUse (tool events, matcher-aware) plus the
6510
- * SessionStart / SessionEnd / Stop session events (no matcher).
6515
+ * SessionStart / SessionEnd / Stop / Notification session events (no matcher).
6511
6516
  * @see https://docs.augmentcode.com/cli/hooks
6512
6517
  */
6513
6518
  const AUGMENTCODE_HOOK_EVENTS = [
@@ -6515,7 +6520,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
6515
6520
  "postToolUse",
6516
6521
  "sessionStart",
6517
6522
  "sessionEnd",
6518
- "stop"
6523
+ "stop",
6524
+ "notification"
6519
6525
  ];
6520
6526
  /**
6521
6527
  * Hook events supported by Mistral Vibe (mistral-vibe).
@@ -6567,6 +6573,10 @@ const JUNIE_HOOK_EVENTS = [
6567
6573
  * The Qwen-specific events
6568
6574
  * `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
6569
6575
  * `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
6576
+ * Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
6577
+ * `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
6578
+ * `InstructionsLoaded`, which map to the canonical `postToolBatch`,
6579
+ * `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
6570
6580
  * @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
6571
6581
  */
6572
6582
  const QWENCODE_HOOK_EVENTS = [
@@ -6575,7 +6585,9 @@ const QWENCODE_HOOK_EVENTS = [
6575
6585
  "preToolUse",
6576
6586
  "postToolUse",
6577
6587
  "postToolUseFailure",
6588
+ "postToolBatch",
6578
6589
  "beforeSubmitPrompt",
6590
+ "userPromptExpansion",
6579
6591
  "stop",
6580
6592
  "stopFailure",
6581
6593
  "subagentStart",
@@ -6583,7 +6595,9 @@ const QWENCODE_HOOK_EVENTS = [
6583
6595
  "preCompact",
6584
6596
  "postCompact",
6585
6597
  "permissionRequest",
6598
+ "permissionDenied",
6586
6599
  "notification",
6600
+ "instructionsLoaded",
6587
6601
  "todoCreated",
6588
6602
  "todoCompleted"
6589
6603
  ];
@@ -6593,11 +6607,11 @@ const QWENCODE_HOOK_EVENTS = [
6593
6607
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6594
6608
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6595
6609
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6596
- * `SubagentStop`, `Notification`, `PreCompact`). The eight events with a clean
6597
- * canonical equivalent are mapped: `PreToolUse`, `PostToolUse`,
6610
+ * `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
6611
+ * equivalent and are mapped: `PreToolUse`, `PostToolUse`,
6598
6612
  * `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
6599
- * `SessionEnd`, `SubagentStop`, and `PostLLMCall` ← `postModelInvocation`.
6600
- * (`Notification` and `PreCompact` have no canonical event and are left out.)
6613
+ * `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
6614
+ * `Notification` ← `notification`, and `PreCompact` `preCompact`.
6601
6615
  * `match` (Reasonix's matcher field name) is honored only on
6602
6616
  * `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
6603
6617
  * tool-event scoping used by other adapters.
@@ -6611,7 +6625,39 @@ const REASONIX_HOOK_EVENTS = [
6611
6625
  "sessionStart",
6612
6626
  "sessionEnd",
6613
6627
  "subagentStop",
6614
- "postModelInvocation"
6628
+ "postModelInvocation",
6629
+ "notification",
6630
+ "preCompact"
6631
+ ];
6632
+ /**
6633
+ * Hook events supported by Grok CLI (xAI Grok Build).
6634
+ *
6635
+ * Grok Build documents a Claude-Code-compatible hooks surface with fourteen
6636
+ * PascalCase events, all of which map 1:1 onto an existing canonical arm:
6637
+ * `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
6638
+ * `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
6639
+ * `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
6640
+ * `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
6641
+ * name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
6642
+ * `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
6643
+ * the remaining lifecycle events are matcher-less.
6644
+ * @see https://docs.x.ai/build/features/hooks
6645
+ */
6646
+ const GROKCLI_HOOK_EVENTS = [
6647
+ "sessionStart",
6648
+ "sessionEnd",
6649
+ "beforeSubmitPrompt",
6650
+ "preToolUse",
6651
+ "postToolUse",
6652
+ "postToolUseFailure",
6653
+ "permissionDenied",
6654
+ "stop",
6655
+ "stopFailure",
6656
+ "notification",
6657
+ "subagentStart",
6658
+ "subagentStop",
6659
+ "preCompact",
6660
+ "postCompact"
6615
6661
  ];
6616
6662
  /**
6617
6663
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6685,6 +6731,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
6685
6731
  junie: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6686
6732
  vibe: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6687
6733
  reasonix: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6734
+ grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6688
6735
  qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
6689
6736
  hooks: zod_mini.z.optional(hooksRecordSchema),
6690
6737
  disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
@@ -6743,7 +6790,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
6743
6790
  postToolUse: "PostToolUse",
6744
6791
  beforeSubmitPrompt: "UserPromptSubmit",
6745
6792
  stop: "Stop",
6746
- permissionRequest: "PermissionRequest"
6793
+ permissionRequest: "PermissionRequest",
6794
+ postCompact: "PostCompaction"
6747
6795
  };
6748
6796
  /**
6749
6797
  * Map Devin Local PascalCase event names to canonical camelCase.
@@ -6758,7 +6806,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
6758
6806
  postToolUse: "PostToolUse",
6759
6807
  sessionStart: "SessionStart",
6760
6808
  sessionEnd: "SessionEnd",
6761
- stop: "Stop"
6809
+ stop: "Stop",
6810
+ notification: "Notification"
6762
6811
  };
6763
6812
  /**
6764
6813
  * Map AugmentCode PascalCase event names to canonical camelCase.
@@ -7028,7 +7077,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
7028
7077
  preToolUse: "PreToolUse",
7029
7078
  postToolUse: "PostToolUse",
7030
7079
  postToolUseFailure: "PostToolUseFailure",
7080
+ postToolBatch: "PostToolBatch",
7031
7081
  beforeSubmitPrompt: "UserPromptSubmit",
7082
+ userPromptExpansion: "UserPromptExpansion",
7032
7083
  stop: "Stop",
7033
7084
  subagentStart: "SubagentStart",
7034
7085
  subagentStop: "SubagentStop",
@@ -7036,7 +7087,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
7036
7087
  preCompact: "PreCompact",
7037
7088
  postCompact: "PostCompact",
7038
7089
  permissionRequest: "PermissionRequest",
7090
+ permissionDenied: "PermissionDenied",
7039
7091
  notification: "Notification",
7092
+ instructionsLoaded: "InstructionsLoaded",
7040
7093
  todoCreated: "TodoCreated",
7041
7094
  todoCompleted: "TodoCompleted"
7042
7095
  };
@@ -7058,12 +7111,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
7058
7111
  sessionStart: "SessionStart",
7059
7112
  sessionEnd: "SessionEnd",
7060
7113
  subagentStop: "SubagentStop",
7061
- postModelInvocation: "PostLLMCall"
7114
+ postModelInvocation: "PostLLMCall",
7115
+ notification: "Notification",
7116
+ preCompact: "PreCompact"
7062
7117
  };
7063
7118
  /**
7064
7119
  * Map Reasonix PascalCase event names to canonical camelCase.
7065
7120
  */
7066
7121
  const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
7122
+ /**
7123
+ * Map canonical camelCase event names to Grok CLI PascalCase.
7124
+ * Grok Build reuses the same Claude-style PascalCase event names, so each
7125
+ * canonical arm maps to its PascalCase equivalent.
7126
+ * @see https://docs.x.ai/build/features/hooks
7127
+ */
7128
+ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
7129
+ sessionStart: "SessionStart",
7130
+ sessionEnd: "SessionEnd",
7131
+ beforeSubmitPrompt: "UserPromptSubmit",
7132
+ preToolUse: "PreToolUse",
7133
+ postToolUse: "PostToolUse",
7134
+ postToolUseFailure: "PostToolUseFailure",
7135
+ permissionDenied: "PermissionDenied",
7136
+ stop: "Stop",
7137
+ stopFailure: "StopFailure",
7138
+ notification: "Notification",
7139
+ subagentStart: "SubagentStart",
7140
+ subagentStop: "SubagentStop",
7141
+ preCompact: "PreCompact",
7142
+ postCompact: "PostCompact"
7143
+ };
7144
+ /**
7145
+ * Map Grok CLI PascalCase event names to canonical camelCase.
7146
+ */
7147
+ const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
7067
7148
  //#endregion
7068
7149
  //#region src/features/hooks/tool-hooks-converter.ts
7069
7150
  function isToolMatcherEntry(x) {
@@ -7588,7 +7669,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
7588
7669
  noMatcherEvents: /* @__PURE__ */ new Set([
7589
7670
  "sessionStart",
7590
7671
  "sessionEnd",
7591
- "stop"
7672
+ "stop",
7673
+ "notification"
7592
7674
  ])
7593
7675
  };
7594
7676
  /**
@@ -8687,7 +8769,8 @@ const DEVIN_CONVERTER_CONFIG = {
8687
8769
  "sessionStart",
8688
8770
  "sessionEnd",
8689
8771
  "stop",
8690
- "beforeSubmitPrompt"
8772
+ "beforeSubmitPrompt",
8773
+ "postCompact"
8691
8774
  ])
8692
8775
  };
8693
8776
  /**
@@ -8996,6 +9079,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
8996
9079
  }
8997
9080
  };
8998
9081
  //#endregion
9082
+ //#region src/constants/grokcli-paths.ts
9083
+ /**
9084
+ * Grok Build CLI (xAI) configuration-layout conventions.
9085
+ *
9086
+ * Single source of truth for where Grok Build expects its files. Grok Build
9087
+ * stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
9088
+ * with project/global scopes resolved by the directory the CLI runs in
9089
+ * (`./.grok/config.toml` vs `~/.grok/config.toml`).
9090
+ *
9091
+ * Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
9092
+ * `-s project` writes `./.grok/config.toml`, `-s user` writes
9093
+ * `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
9094
+ * @see https://docs.x.ai/build/overview
9095
+ */
9096
+ /** Root directory for Grok Build configuration, relative to the scope root. */
9097
+ const GROKCLI_DIR = ".grok";
9098
+ /** MCP servers and other settings live in `config.toml` under `.grok/`. */
9099
+ const GROKCLI_MCP_FILE_NAME = "config.toml";
9100
+ /**
9101
+ * Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
9102
+ * permission mode, and other settings all live here; permissions reuse the same
9103
+ * file name as MCP since Grok consolidates everything into one config.
9104
+ */
9105
+ const GROKCLI_CONFIG_FILE_NAME = "config.toml";
9106
+ /** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
9107
+ const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
9108
+ /**
9109
+ * Hooks directory under `.grok/`. Grok Build discovers hook config files from
9110
+ * `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
9111
+ * standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
9112
+ * shape. rulesync writes all its hooks into a single `rulesync.json`.
9113
+ * @see https://docs.x.ai/build/features/hooks
9114
+ */
9115
+ const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
9116
+ /** rulesync-managed Grok hooks file under `.grok/hooks/`. */
9117
+ const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
9118
+ /**
9119
+ * Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
9120
+ * agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
9121
+ * (global), each a Markdown file with YAML frontmatter (verified via
9122
+ * `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
9123
+ */
9124
+ const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
9125
+ /**
9126
+ * Instruction file. Grok reads the AGENTS.md instruction-file family natively,
9127
+ * including the user-level `~/.grok/AGENTS.md` for global rules (verified via
9128
+ * `grok inspect`, consistent with the `.grok/` global discovery used by the
9129
+ * MCP/skills/subagents adapters).
9130
+ */
9131
+ const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
9132
+ //#endregion
9133
+ //#region src/features/hooks/grokcli-hooks.ts
9134
+ const GROKCLI_CONVERTER_CONFIG = {
9135
+ supportedEvents: GROKCLI_HOOK_EVENTS,
9136
+ canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
9137
+ toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
9138
+ projectDirVar: "",
9139
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
9140
+ noMatcherEvents: /* @__PURE__ */ new Set([
9141
+ "sessionStart",
9142
+ "sessionEnd",
9143
+ "beforeSubmitPrompt",
9144
+ "stop",
9145
+ "stopFailure",
9146
+ "notification",
9147
+ "subagentStart",
9148
+ "subagentStop",
9149
+ "preCompact",
9150
+ "postCompact"
9151
+ ])
9152
+ };
9153
+ /**
9154
+ * Hooks generator for Grok CLI (xAI Grok Build).
9155
+ *
9156
+ * Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
9157
+ * maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
9158
+ * matcher groups under a top-level `hooks` key. rulesync writes all its hooks
9159
+ * into a single standalone `rulesync.json` file discovered from
9160
+ * `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
9161
+ * is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
9162
+ * as an orphan.
9163
+ *
9164
+ * @see https://docs.x.ai/build/features/hooks
9165
+ */
9166
+ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
9167
+ constructor(params) {
9168
+ super({
9169
+ ...params,
9170
+ fileContent: params.fileContent ?? "{}"
9171
+ });
9172
+ }
9173
+ static getSettablePaths(_options = {}) {
9174
+ return {
9175
+ relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
9176
+ relativeFilePath: GROKCLI_HOOKS_FILE_NAME
9177
+ };
9178
+ }
9179
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
9180
+ const paths = GrokcliHooks.getSettablePaths({ global });
9181
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
9182
+ return new GrokcliHooks({
9183
+ outputRoot,
9184
+ relativeDirPath: paths.relativeDirPath,
9185
+ relativeFilePath: paths.relativeFilePath,
9186
+ fileContent,
9187
+ validate
9188
+ });
9189
+ }
9190
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
9191
+ const paths = GrokcliHooks.getSettablePaths({ global });
9192
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
9193
+ const config = rulesyncHooks.getJson();
9194
+ const grokHooks = canonicalToToolHooks({
9195
+ config,
9196
+ toolOverrideHooks: config.grokcli?.hooks,
9197
+ converterConfig: GROKCLI_CONVERTER_CONFIG,
9198
+ logger
9199
+ });
9200
+ await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
9201
+ const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
9202
+ return new GrokcliHooks({
9203
+ outputRoot,
9204
+ relativeDirPath: paths.relativeDirPath,
9205
+ relativeFilePath: paths.relativeFilePath,
9206
+ fileContent,
9207
+ validate
9208
+ });
9209
+ }
9210
+ toRulesyncHooks() {
9211
+ let parsed;
9212
+ try {
9213
+ parsed = JSON.parse(this.getFileContent());
9214
+ } catch (error) {
9215
+ throw new Error(`Failed to parse Grok hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
9216
+ }
9217
+ const hooks = toolHooksToCanonical({
9218
+ hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
9219
+ converterConfig: GROKCLI_CONVERTER_CONFIG
9220
+ });
9221
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9222
+ version: 1,
9223
+ hooks
9224
+ }, null, 2) });
9225
+ }
9226
+ validate() {
9227
+ return {
9228
+ success: true,
9229
+ error: null
9230
+ };
9231
+ }
9232
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
9233
+ return new GrokcliHooks({
9234
+ outputRoot,
9235
+ relativeDirPath,
9236
+ relativeFilePath,
9237
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
9238
+ validate: false
9239
+ });
9240
+ }
9241
+ };
9242
+ //#endregion
8999
9243
  //#region src/features/hooks/hermesagent-hooks.ts
9000
9244
  /**
9001
9245
  * Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
@@ -10202,9 +10446,9 @@ function reasonixHooksToCanonical(hooks) {
10202
10446
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
10203
10447
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
10204
10448
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
10205
- * The eight upstream events with a clean canonical equivalent are mapped:
10449
+ * All ten upstream events have a clean canonical equivalent and are mapped:
10206
10450
  * PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
10207
- * SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
10451
+ * SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
10208
10452
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
10209
10453
  */
10210
10454
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -10792,6 +11036,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
10792
11036
  supportedEvents: REASONIX_HOOK_EVENTS,
10793
11037
  supportedHookTypes: ["command"],
10794
11038
  supportsMatcher: true
11039
+ }],
11040
+ ["grokcli", {
11041
+ class: GrokcliHooks,
11042
+ meta: {
11043
+ supportsProject: true,
11044
+ supportsGlobal: true,
11045
+ supportsImport: true
11046
+ },
11047
+ supportedEvents: GROKCLI_HOOK_EVENTS,
11048
+ supportedHookTypes: ["command"],
11049
+ supportsMatcher: true
10795
11050
  }]
10796
11051
  ]);
10797
11052
  const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
@@ -13047,7 +13302,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13047
13302
  ...rest,
13048
13303
  validate: false
13049
13304
  });
13050
- this.toml = smol_toml.parse(this.fileContent);
13305
+ let toml;
13306
+ try {
13307
+ toml = smol_toml.parse(this.fileContent);
13308
+ } catch (error) {
13309
+ throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
13310
+ }
13311
+ this.toml = toml;
13051
13312
  if (rest.validate) {
13052
13313
  const result = this.validate();
13053
13314
  if (!result.success) throw result.error;
@@ -13083,7 +13344,12 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13083
13344
  const paths = this.getSettablePaths({ global });
13084
13345
  const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
13085
13346
  const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
13086
- const configToml = smol_toml.parse(configTomlFileContent || smol_toml.stringify({}));
13347
+ let configToml;
13348
+ try {
13349
+ configToml = smol_toml.parse(configTomlFileContent || smol_toml.stringify({}));
13350
+ } catch (error) {
13351
+ throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
13352
+ }
13087
13353
  const strippedMcpServers = rulesyncMcp.getMcpServers();
13088
13354
  const rawMcpServers = rulesyncMcp.getJson().mcpServers;
13089
13355
  const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
@@ -14057,47 +14323,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
14057
14323
  }
14058
14324
  };
14059
14325
  //#endregion
14060
- //#region src/constants/grokcli-paths.ts
14061
- /**
14062
- * Grok Build CLI (xAI) configuration-layout conventions.
14063
- *
14064
- * Single source of truth for where Grok Build expects its files. Grok Build
14065
- * stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
14066
- * with project/global scopes resolved by the directory the CLI runs in
14067
- * (`./.grok/config.toml` vs `~/.grok/config.toml`).
14068
- *
14069
- * Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
14070
- * `-s project` writes `./.grok/config.toml`, `-s user` writes
14071
- * `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
14072
- * @see https://docs.x.ai/build/overview
14073
- */
14074
- /** Root directory for Grok Build configuration, relative to the scope root. */
14075
- const GROKCLI_DIR = ".grok";
14076
- /** MCP servers and other settings live in `config.toml` under `.grok/`. */
14077
- const GROKCLI_MCP_FILE_NAME = "config.toml";
14078
- /**
14079
- * Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
14080
- * permission mode, and other settings all live here; permissions reuse the same
14081
- * file name as MCP since Grok consolidates everything into one config.
14082
- */
14083
- const GROKCLI_CONFIG_FILE_NAME = "config.toml";
14084
- /** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
14085
- const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
14086
- /**
14087
- * Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
14088
- * agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
14089
- * (global), each a Markdown file with YAML frontmatter (verified via
14090
- * `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
14091
- */
14092
- const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
14093
- /**
14094
- * Instruction file. Grok reads the AGENTS.md instruction-file family natively,
14095
- * including the user-level `~/.grok/AGENTS.md` for global rules (verified via
14096
- * `grok inspect`, consistent with the `.grok/` global discovery used by the
14097
- * MCP/skills/subagents adapters).
14098
- */
14099
- const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
14100
- //#endregion
14101
14326
  //#region src/features/mcp/grokcli-mcp.ts
14102
14327
  const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
14103
14328
  /**
@@ -14257,6 +14482,54 @@ function resolveHermesTimeout(config) {
14257
14482
  if (typeof config.networkTimeout === "number") return config.networkTimeout;
14258
14483
  }
14259
14484
  /**
14485
+ * Copies the advanced Hermes-recognized per-server fields that have no canonical
14486
+ * alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
14487
+ * path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
14488
+ * `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
14489
+ * from `source` to `target`. Field names are identical on both sides (the
14490
+ * canonical `McpServerSchema` is a `looseObject`), so this serves export and
14491
+ * import alike. See the Hermes mcp-config-reference.
14492
+ */
14493
+ function copyHermesAdvancedFields(source, target) {
14494
+ if (typeof source.auth === "string") target.auth = source.auth;
14495
+ if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
14496
+ if (typeof source.client_key === "string") target.client_key = source.client_key;
14497
+ if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
14498
+ if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
14499
+ }
14500
+ /**
14501
+ * Builds Hermes's per-server `tools` block from a canonical server config. The
14502
+ * canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
14503
+ * and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
14504
+ * `prompts`/`resources` capability flags. Returns an empty object when the
14505
+ * server has no tool scoping (the caller omits the block in that case).
14506
+ *
14507
+ * Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
14508
+ * than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
14509
+ * is reserved as a `string[]` (used by other tools) — reusing it for an object
14510
+ * would fail validation on the next `generate`.
14511
+ */
14512
+ function buildHermesToolsBlock(config) {
14513
+ const tools = {};
14514
+ if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14515
+ if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
14516
+ if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
14517
+ if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
14518
+ return tools;
14519
+ }
14520
+ /**
14521
+ * Applies a Hermes per-server `tools` block back onto a canonical server config
14522
+ * (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
14523
+ * `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
14524
+ * `promptsEnabled`/`resourcesEnabled` top-level toggles.
14525
+ */
14526
+ function applyHermesToolsBlock(hermesTools, server) {
14527
+ if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
14528
+ if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
14529
+ if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
14530
+ if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
14531
+ }
14532
+ /**
14260
14533
  * Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
14261
14534
  *
14262
14535
  * Hermes is close to the MCP spec but not identical: `command` must be a single
@@ -14290,9 +14563,8 @@ function convertServerToHermes(config) {
14290
14563
  if (config.disabled === true) out.enabled = false;
14291
14564
  const timeout = resolveHermesTimeout(config);
14292
14565
  if (timeout !== void 0) out.timeout = timeout;
14293
- const tools = {};
14294
- if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14295
- if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
14566
+ copyHermesAdvancedFields(config, out);
14567
+ const tools = buildHermesToolsBlock(config);
14296
14568
  if (Object.keys(tools).length > 0) out.tools = tools;
14297
14569
  return out;
14298
14570
  }
@@ -14335,10 +14607,8 @@ function convertFromHermesFormat(mcpServers) {
14335
14607
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
14336
14608
  if (config.enabled === false) server.disabled = true;
14337
14609
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
14338
- if (isRecord(config.tools)) {
14339
- if (isStringArray(config.tools.include)) server.enabledTools = config.tools.include;
14340
- if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
14341
- }
14610
+ copyHermesAdvancedFields(config, server);
14611
+ if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
14342
14612
  result[name] = server;
14343
14613
  }
14344
14614
  return result;
@@ -16633,18 +16903,27 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
16633
16903
  * Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
16634
16904
  * autonomy/sandbox controls with no canonical permission category — under
16635
16905
  * `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
16636
- * `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). Fields
16637
- * placed here are merged into the matching `settings.json` group and emitted
16638
- * only for Qwen, while the shared `permission` block continues to drive the
16639
- * `permissions.allow`/`ask`/`deny` arrays. Kept `looseObject` (verbatim
16640
- * passthrough) so any current or future `tools`/`security` key can be authored.
16906
+ * `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
16907
+ * exposes `permissions.autoMode` (the Auto Mode classifier config:
16908
+ * `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` see
16909
+ * https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
16910
+ * likewise has no canonical category. Fields placed here are merged into the
16911
+ * matching `settings.json` group and emitted only for Qwen, while the shared
16912
+ * `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
16913
+ * arrays. Kept `looseObject` (verbatim passthrough) so any current or future
16914
+ * `tools`/`security`/`autoMode` key can be authored.
16641
16915
  *
16642
16916
  * @example
16643
- * { "tools": { "approvalMode": "auto-edit" }, "security": { "folderTrust": { "enabled": true } } }
16917
+ * {
16918
+ * "tools": { "approvalMode": "auto-edit" },
16919
+ * "security": { "folderTrust": { "enabled": true } },
16920
+ * "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
16921
+ * }
16644
16922
  */
16645
16923
  const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
16646
16924
  tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
16647
- security: zod_mini.z.optional(zod_mini.z.looseObject({}))
16925
+ security: zod_mini.z.optional(zod_mini.z.looseObject({})),
16926
+ autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
16648
16927
  });
16649
16928
  /**
16650
16929
  * Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
@@ -16671,9 +16950,11 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
16671
16950
  * exposes security controls with no canonical per-command allow/ask/deny slot —
16672
16951
  * `commandBlocklist` (a hard-block tier that can never be approved, distinct from
16673
16952
  * an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
16674
- * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`, `enableDroidShield`,
16953
+ * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
16954
+ * `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
16675
16955
  * and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
16676
- * `interactionMode`). Fields placed here are merged into `settings.json` and
16956
+ * `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
16957
+ * into `settings.json` and
16677
16958
  * emitted only for Factory Droid, while the shared `permission` block continues
16678
16959
  * to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
16679
16960
  *
@@ -16866,6 +17147,41 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
16866
17147
  }))
16867
17148
  })) });
16868
17149
  /**
17150
+ * Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
17151
+ * `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
17152
+ * Codex still accepts, so it is included so existing configs round-trip. The
17153
+ * granular table form (`{ granular = { … } }`) is modeled separately in the
17154
+ * override union.
17155
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17156
+ */
17157
+ const CodexApprovalPolicySchema = zod_mini.z.enum([
17158
+ "untrusted",
17159
+ "on-request",
17160
+ "on-failure",
17161
+ "never"
17162
+ ]);
17163
+ /**
17164
+ * Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
17165
+ * `.codex/config.toml`.
17166
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17167
+ */
17168
+ const CodexSandboxModeSchema = zod_mini.z.enum([
17169
+ "read-only",
17170
+ "workspace-write",
17171
+ "danger-full-access"
17172
+ ]);
17173
+ /**
17174
+ * Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
17175
+ * value Codex still accepts for backward compatibility, so it is included so
17176
+ * existing configs round-trip through the rulesync model.
17177
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17178
+ */
17179
+ const CodexApprovalsReviewerSchema = zod_mini.z.enum([
17180
+ "user",
17181
+ "auto_review",
17182
+ "guardian_subagent"
17183
+ ]);
17184
+ /**
16869
17185
  * Codex CLI-scoped permission override.
16870
17186
  *
16871
17187
  * Codex CLI's permission surface is richer than the canonical allow/ask/deny
@@ -16874,15 +17190,16 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
16874
17190
  * override whose fields are written verbatim as top-level `.codex/config.toml`
16875
17191
  * keys (the override wins per key; existing sibling keys the user set directly
16876
17192
  * are preserved):
16877
- * - `approval_policy` — `untrusted` | `on-request` | `never`, or a
16878
- * `{ granular = { … } }` table (kept verbatim; the granular schema has
16879
- * required fields that are brittle to model as typed keys).
17193
+ * - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
17194
+ * `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
17195
+ * schema has required fields that are brittle to model as typed keys).
16880
17196
  * - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
16881
17197
  * with the sibling `sandbox_workspace_write` table (`network_access`,
16882
17198
  * `writable_roots`, …).
16883
17199
  * - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
16884
17200
  * `.enabled`, `apps.<id>.default_tools_approval_mode`).
16885
- * - `approvals_reviewer` — the reviewer-approval surface.
17201
+ * - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
17202
+ * | `guardian_subagent`), or a table for the richer reviewer config.
16886
17203
  *
16887
17204
  * Two surfaces are deliberately NOT authorable here so the override can never
16888
17205
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -16900,11 +17217,11 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
16900
17217
  * "sandbox_workspace_write": { "network_access": true } }
16901
17218
  */
16902
17219
  const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
16903
- approval_policy: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
16904
- sandbox_mode: zod_mini.z.optional(zod_mini.z.string()),
17220
+ approval_policy: zod_mini.z.optional(zod_mini.z.union([CodexApprovalPolicySchema, zod_mini.z.looseObject({})])),
17221
+ sandbox_mode: zod_mini.z.optional(CodexSandboxModeSchema),
16905
17222
  sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
16906
17223
  apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
16907
- approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})]))
17224
+ approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})]))
16908
17225
  });
16909
17226
  /**
16910
17227
  * Permissions configuration.
@@ -19765,9 +20082,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
19765
20082
  "networkPolicy",
19766
20083
  "sandbox",
19767
20084
  "mcpPolicy",
20085
+ "mcpAutonomyOverrides",
19768
20086
  "enableDroidShield",
19769
20087
  "sessionDefaultSettings",
19770
20088
  "maxAutonomyLevel",
20089
+ "subagentAutonomyLevel",
19771
20090
  "interactionMode"
19772
20091
  ];
19773
20092
  /**
@@ -20125,7 +20444,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
20125
20444
  }
20126
20445
  //#endregion
20127
20446
  //#region src/features/permissions/grokcli-permissions.ts
20128
- const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
20129
20447
  const GROKCLI_UI_KEY = "ui";
20130
20448
  const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
20131
20449
  const GROKCLI_PERMISSION_KEY = "permission";
@@ -20227,13 +20545,19 @@ function parseGrokEntry(entry) {
20227
20545
  * compatibility with older Grok versions: `always-approve` when the config is
20228
20546
  * pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
20229
20547
  * while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
20230
- * arrays).
20231
- *
20232
- * This surface is **global only** — the adapter syncs the user-level
20233
- * `~/.grok/config.toml`. (Grok also supports a project-scoped `[permission]`
20234
- * file; project scope is not modeled here.) The shared config is merged in
20548
+ * arrays). It is a user-level UI setting, so it is only written in global scope
20549
+ * (see below).
20550
+ *
20551
+ * Both scopes are supported: the adapter syncs the project-level
20552
+ * `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
20553
+ * (with `--global`). Grok documents that "Project configs are limited to MCP
20554
+ * servers, plugins, and permission rules, not full user configs"
20555
+ * (https://docs.x.ai/build/settings), so the project config carries only the
20556
+ * fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
20557
+ * is written in global scope only. The shared config is merged in
20235
20558
  * place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
20236
- * tools it models and the `[ui] permission_mode` value, while every other key
20559
+ * tools it models and (in global scope) the `[ui] permission_mode` value, while
20560
+ * every other key
20237
20561
  * (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
20238
20562
  * entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
20239
20563
  * file is never deleted.
@@ -20255,7 +20579,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20255
20579
  };
20256
20580
  }
20257
20581
  static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
20258
- if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
20259
20582
  const paths = GrokcliPermissions.getSettablePaths({ global });
20260
20583
  const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
20261
20584
  return new GrokcliPermissions({
@@ -20264,11 +20587,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20264
20587
  relativeFilePath: paths.relativeFilePath,
20265
20588
  fileContent,
20266
20589
  validate,
20267
- global: true
20590
+ global
20268
20591
  });
20269
20592
  }
20270
20593
  static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
20271
- if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
20272
20594
  const paths = GrokcliPermissions.getSettablePaths({ global });
20273
20595
  const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20274
20596
  const existingContent = await readFileContentOrNull(filePath) ?? "";
@@ -20287,11 +20609,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20287
20609
  deny: buckets.deny,
20288
20610
  ask: buckets.ask
20289
20611
  };
20290
- const mode = deriveGrokPermissionMode(config);
20291
- const ui = {
20612
+ const uiPatch = global ? { [GROKCLI_UI_KEY]: {
20292
20613
  ...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
20293
- [GROKCLI_PERMISSION_MODE_KEY]: mode
20294
- };
20614
+ [GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
20615
+ } } : {};
20295
20616
  return new GrokcliPermissions({
20296
20617
  outputRoot,
20297
20618
  relativeDirPath: paths.relativeDirPath,
@@ -20302,12 +20623,12 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20302
20623
  existingContent,
20303
20624
  patch: {
20304
20625
  [GROKCLI_PERMISSION_KEY]: permission,
20305
- [GROKCLI_UI_KEY]: ui
20626
+ ...uiPatch
20306
20627
  },
20307
20628
  filePath
20308
20629
  }),
20309
20630
  validate: true,
20310
- global: true
20631
+ global
20311
20632
  });
20312
20633
  }
20313
20634
  toRulesyncPermissions() {
@@ -20328,14 +20649,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20328
20649
  error: null
20329
20650
  };
20330
20651
  }
20331
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
20652
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
20332
20653
  return new GrokcliPermissions({
20333
20654
  outputRoot,
20334
20655
  relativeDirPath,
20335
20656
  relativeFilePath,
20336
20657
  fileContent: "",
20337
20658
  validate: false,
20338
- global: true
20659
+ global
20339
20660
  });
20340
20661
  }
20341
20662
  };
@@ -21250,6 +21571,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
21250
21571
  "notebookedit",
21251
21572
  "agent"
21252
21573
  ]);
21574
+ /**
21575
+ * Translate between rulesync's canonical permission category names and
21576
+ * OpenCode's native permission keys. OpenCode has no `agent` key — subagent
21577
+ * launches are gated by the `task` key (see the documented key list at
21578
+ * https://opencode.ai/docs/permissions/). Without this translation a canonical
21579
+ * `agent: deny` would be written verbatim into `opencode.json` and silently
21580
+ * ignored by OpenCode. Unknown names pass through unchanged.
21581
+ */
21582
+ const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
21583
+ const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
21584
+ function toOpencodePermissionKey(canonical) {
21585
+ return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
21586
+ }
21587
+ function toCanonicalPermissionKey(opencodeKey) {
21588
+ return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
21589
+ }
21253
21590
  function isSharedPermissionCategory(category) {
21254
21591
  return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
21255
21592
  }
@@ -21316,6 +21653,8 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21316
21653
  }
21317
21654
  const rulesyncJson = rulesyncPermissions.getJson();
21318
21655
  const overridePermission = rulesyncJson.opencode?.permission ?? {};
21656
+ const sharedPermission = {};
21657
+ for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
21319
21658
  return new OpencodePermissions({
21320
21659
  outputRoot,
21321
21660
  relativeDirPath: basePaths.relativeDirPath,
@@ -21325,7 +21664,7 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21325
21664
  feature: "permissions",
21326
21665
  existingContent: fileContent ?? "",
21327
21666
  patch: { permission: {
21328
- ...rulesyncJson.permission,
21667
+ ...sharedPermission,
21329
21668
  ...overridePermission
21330
21669
  } },
21331
21670
  filePath: (0, node_path.join)(jsonDir, relativeFilePath)
@@ -21341,8 +21680,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21341
21680
  }
21342
21681
  const shared = {};
21343
21682
  const overrideOnly = {};
21344
- for (const [category, value] of Object.entries(rawPermission)) if (isSharedPermissionCategory(category)) shared[category] = typeof value === "string" ? { "*": value } : value;
21345
- else overrideOnly[category] = value;
21683
+ for (const [category, value] of Object.entries(rawPermission)) {
21684
+ const canonicalCategory = toCanonicalPermissionKey(category);
21685
+ if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
21686
+ else overrideOnly[category] = value;
21687
+ }
21346
21688
  const json = Object.keys(overrideOnly).length > 0 ? {
21347
21689
  permission: shared,
21348
21690
  opencode: { permission: overrideOnly }
@@ -21467,6 +21809,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
21467
21809
  "disabled"
21468
21810
  ];
21469
21811
  const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
21812
+ const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
21470
21813
  function asPlainRecord(value) {
21471
21814
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
21472
21815
  }
@@ -21538,8 +21881,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
21538
21881
  else delete mergedPermissions.ask;
21539
21882
  if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
21540
21883
  else delete mergedPermissions.deny;
21541
- const patch = { permissions: mergedPermissions };
21542
21884
  const override = config.qwencode;
21885
+ if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
21886
+ const patch = { permissions: mergedPermissions };
21543
21887
  if (override?.tools !== void 0) patch.tools = {
21544
21888
  ...asPlainRecord(settings.tools),
21545
21889
  ...asPlainRecord(override.tools)
@@ -21581,9 +21925,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
21581
21925
  });
21582
21926
  const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
21583
21927
  const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
21928
+ const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
21584
21929
  const qwencodeOverride = {};
21585
21930
  if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
21586
21931
  if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
21932
+ if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
21587
21933
  const result = { ...config };
21588
21934
  if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
21589
21935
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
@@ -23092,7 +23438,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
23092
23438
  ["grokcli", {
23093
23439
  class: GrokcliPermissions,
23094
23440
  meta: {
23095
- supportsProject: false,
23441
+ supportsProject: true,
23096
23442
  supportsGlobal: true,
23097
23443
  supportsImport: true
23098
23444
  }