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.
@@ -322,7 +322,8 @@ const hooksProcessorToolTargetTuple = [
322
322
  "junie",
323
323
  "vibe",
324
324
  "qwencode",
325
- "reasonix"
325
+ "reasonix",
326
+ "grokcli"
326
327
  ];
327
328
  const permissionsProcessorToolTargetTuple = [
328
329
  "amp",
@@ -6269,10 +6270,13 @@ const CLAUDE_HOOK_EVENTS = [
6269
6270
  * Hook events supported by Devin Local (native `.devin/` hooks).
6270
6271
  *
6271
6272
  * Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
6272
- * seven events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
6273
- * `UserPromptSubmit`, `Stop`, `SessionStart`, and `SessionEnd`. The
6273
+ * eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
6274
+ * `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
6275
+ * `PostCompaction` (fires after context compaction; added in the Devin CLI
6276
+ * stable changelog — https://docs.devin.ai/cli/changelog/stable). The
6274
6277
  * tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
6275
- * a `matcher` (regex against `tool_name`); the session/turn events do not.
6278
+ * a `matcher` (regex against `tool_name`); the session/turn/compaction events
6279
+ * do not.
6276
6280
  *
6277
6281
  * Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
6278
6282
  * is the entire file) or under the `"hooks"` key of `.devin/config.json` /
@@ -6287,7 +6291,8 @@ const DEVIN_HOOK_EVENTS = [
6287
6291
  "postToolUse",
6288
6292
  "beforeSubmitPrompt",
6289
6293
  "stop",
6290
- "permissionRequest"
6294
+ "permissionRequest",
6295
+ "postCompact"
6291
6296
  ];
6292
6297
  /** Hook events supported by OpenCode. */
6293
6298
  const OPENCODE_HOOK_EVENTS = [
@@ -6481,7 +6486,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
6481
6486
  * Hook events supported by AugmentCode (Auggie CLI).
6482
6487
  * Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
6483
6488
  * PreToolUse / PostToolUse (tool events, matcher-aware) plus the
6484
- * SessionStart / SessionEnd / Stop session events (no matcher).
6489
+ * SessionStart / SessionEnd / Stop / Notification session events (no matcher).
6485
6490
  * @see https://docs.augmentcode.com/cli/hooks
6486
6491
  */
6487
6492
  const AUGMENTCODE_HOOK_EVENTS = [
@@ -6489,7 +6494,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
6489
6494
  "postToolUse",
6490
6495
  "sessionStart",
6491
6496
  "sessionEnd",
6492
- "stop"
6497
+ "stop",
6498
+ "notification"
6493
6499
  ];
6494
6500
  /**
6495
6501
  * Hook events supported by Mistral Vibe (mistral-vibe).
@@ -6541,6 +6547,10 @@ const JUNIE_HOOK_EVENTS = [
6541
6547
  * The Qwen-specific events
6542
6548
  * `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
6543
6549
  * `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
6550
+ * Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
6551
+ * `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
6552
+ * `InstructionsLoaded`, which map to the canonical `postToolBatch`,
6553
+ * `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
6544
6554
  * @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
6545
6555
  */
6546
6556
  const QWENCODE_HOOK_EVENTS = [
@@ -6549,7 +6559,9 @@ const QWENCODE_HOOK_EVENTS = [
6549
6559
  "preToolUse",
6550
6560
  "postToolUse",
6551
6561
  "postToolUseFailure",
6562
+ "postToolBatch",
6552
6563
  "beforeSubmitPrompt",
6564
+ "userPromptExpansion",
6553
6565
  "stop",
6554
6566
  "stopFailure",
6555
6567
  "subagentStart",
@@ -6557,7 +6569,9 @@ const QWENCODE_HOOK_EVENTS = [
6557
6569
  "preCompact",
6558
6570
  "postCompact",
6559
6571
  "permissionRequest",
6572
+ "permissionDenied",
6560
6573
  "notification",
6574
+ "instructionsLoaded",
6561
6575
  "todoCreated",
6562
6576
  "todoCompleted"
6563
6577
  ];
@@ -6567,11 +6581,11 @@ const QWENCODE_HOOK_EVENTS = [
6567
6581
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6568
6582
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6569
6583
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6570
- * `SubagentStop`, `Notification`, `PreCompact`). The eight events with a clean
6571
- * canonical equivalent are mapped: `PreToolUse`, `PostToolUse`,
6584
+ * `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
6585
+ * equivalent and are mapped: `PreToolUse`, `PostToolUse`,
6572
6586
  * `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
6573
- * `SessionEnd`, `SubagentStop`, and `PostLLMCall` ← `postModelInvocation`.
6574
- * (`Notification` and `PreCompact` have no canonical event and are left out.)
6587
+ * `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
6588
+ * `Notification` ← `notification`, and `PreCompact` `preCompact`.
6575
6589
  * `match` (Reasonix's matcher field name) is honored only on
6576
6590
  * `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
6577
6591
  * tool-event scoping used by other adapters.
@@ -6585,7 +6599,39 @@ const REASONIX_HOOK_EVENTS = [
6585
6599
  "sessionStart",
6586
6600
  "sessionEnd",
6587
6601
  "subagentStop",
6588
- "postModelInvocation"
6602
+ "postModelInvocation",
6603
+ "notification",
6604
+ "preCompact"
6605
+ ];
6606
+ /**
6607
+ * Hook events supported by Grok CLI (xAI Grok Build).
6608
+ *
6609
+ * Grok Build documents a Claude-Code-compatible hooks surface with fourteen
6610
+ * PascalCase events, all of which map 1:1 onto an existing canonical arm:
6611
+ * `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
6612
+ * `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
6613
+ * `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
6614
+ * `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
6615
+ * name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
6616
+ * `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
6617
+ * the remaining lifecycle events are matcher-less.
6618
+ * @see https://docs.x.ai/build/features/hooks
6619
+ */
6620
+ const GROKCLI_HOOK_EVENTS = [
6621
+ "sessionStart",
6622
+ "sessionEnd",
6623
+ "beforeSubmitPrompt",
6624
+ "preToolUse",
6625
+ "postToolUse",
6626
+ "postToolUseFailure",
6627
+ "permissionDenied",
6628
+ "stop",
6629
+ "stopFailure",
6630
+ "notification",
6631
+ "subagentStart",
6632
+ "subagentStop",
6633
+ "preCompact",
6634
+ "postCompact"
6589
6635
  ];
6590
6636
  /**
6591
6637
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6659,6 +6705,7 @@ const HooksConfigSchema = z.looseObject({
6659
6705
  junie: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6660
6706
  vibe: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6661
6707
  reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6708
+ grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6662
6709
  qwencode: z.optional(z.looseObject({
6663
6710
  hooks: z.optional(hooksRecordSchema),
6664
6711
  disableAllHooks: z.optional(z.boolean())
@@ -6717,7 +6764,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
6717
6764
  postToolUse: "PostToolUse",
6718
6765
  beforeSubmitPrompt: "UserPromptSubmit",
6719
6766
  stop: "Stop",
6720
- permissionRequest: "PermissionRequest"
6767
+ permissionRequest: "PermissionRequest",
6768
+ postCompact: "PostCompaction"
6721
6769
  };
6722
6770
  /**
6723
6771
  * Map Devin Local PascalCase event names to canonical camelCase.
@@ -6732,7 +6780,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
6732
6780
  postToolUse: "PostToolUse",
6733
6781
  sessionStart: "SessionStart",
6734
6782
  sessionEnd: "SessionEnd",
6735
- stop: "Stop"
6783
+ stop: "Stop",
6784
+ notification: "Notification"
6736
6785
  };
6737
6786
  /**
6738
6787
  * Map AugmentCode PascalCase event names to canonical camelCase.
@@ -7002,7 +7051,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
7002
7051
  preToolUse: "PreToolUse",
7003
7052
  postToolUse: "PostToolUse",
7004
7053
  postToolUseFailure: "PostToolUseFailure",
7054
+ postToolBatch: "PostToolBatch",
7005
7055
  beforeSubmitPrompt: "UserPromptSubmit",
7056
+ userPromptExpansion: "UserPromptExpansion",
7006
7057
  stop: "Stop",
7007
7058
  subagentStart: "SubagentStart",
7008
7059
  subagentStop: "SubagentStop",
@@ -7010,7 +7061,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
7010
7061
  preCompact: "PreCompact",
7011
7062
  postCompact: "PostCompact",
7012
7063
  permissionRequest: "PermissionRequest",
7064
+ permissionDenied: "PermissionDenied",
7013
7065
  notification: "Notification",
7066
+ instructionsLoaded: "InstructionsLoaded",
7014
7067
  todoCreated: "TodoCreated",
7015
7068
  todoCompleted: "TodoCompleted"
7016
7069
  };
@@ -7032,12 +7085,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
7032
7085
  sessionStart: "SessionStart",
7033
7086
  sessionEnd: "SessionEnd",
7034
7087
  subagentStop: "SubagentStop",
7035
- postModelInvocation: "PostLLMCall"
7088
+ postModelInvocation: "PostLLMCall",
7089
+ notification: "Notification",
7090
+ preCompact: "PreCompact"
7036
7091
  };
7037
7092
  /**
7038
7093
  * Map Reasonix PascalCase event names to canonical camelCase.
7039
7094
  */
7040
7095
  const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
7096
+ /**
7097
+ * Map canonical camelCase event names to Grok CLI PascalCase.
7098
+ * Grok Build reuses the same Claude-style PascalCase event names, so each
7099
+ * canonical arm maps to its PascalCase equivalent.
7100
+ * @see https://docs.x.ai/build/features/hooks
7101
+ */
7102
+ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
7103
+ sessionStart: "SessionStart",
7104
+ sessionEnd: "SessionEnd",
7105
+ beforeSubmitPrompt: "UserPromptSubmit",
7106
+ preToolUse: "PreToolUse",
7107
+ postToolUse: "PostToolUse",
7108
+ postToolUseFailure: "PostToolUseFailure",
7109
+ permissionDenied: "PermissionDenied",
7110
+ stop: "Stop",
7111
+ stopFailure: "StopFailure",
7112
+ notification: "Notification",
7113
+ subagentStart: "SubagentStart",
7114
+ subagentStop: "SubagentStop",
7115
+ preCompact: "PreCompact",
7116
+ postCompact: "PostCompact"
7117
+ };
7118
+ /**
7119
+ * Map Grok CLI PascalCase event names to canonical camelCase.
7120
+ */
7121
+ const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
7041
7122
  //#endregion
7042
7123
  //#region src/features/hooks/tool-hooks-converter.ts
7043
7124
  function isToolMatcherEntry(x) {
@@ -7562,7 +7643,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
7562
7643
  noMatcherEvents: /* @__PURE__ */ new Set([
7563
7644
  "sessionStart",
7564
7645
  "sessionEnd",
7565
- "stop"
7646
+ "stop",
7647
+ "notification"
7566
7648
  ])
7567
7649
  };
7568
7650
  /**
@@ -8661,7 +8743,8 @@ const DEVIN_CONVERTER_CONFIG = {
8661
8743
  "sessionStart",
8662
8744
  "sessionEnd",
8663
8745
  "stop",
8664
- "beforeSubmitPrompt"
8746
+ "beforeSubmitPrompt",
8747
+ "postCompact"
8665
8748
  ])
8666
8749
  };
8667
8750
  /**
@@ -8970,6 +9053,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
8970
9053
  }
8971
9054
  };
8972
9055
  //#endregion
9056
+ //#region src/constants/grokcli-paths.ts
9057
+ /**
9058
+ * Grok Build CLI (xAI) configuration-layout conventions.
9059
+ *
9060
+ * Single source of truth for where Grok Build expects its files. Grok Build
9061
+ * stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
9062
+ * with project/global scopes resolved by the directory the CLI runs in
9063
+ * (`./.grok/config.toml` vs `~/.grok/config.toml`).
9064
+ *
9065
+ * Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
9066
+ * `-s project` writes `./.grok/config.toml`, `-s user` writes
9067
+ * `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
9068
+ * @see https://docs.x.ai/build/overview
9069
+ */
9070
+ /** Root directory for Grok Build configuration, relative to the scope root. */
9071
+ const GROKCLI_DIR = ".grok";
9072
+ /** MCP servers and other settings live in `config.toml` under `.grok/`. */
9073
+ const GROKCLI_MCP_FILE_NAME = "config.toml";
9074
+ /**
9075
+ * Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
9076
+ * permission mode, and other settings all live here; permissions reuse the same
9077
+ * file name as MCP since Grok consolidates everything into one config.
9078
+ */
9079
+ const GROKCLI_CONFIG_FILE_NAME = "config.toml";
9080
+ /** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
9081
+ const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
9082
+ /**
9083
+ * Hooks directory under `.grok/`. Grok Build discovers hook config files from
9084
+ * `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
9085
+ * standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
9086
+ * shape. rulesync writes all its hooks into a single `rulesync.json`.
9087
+ * @see https://docs.x.ai/build/features/hooks
9088
+ */
9089
+ const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
9090
+ /** rulesync-managed Grok hooks file under `.grok/hooks/`. */
9091
+ const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
9092
+ /**
9093
+ * Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
9094
+ * agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
9095
+ * (global), each a Markdown file with YAML frontmatter (verified via
9096
+ * `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
9097
+ */
9098
+ const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
9099
+ /**
9100
+ * Instruction file. Grok reads the AGENTS.md instruction-file family natively,
9101
+ * including the user-level `~/.grok/AGENTS.md` for global rules (verified via
9102
+ * `grok inspect`, consistent with the `.grok/` global discovery used by the
9103
+ * MCP/skills/subagents adapters).
9104
+ */
9105
+ const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
9106
+ //#endregion
9107
+ //#region src/features/hooks/grokcli-hooks.ts
9108
+ const GROKCLI_CONVERTER_CONFIG = {
9109
+ supportedEvents: GROKCLI_HOOK_EVENTS,
9110
+ canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
9111
+ toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
9112
+ projectDirVar: "",
9113
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
9114
+ noMatcherEvents: /* @__PURE__ */ new Set([
9115
+ "sessionStart",
9116
+ "sessionEnd",
9117
+ "beforeSubmitPrompt",
9118
+ "stop",
9119
+ "stopFailure",
9120
+ "notification",
9121
+ "subagentStart",
9122
+ "subagentStop",
9123
+ "preCompact",
9124
+ "postCompact"
9125
+ ])
9126
+ };
9127
+ /**
9128
+ * Hooks generator for Grok CLI (xAI Grok Build).
9129
+ *
9130
+ * Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
9131
+ * maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
9132
+ * matcher groups under a top-level `hooks` key. rulesync writes all its hooks
9133
+ * into a single standalone `rulesync.json` file discovered from
9134
+ * `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
9135
+ * is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
9136
+ * as an orphan.
9137
+ *
9138
+ * @see https://docs.x.ai/build/features/hooks
9139
+ */
9140
+ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
9141
+ constructor(params) {
9142
+ super({
9143
+ ...params,
9144
+ fileContent: params.fileContent ?? "{}"
9145
+ });
9146
+ }
9147
+ static getSettablePaths(_options = {}) {
9148
+ return {
9149
+ relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
9150
+ relativeFilePath: GROKCLI_HOOKS_FILE_NAME
9151
+ };
9152
+ }
9153
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
9154
+ const paths = GrokcliHooks.getSettablePaths({ global });
9155
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
9156
+ return new GrokcliHooks({
9157
+ outputRoot,
9158
+ relativeDirPath: paths.relativeDirPath,
9159
+ relativeFilePath: paths.relativeFilePath,
9160
+ fileContent,
9161
+ validate
9162
+ });
9163
+ }
9164
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
9165
+ const paths = GrokcliHooks.getSettablePaths({ global });
9166
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
9167
+ const config = rulesyncHooks.getJson();
9168
+ const grokHooks = canonicalToToolHooks({
9169
+ config,
9170
+ toolOverrideHooks: config.grokcli?.hooks,
9171
+ converterConfig: GROKCLI_CONVERTER_CONFIG,
9172
+ logger
9173
+ });
9174
+ await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
9175
+ const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
9176
+ return new GrokcliHooks({
9177
+ outputRoot,
9178
+ relativeDirPath: paths.relativeDirPath,
9179
+ relativeFilePath: paths.relativeFilePath,
9180
+ fileContent,
9181
+ validate
9182
+ });
9183
+ }
9184
+ toRulesyncHooks() {
9185
+ let parsed;
9186
+ try {
9187
+ parsed = JSON.parse(this.getFileContent());
9188
+ } catch (error) {
9189
+ throw new Error(`Failed to parse Grok hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
9190
+ }
9191
+ const hooks = toolHooksToCanonical({
9192
+ hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
9193
+ converterConfig: GROKCLI_CONVERTER_CONFIG
9194
+ });
9195
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9196
+ version: 1,
9197
+ hooks
9198
+ }, null, 2) });
9199
+ }
9200
+ validate() {
9201
+ return {
9202
+ success: true,
9203
+ error: null
9204
+ };
9205
+ }
9206
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
9207
+ return new GrokcliHooks({
9208
+ outputRoot,
9209
+ relativeDirPath,
9210
+ relativeFilePath,
9211
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
9212
+ validate: false
9213
+ });
9214
+ }
9215
+ };
9216
+ //#endregion
8973
9217
  //#region src/features/hooks/hermesagent-hooks.ts
8974
9218
  /**
8975
9219
  * Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
@@ -10176,9 +10420,9 @@ function reasonixHooksToCanonical(hooks) {
10176
10420
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
10177
10421
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
10178
10422
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
10179
- * The eight upstream events with a clean canonical equivalent are mapped:
10423
+ * All ten upstream events have a clean canonical equivalent and are mapped:
10180
10424
  * PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
10181
- * SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
10425
+ * SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
10182
10426
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
10183
10427
  */
10184
10428
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -10766,6 +11010,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
10766
11010
  supportedEvents: REASONIX_HOOK_EVENTS,
10767
11011
  supportedHookTypes: ["command"],
10768
11012
  supportsMatcher: true
11013
+ }],
11014
+ ["grokcli", {
11015
+ class: GrokcliHooks,
11016
+ meta: {
11017
+ supportsProject: true,
11018
+ supportsGlobal: true,
11019
+ supportsImport: true
11020
+ },
11021
+ supportedEvents: GROKCLI_HOOK_EVENTS,
11022
+ supportedHookTypes: ["command"],
11023
+ supportsMatcher: true
10769
11024
  }]
10770
11025
  ]);
10771
11026
  const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
@@ -13021,7 +13276,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13021
13276
  ...rest,
13022
13277
  validate: false
13023
13278
  });
13024
- this.toml = smolToml.parse(this.fileContent);
13279
+ let toml;
13280
+ try {
13281
+ toml = smolToml.parse(this.fileContent);
13282
+ } catch (error) {
13283
+ throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
13284
+ }
13285
+ this.toml = toml;
13025
13286
  if (rest.validate) {
13026
13287
  const result = this.validate();
13027
13288
  if (!result.success) throw result.error;
@@ -13057,7 +13318,12 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13057
13318
  const paths = this.getSettablePaths({ global });
13058
13319
  const configTomlFilePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
13059
13320
  const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
13060
- const configToml = smolToml.parse(configTomlFileContent || smolToml.stringify({}));
13321
+ let configToml;
13322
+ try {
13323
+ configToml = smolToml.parse(configTomlFileContent || smolToml.stringify({}));
13324
+ } catch (error) {
13325
+ throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
13326
+ }
13061
13327
  const strippedMcpServers = rulesyncMcp.getMcpServers();
13062
13328
  const rawMcpServers = rulesyncMcp.getJson().mcpServers;
13063
13329
  const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
@@ -14031,47 +14297,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
14031
14297
  }
14032
14298
  };
14033
14299
  //#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
14300
  //#region src/features/mcp/grokcli-mcp.ts
14076
14301
  const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
14077
14302
  /**
@@ -14231,6 +14456,54 @@ function resolveHermesTimeout(config) {
14231
14456
  if (typeof config.networkTimeout === "number") return config.networkTimeout;
14232
14457
  }
14233
14458
  /**
14459
+ * Copies the advanced Hermes-recognized per-server fields that have no canonical
14460
+ * alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
14461
+ * path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
14462
+ * `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
14463
+ * from `source` to `target`. Field names are identical on both sides (the
14464
+ * canonical `McpServerSchema` is a `looseObject`), so this serves export and
14465
+ * import alike. See the Hermes mcp-config-reference.
14466
+ */
14467
+ function copyHermesAdvancedFields(source, target) {
14468
+ if (typeof source.auth === "string") target.auth = source.auth;
14469
+ if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
14470
+ if (typeof source.client_key === "string") target.client_key = source.client_key;
14471
+ if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
14472
+ if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
14473
+ }
14474
+ /**
14475
+ * Builds Hermes's per-server `tools` block from a canonical server config. The
14476
+ * canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
14477
+ * and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
14478
+ * `prompts`/`resources` capability flags. Returns an empty object when the
14479
+ * server has no tool scoping (the caller omits the block in that case).
14480
+ *
14481
+ * Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
14482
+ * than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
14483
+ * is reserved as a `string[]` (used by other tools) — reusing it for an object
14484
+ * would fail validation on the next `generate`.
14485
+ */
14486
+ function buildHermesToolsBlock(config) {
14487
+ const tools = {};
14488
+ if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14489
+ if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
14490
+ if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
14491
+ if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
14492
+ return tools;
14493
+ }
14494
+ /**
14495
+ * Applies a Hermes per-server `tools` block back onto a canonical server config
14496
+ * (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
14497
+ * `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
14498
+ * `promptsEnabled`/`resourcesEnabled` top-level toggles.
14499
+ */
14500
+ function applyHermesToolsBlock(hermesTools, server) {
14501
+ if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
14502
+ if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
14503
+ if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
14504
+ if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
14505
+ }
14506
+ /**
14234
14507
  * Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
14235
14508
  *
14236
14509
  * Hermes is close to the MCP spec but not identical: `command` must be a single
@@ -14264,9 +14537,8 @@ function convertServerToHermes(config) {
14264
14537
  if (config.disabled === true) out.enabled = false;
14265
14538
  const timeout = resolveHermesTimeout(config);
14266
14539
  if (timeout !== void 0) out.timeout = timeout;
14267
- const tools = {};
14268
- if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14269
- if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
14540
+ copyHermesAdvancedFields(config, out);
14541
+ const tools = buildHermesToolsBlock(config);
14270
14542
  if (Object.keys(tools).length > 0) out.tools = tools;
14271
14543
  return out;
14272
14544
  }
@@ -14309,10 +14581,8 @@ function convertFromHermesFormat(mcpServers) {
14309
14581
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
14310
14582
  if (config.enabled === false) server.disabled = true;
14311
14583
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
14312
- if (isRecord(config.tools)) {
14313
- if (isStringArray(config.tools.include)) server.enabledTools = config.tools.include;
14314
- if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
14315
- }
14584
+ copyHermesAdvancedFields(config, server);
14585
+ if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
14316
14586
  result[name] = server;
14317
14587
  }
14318
14588
  return result;
@@ -16607,18 +16877,27 @@ const CursorPermissionsOverrideSchema = z.looseObject({
16607
16877
  * Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
16608
16878
  * autonomy/sandbox controls with no canonical permission category — under
16609
16879
  * `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
16610
- * `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). Fields
16611
- * placed here are merged into the matching `settings.json` group and emitted
16612
- * only for Qwen, while the shared `permission` block continues to drive the
16613
- * `permissions.allow`/`ask`/`deny` arrays. Kept `looseObject` (verbatim
16614
- * passthrough) so any current or future `tools`/`security` key can be authored.
16880
+ * `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
16881
+ * exposes `permissions.autoMode` (the Auto Mode classifier config:
16882
+ * `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` see
16883
+ * https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
16884
+ * likewise has no canonical category. Fields placed here are merged into the
16885
+ * matching `settings.json` group and emitted only for Qwen, while the shared
16886
+ * `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
16887
+ * arrays. Kept `looseObject` (verbatim passthrough) so any current or future
16888
+ * `tools`/`security`/`autoMode` key can be authored.
16615
16889
  *
16616
16890
  * @example
16617
- * { "tools": { "approvalMode": "auto-edit" }, "security": { "folderTrust": { "enabled": true } } }
16891
+ * {
16892
+ * "tools": { "approvalMode": "auto-edit" },
16893
+ * "security": { "folderTrust": { "enabled": true } },
16894
+ * "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
16895
+ * }
16618
16896
  */
16619
16897
  const QwencodePermissionsOverrideSchema = z.looseObject({
16620
16898
  tools: z.optional(z.looseObject({})),
16621
- security: z.optional(z.looseObject({}))
16899
+ security: z.optional(z.looseObject({})),
16900
+ autoMode: z.optional(z.looseObject({}))
16622
16901
  });
16623
16902
  /**
16624
16903
  * Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
@@ -16645,9 +16924,11 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
16645
16924
  * exposes security controls with no canonical per-command allow/ask/deny slot —
16646
16925
  * `commandBlocklist` (a hard-block tier that can never be approved, distinct from
16647
16926
  * an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
16648
- * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`, `enableDroidShield`,
16927
+ * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
16928
+ * `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
16649
16929
  * and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
16650
- * `interactionMode`). Fields placed here are merged into `settings.json` and
16930
+ * `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
16931
+ * into `settings.json` and
16651
16932
  * emitted only for Factory Droid, while the shared `permission` block continues
16652
16933
  * to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
16653
16934
  *
@@ -16840,6 +17121,41 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
16840
17121
  }))
16841
17122
  })) });
16842
17123
  /**
17124
+ * Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
17125
+ * `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
17126
+ * Codex still accepts, so it is included so existing configs round-trip. The
17127
+ * granular table form (`{ granular = { … } }`) is modeled separately in the
17128
+ * override union.
17129
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17130
+ */
17131
+ const CodexApprovalPolicySchema = z.enum([
17132
+ "untrusted",
17133
+ "on-request",
17134
+ "on-failure",
17135
+ "never"
17136
+ ]);
17137
+ /**
17138
+ * Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
17139
+ * `.codex/config.toml`.
17140
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17141
+ */
17142
+ const CodexSandboxModeSchema = z.enum([
17143
+ "read-only",
17144
+ "workspace-write",
17145
+ "danger-full-access"
17146
+ ]);
17147
+ /**
17148
+ * Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
17149
+ * value Codex still accepts for backward compatibility, so it is included so
17150
+ * existing configs round-trip through the rulesync model.
17151
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
17152
+ */
17153
+ const CodexApprovalsReviewerSchema = z.enum([
17154
+ "user",
17155
+ "auto_review",
17156
+ "guardian_subagent"
17157
+ ]);
17158
+ /**
16843
17159
  * Codex CLI-scoped permission override.
16844
17160
  *
16845
17161
  * Codex CLI's permission surface is richer than the canonical allow/ask/deny
@@ -16848,15 +17164,16 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
16848
17164
  * override whose fields are written verbatim as top-level `.codex/config.toml`
16849
17165
  * keys (the override wins per key; existing sibling keys the user set directly
16850
17166
  * are preserved):
16851
- * - `approval_policy` — `untrusted` | `on-request` | `never`, or a
16852
- * `{ granular = { … } }` table (kept verbatim; the granular schema has
16853
- * required fields that are brittle to model as typed keys).
17167
+ * - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
17168
+ * `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
17169
+ * schema has required fields that are brittle to model as typed keys).
16854
17170
  * - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
16855
17171
  * with the sibling `sandbox_workspace_write` table (`network_access`,
16856
17172
  * `writable_roots`, …).
16857
17173
  * - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
16858
17174
  * `.enabled`, `apps.<id>.default_tools_approval_mode`).
16859
- * - `approvals_reviewer` — the reviewer-approval surface.
17175
+ * - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
17176
+ * | `guardian_subagent`), or a table for the richer reviewer config.
16860
17177
  *
16861
17178
  * Two surfaces are deliberately NOT authorable here so the override can never
16862
17179
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -16874,11 +17191,11 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
16874
17191
  * "sandbox_workspace_write": { "network_access": true } }
16875
17192
  */
16876
17193
  const CodexcliPermissionsOverrideSchema = z.looseObject({
16877
- approval_policy: z.optional(z.union([z.string(), z.looseObject({})])),
16878
- sandbox_mode: z.optional(z.string()),
17194
+ approval_policy: z.optional(z.union([CodexApprovalPolicySchema, z.looseObject({})])),
17195
+ sandbox_mode: z.optional(CodexSandboxModeSchema),
16879
17196
  sandbox_workspace_write: z.optional(z.looseObject({})),
16880
17197
  apps: z.optional(z.looseObject({})),
16881
- approvals_reviewer: z.optional(z.union([z.string(), z.looseObject({})]))
17198
+ approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})]))
16882
17199
  });
16883
17200
  /**
16884
17201
  * Permissions configuration.
@@ -19739,9 +20056,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
19739
20056
  "networkPolicy",
19740
20057
  "sandbox",
19741
20058
  "mcpPolicy",
20059
+ "mcpAutonomyOverrides",
19742
20060
  "enableDroidShield",
19743
20061
  "sessionDefaultSettings",
19744
20062
  "maxAutonomyLevel",
20063
+ "subagentAutonomyLevel",
19745
20064
  "interactionMode"
19746
20065
  ];
19747
20066
  /**
@@ -20099,7 +20418,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
20099
20418
  }
20100
20419
  //#endregion
20101
20420
  //#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
20421
  const GROKCLI_UI_KEY = "ui";
20104
20422
  const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
20105
20423
  const GROKCLI_PERMISSION_KEY = "permission";
@@ -20201,13 +20519,19 @@ function parseGrokEntry(entry) {
20201
20519
  * compatibility with older Grok versions: `always-approve` when the config is
20202
20520
  * pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
20203
20521
  * while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
20204
- * arrays).
20205
- *
20206
- * This surface is **global only** — the adapter syncs the user-level
20207
- * `~/.grok/config.toml`. (Grok also supports a project-scoped `[permission]`
20208
- * file; project scope is not modeled here.) The shared config is merged in
20522
+ * arrays). It is a user-level UI setting, so it is only written in global scope
20523
+ * (see below).
20524
+ *
20525
+ * Both scopes are supported: the adapter syncs the project-level
20526
+ * `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
20527
+ * (with `--global`). Grok documents that "Project configs are limited to MCP
20528
+ * servers, plugins, and permission rules, not full user configs"
20529
+ * (https://docs.x.ai/build/settings), so the project config carries only the
20530
+ * fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
20531
+ * is written in global scope only. The shared config is merged in
20209
20532
  * place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
20210
- * tools it models and the `[ui] permission_mode` value, while every other key
20533
+ * tools it models and (in global scope) the `[ui] permission_mode` value, while
20534
+ * every other key
20211
20535
  * (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
20212
20536
  * entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
20213
20537
  * file is never deleted.
@@ -20229,7 +20553,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20229
20553
  };
20230
20554
  }
20231
20555
  static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
20232
- if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
20233
20556
  const paths = GrokcliPermissions.getSettablePaths({ global });
20234
20557
  const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
20235
20558
  return new GrokcliPermissions({
@@ -20238,11 +20561,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20238
20561
  relativeFilePath: paths.relativeFilePath,
20239
20562
  fileContent,
20240
20563
  validate,
20241
- global: true
20564
+ global
20242
20565
  });
20243
20566
  }
20244
20567
  static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
20245
- if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
20246
20568
  const paths = GrokcliPermissions.getSettablePaths({ global });
20247
20569
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20248
20570
  const existingContent = await readFileContentOrNull(filePath) ?? "";
@@ -20261,11 +20583,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20261
20583
  deny: buckets.deny,
20262
20584
  ask: buckets.ask
20263
20585
  };
20264
- const mode = deriveGrokPermissionMode(config);
20265
- const ui = {
20586
+ const uiPatch = global ? { [GROKCLI_UI_KEY]: {
20266
20587
  ...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
20267
- [GROKCLI_PERMISSION_MODE_KEY]: mode
20268
- };
20588
+ [GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
20589
+ } } : {};
20269
20590
  return new GrokcliPermissions({
20270
20591
  outputRoot,
20271
20592
  relativeDirPath: paths.relativeDirPath,
@@ -20276,12 +20597,12 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20276
20597
  existingContent,
20277
20598
  patch: {
20278
20599
  [GROKCLI_PERMISSION_KEY]: permission,
20279
- [GROKCLI_UI_KEY]: ui
20600
+ ...uiPatch
20280
20601
  },
20281
20602
  filePath
20282
20603
  }),
20283
20604
  validate: true,
20284
- global: true
20605
+ global
20285
20606
  });
20286
20607
  }
20287
20608
  toRulesyncPermissions() {
@@ -20302,14 +20623,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
20302
20623
  error: null
20303
20624
  };
20304
20625
  }
20305
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
20626
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
20306
20627
  return new GrokcliPermissions({
20307
20628
  outputRoot,
20308
20629
  relativeDirPath,
20309
20630
  relativeFilePath,
20310
20631
  fileContent: "",
20311
20632
  validate: false,
20312
- global: true
20633
+ global
20313
20634
  });
20314
20635
  }
20315
20636
  };
@@ -21224,6 +21545,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
21224
21545
  "notebookedit",
21225
21546
  "agent"
21226
21547
  ]);
21548
+ /**
21549
+ * Translate between rulesync's canonical permission category names and
21550
+ * OpenCode's native permission keys. OpenCode has no `agent` key — subagent
21551
+ * launches are gated by the `task` key (see the documented key list at
21552
+ * https://opencode.ai/docs/permissions/). Without this translation a canonical
21553
+ * `agent: deny` would be written verbatim into `opencode.json` and silently
21554
+ * ignored by OpenCode. Unknown names pass through unchanged.
21555
+ */
21556
+ const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
21557
+ const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
21558
+ function toOpencodePermissionKey(canonical) {
21559
+ return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
21560
+ }
21561
+ function toCanonicalPermissionKey(opencodeKey) {
21562
+ return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
21563
+ }
21227
21564
  function isSharedPermissionCategory(category) {
21228
21565
  return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
21229
21566
  }
@@ -21290,6 +21627,8 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21290
21627
  }
21291
21628
  const rulesyncJson = rulesyncPermissions.getJson();
21292
21629
  const overridePermission = rulesyncJson.opencode?.permission ?? {};
21630
+ const sharedPermission = {};
21631
+ for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
21293
21632
  return new OpencodePermissions({
21294
21633
  outputRoot,
21295
21634
  relativeDirPath: basePaths.relativeDirPath,
@@ -21299,7 +21638,7 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21299
21638
  feature: "permissions",
21300
21639
  existingContent: fileContent ?? "",
21301
21640
  patch: { permission: {
21302
- ...rulesyncJson.permission,
21641
+ ...sharedPermission,
21303
21642
  ...overridePermission
21304
21643
  } },
21305
21644
  filePath: join(jsonDir, relativeFilePath)
@@ -21315,8 +21654,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
21315
21654
  }
21316
21655
  const shared = {};
21317
21656
  const overrideOnly = {};
21318
- for (const [category, value] of Object.entries(rawPermission)) if (isSharedPermissionCategory(category)) shared[category] = typeof value === "string" ? { "*": value } : value;
21319
- else overrideOnly[category] = value;
21657
+ for (const [category, value] of Object.entries(rawPermission)) {
21658
+ const canonicalCategory = toCanonicalPermissionKey(category);
21659
+ if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
21660
+ else overrideOnly[category] = value;
21661
+ }
21320
21662
  const json = Object.keys(overrideOnly).length > 0 ? {
21321
21663
  permission: shared,
21322
21664
  opencode: { permission: overrideOnly }
@@ -21441,6 +21783,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
21441
21783
  "disabled"
21442
21784
  ];
21443
21785
  const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
21786
+ const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
21444
21787
  function asPlainRecord(value) {
21445
21788
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
21446
21789
  }
@@ -21512,8 +21855,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
21512
21855
  else delete mergedPermissions.ask;
21513
21856
  if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
21514
21857
  else delete mergedPermissions.deny;
21515
- const patch = { permissions: mergedPermissions };
21516
21858
  const override = config.qwencode;
21859
+ if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
21860
+ const patch = { permissions: mergedPermissions };
21517
21861
  if (override?.tools !== void 0) patch.tools = {
21518
21862
  ...asPlainRecord(settings.tools),
21519
21863
  ...asPlainRecord(override.tools)
@@ -21555,9 +21899,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
21555
21899
  });
21556
21900
  const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
21557
21901
  const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
21902
+ const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
21558
21903
  const qwencodeOverride = {};
21559
21904
  if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
21560
21905
  if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
21906
+ if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
21561
21907
  const result = { ...config };
21562
21908
  if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
21563
21909
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
@@ -23066,7 +23412,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
23066
23412
  ["grokcli", {
23067
23413
  class: GrokcliPermissions,
23068
23414
  meta: {
23069
- supportsProject: false,
23415
+ supportsProject: true,
23070
23416
  supportsGlobal: true,
23071
23417
  supportsImport: true
23072
23418
  }
@@ -39591,4 +39937,4 @@ async function importPermissionsCore(params) {
39591
39937
  //#endregion
39592
39938
  export { removeTempDirectory as $, RulesyncCommand as A, checkPathTraversal as B, RulesyncHooks as C, RULESYNC_RULES_RELATIVE_DIR_PATH as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, formatError as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Et, findControlCharacter as F, findFilesByGlobs as G, directoryExists as H, ConsoleLogger as I, isSymlink as J, getFileSize as K, JsonLogger as L, stringifyFrontmatter as M, loadYaml as N, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES as Ot, ConfigResolver as P, removeFile as Q, CLIError as R, HooksProcessor as S, RULESYNC_RELATIVE_DIR_PATH as St, CLAUDECODE_DIR as T, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Tt, ensureDir as U, createTempDirectory as V, fileExists as W, readFileContent as X, listDirectoryFiles as Y, removeDirectory as Z, RulesyncPermissions as _, RULESYNC_MCP_RELATIVE_FILE_PATH as _t, convertFromTool as a, MAX_FILE_SIZE as at, IgnoreProcessor as b, RULESYNC_PERMISSIONS_FILE_NAME as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dt, toPosixPath as et, SkillsProcessor as f, RULESYNC_HOOKS_FILE_NAME as ft, SKILL_FILE_NAME as g, RULESYNC_MCP_FILE_NAME as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ToolTargetSchema as it, RulesyncCommandFrontmatterSchema as j, CLAUDECODE_SKILLS_DIR_PATH as k, ALL_FEATURES_WITH_WILDCARD as kt, SubagentsProcessor as l, RULESYNC_CONFIG_RELATIVE_FILE_PATH as lt, RulesyncSkill as m, RULESYNC_IGNORE_RELATIVE_FILE_PATH as mt, checkRulesyncDirExists as n, ALL_TOOL_TARGETS as nt, RulesProcessor as o, RULESYNC_AIIGNORE_FILE_NAME as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_RELATIVE_FILE_PATH as pt, getHomeDirectory as q, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rt, RulesyncRule as s, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as st, importFromTool as t, writeFileContent as tt, RulesyncSubagent as u, RULESYNC_CONFIG_SCHEMA_URL as ut, McpProcessor as v, RULESYNC_MCP_SCHEMA_URL as vt, CommandsProcessor as w, RULESYNC_SKILLS_RELATIVE_DIR_PATH as wt, RulesyncIgnore as x, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as xt, RulesyncMcp as y, RULESYNC_OVERVIEW_FILE_NAME as yt, ErrorCodes as z };
39593
39939
 
39594
- //# sourceMappingURL=import-DC9T1JlQ.js.map
39940
+ //# sourceMappingURL=import-Dioh9ca8.js.map