rulesync 9.2.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BkxwFCDM.js → import-B7VzSVUK.js} +901 -166
- package/dist/import-B7VzSVUK.js.map +1 -0
- package/dist/{import-H0MKtu9x.cjs → import-CE3Rx3Wt.cjs} +900 -165
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BkxwFCDM.js.map +0 -1
|
@@ -3029,8 +3029,8 @@ const DEVIN_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "skills");
|
|
|
3029
3029
|
const DEVIN_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "agents");
|
|
3030
3030
|
const DEVIN_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(".config", "devin");
|
|
3031
3031
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
3032
|
+
const DEVIN_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
3032
3033
|
const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(CODEIUM_WINDSURF_DIR, "global_workflows");
|
|
3033
|
-
const CODEIUM_WINDSURF_SKILLS_DIR_PATH = (0, node_path.join)(CODEIUM_WINDSURF_DIR, "skills");
|
|
3034
3034
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
3035
3035
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
3036
3036
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
@@ -5715,6 +5715,8 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
5715
5715
|
"sessionEnd",
|
|
5716
5716
|
"beforeSubmitPrompt",
|
|
5717
5717
|
"permissionRequest",
|
|
5718
|
+
"preToolUse",
|
|
5719
|
+
"postToolUse",
|
|
5718
5720
|
"postToolUseFailure",
|
|
5719
5721
|
"stop",
|
|
5720
5722
|
"preCompact",
|
|
@@ -5740,7 +5742,13 @@ const CODEXCLI_HOOK_EVENTS = [
|
|
|
5740
5742
|
* Goose adopts the Open Plugins hooks spec: each plugin's `hooks/hooks.json`
|
|
5741
5743
|
* maps PascalCase event names to matcher/handler arrays. Every Goose event has a
|
|
5742
5744
|
* 1:1 canonical equivalent, so no new canonical events are required.
|
|
5743
|
-
*
|
|
5745
|
+
*
|
|
5746
|
+
* Goose's `HookEvent` enum defines exactly these 11 events (v1.41.0). Notably it
|
|
5747
|
+
* has NO `SubagentStart`/`SubagentStop` arms — emitting them would write keys
|
|
5748
|
+
* Goose silently ignores, so `subagentStart`/`subagentStop` are intentionally
|
|
5749
|
+
* excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
|
|
5750
|
+
* @see https://github.com/block/goose/blob/v1.41.0/crates/goose/src/hooks/mod.rs
|
|
5751
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
5744
5752
|
*/
|
|
5745
5753
|
const GOOSE_HOOK_EVENTS = [
|
|
5746
5754
|
"sessionStart",
|
|
@@ -5753,9 +5761,7 @@ const GOOSE_HOOK_EVENTS = [
|
|
|
5753
5761
|
"beforeReadFile",
|
|
5754
5762
|
"afterFileEdit",
|
|
5755
5763
|
"beforeShellExecution",
|
|
5756
|
-
"afterShellExecution"
|
|
5757
|
-
"subagentStart",
|
|
5758
|
-
"subagentStop"
|
|
5764
|
+
"afterShellExecution"
|
|
5759
5765
|
];
|
|
5760
5766
|
/** Hook events supported by Kiro CLI. */
|
|
5761
5767
|
const KIRO_HOOK_EVENTS = [
|
|
@@ -6208,9 +6214,7 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
6208
6214
|
beforeReadFile: "BeforeReadFile",
|
|
6209
6215
|
afterFileEdit: "AfterFileEdit",
|
|
6210
6216
|
beforeShellExecution: "BeforeShellExecution",
|
|
6211
|
-
afterShellExecution: "AfterShellExecution"
|
|
6212
|
-
subagentStart: "SubagentStart",
|
|
6213
|
-
subagentStop: "SubagentStop"
|
|
6217
|
+
afterShellExecution: "AfterShellExecution"
|
|
6214
6218
|
};
|
|
6215
6219
|
/**
|
|
6216
6220
|
* Map Goose PascalCase event names to canonical camelCase.
|
|
@@ -6224,6 +6228,8 @@ const CANONICAL_TO_DEEPAGENTS_EVENT_NAMES = {
|
|
|
6224
6228
|
sessionEnd: "session.end",
|
|
6225
6229
|
beforeSubmitPrompt: "user.prompt",
|
|
6226
6230
|
permissionRequest: "permission.request",
|
|
6231
|
+
preToolUse: "tool.use",
|
|
6232
|
+
postToolUse: "tool.result",
|
|
6227
6233
|
postToolUseFailure: "tool.error",
|
|
6228
6234
|
stop: "task.complete",
|
|
6229
6235
|
preCompact: "context.compact",
|
|
@@ -8218,7 +8224,7 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
8218
8224
|
*
|
|
8219
8225
|
* The JSON shape matches Claude Code's: each PascalCase event maps to an array of
|
|
8220
8226
|
* `{ matcher, hooks: [{ type: "command", command }] }` entries.
|
|
8221
|
-
* @see https://
|
|
8227
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
8222
8228
|
*/
|
|
8223
8229
|
var GooseHooks = class GooseHooks extends ToolHooks {
|
|
8224
8230
|
constructor(params) {
|
|
@@ -8269,7 +8275,7 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8269
8275
|
throw new Error(`Failed to parse Goose hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
8270
8276
|
}
|
|
8271
8277
|
const hooks = toolHooksToCanonical({
|
|
8272
|
-
hooks: parsed.hooks,
|
|
8278
|
+
hooks: parsed.hooks && typeof parsed.hooks === "object" && !Array.isArray(parsed.hooks) ? Object.fromEntries(Object.entries(parsed.hooks).filter(([eventName]) => Object.hasOwn(GOOSE_TO_CANONICAL_EVENT_NAMES, eventName))) : parsed.hooks,
|
|
8273
8279
|
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
8274
8280
|
});
|
|
8275
8281
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
@@ -8324,6 +8330,24 @@ function mergeHermesConfig(fileContent, patch) {
|
|
|
8324
8330
|
...patch
|
|
8325
8331
|
});
|
|
8326
8332
|
}
|
|
8333
|
+
/**
|
|
8334
|
+
* Recursively merge `patch` into `base` (patch wins). Nested plain objects are
|
|
8335
|
+
* merged key-by-key; every other value (arrays, scalars) is replaced wholesale.
|
|
8336
|
+
* Used to overlay the Hermes-scoped permission override onto the natively
|
|
8337
|
+
* emitted `approvals`/`security` structures without one clobbering the other
|
|
8338
|
+
* (e.g. `approvals.deny` from canonical deny rules coexisting with an
|
|
8339
|
+
* `approvals.mode` from the override). Prototype-pollution keys are dropped.
|
|
8340
|
+
*/
|
|
8341
|
+
function deepMergeHermesConfig(base, patch) {
|
|
8342
|
+
const result = { ...base };
|
|
8343
|
+
for (const [key, patchValue] of Object.entries(patch)) {
|
|
8344
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8345
|
+
const baseValue = result[key];
|
|
8346
|
+
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = deepMergeHermesConfig(baseValue, patchValue);
|
|
8347
|
+
else result[key] = sanitizeHermesConfigValue(patchValue);
|
|
8348
|
+
}
|
|
8349
|
+
return result;
|
|
8350
|
+
}
|
|
8327
8351
|
//#endregion
|
|
8328
8352
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8329
8353
|
/**
|
|
@@ -12306,6 +12330,38 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
12306
12330
|
envVars: "env_vars"
|
|
12307
12331
|
};
|
|
12308
12332
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
12333
|
+
/**
|
|
12334
|
+
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
12335
|
+
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
12336
|
+
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
12337
|
+
* falls back to dynamic client registration and fails for providers that do not
|
|
12338
|
+
* support it (e.g. Slack, see #2158). The canonical `clientId` is kept alongside
|
|
12339
|
+
* the added `client_id` so tools that read the camelCase shape keep working and
|
|
12340
|
+
* the round-trip stays stable.
|
|
12341
|
+
*/
|
|
12342
|
+
function mapOauthToCodex(oauth) {
|
|
12343
|
+
const result = omitPrototypePollutionKeys(oauth);
|
|
12344
|
+
if (typeof oauth["clientId"] === "string" && !("client_id" in result)) result["client_id"] = oauth["clientId"];
|
|
12345
|
+
return result;
|
|
12346
|
+
}
|
|
12347
|
+
/**
|
|
12348
|
+
* Reverse of {@link mapOauthToCodex}: collapse Codex's `oauth.client_id` back to
|
|
12349
|
+
* the canonical `clientId` on import. When both keys are present (the shape
|
|
12350
|
+
* rulesync itself emits) the canonical `clientId` wins and `client_id` is
|
|
12351
|
+
* dropped so a subsequent generate does not accumulate duplicates.
|
|
12352
|
+
*/
|
|
12353
|
+
function mapOauthFromCodex(oauth) {
|
|
12354
|
+
const result = {};
|
|
12355
|
+
for (const [key, value] of Object.entries(oauth)) {
|
|
12356
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12357
|
+
if (key === "client_id") {
|
|
12358
|
+
if (!("clientId" in oauth)) result["clientId"] = value;
|
|
12359
|
+
continue;
|
|
12360
|
+
}
|
|
12361
|
+
result[key] = value;
|
|
12362
|
+
}
|
|
12363
|
+
return result;
|
|
12364
|
+
}
|
|
12309
12365
|
function convertFromCodexFormat(codexMcp) {
|
|
12310
12366
|
const result = {};
|
|
12311
12367
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
@@ -12315,7 +12371,8 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
12315
12371
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12316
12372
|
if (key === "enabled") {
|
|
12317
12373
|
if (value === false) converted["disabled"] = true;
|
|
12318
|
-
} else if (
|
|
12374
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
12375
|
+
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
12319
12376
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
12320
12377
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12321
12378
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
@@ -12335,7 +12392,8 @@ function convertToCodexFormat(mcpServers) {
|
|
|
12335
12392
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12336
12393
|
if (key === "disabled") {
|
|
12337
12394
|
if (value === true) converted["enabled"] = false;
|
|
12338
|
-
} else if (
|
|
12395
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
12396
|
+
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
12339
12397
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
12340
12398
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12341
12399
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
@@ -13551,10 +13609,12 @@ function resolveHermesTimeout(config) {
|
|
|
13551
13609
|
*
|
|
13552
13610
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
13553
13611
|
* executable string (an array's tail folds into `args`), a server is disabled
|
|
13554
|
-
* via `enabled: false` (not the canonical `disabled: true`),
|
|
13555
|
-
*
|
|
13556
|
-
* `
|
|
13557
|
-
*
|
|
13612
|
+
* via `enabled: false` (not the canonical `disabled: true`), remote servers use
|
|
13613
|
+
* `url`/`headers`, and per-server tool scoping lives under a `tools: { include,
|
|
13614
|
+
* exclude }` block (from the canonical `enabledTools`/`disabledTools`). Only
|
|
13615
|
+
* fields Hermes understands are emitted, so the shared `config.yaml` is not
|
|
13616
|
+
* polluted with canonical-only aliases (`type`, `transport`, `httpUrl`,
|
|
13617
|
+
* `networkTimeout`, ...).
|
|
13558
13618
|
*/
|
|
13559
13619
|
function convertServerToHermes(config) {
|
|
13560
13620
|
const out = {};
|
|
@@ -13578,6 +13638,10 @@ function convertServerToHermes(config) {
|
|
|
13578
13638
|
if (config.disabled === true) out.enabled = false;
|
|
13579
13639
|
const timeout = resolveHermesTimeout(config);
|
|
13580
13640
|
if (timeout !== void 0) out.timeout = timeout;
|
|
13641
|
+
const tools = {};
|
|
13642
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
13643
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
13644
|
+
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
13581
13645
|
return out;
|
|
13582
13646
|
}
|
|
13583
13647
|
/**
|
|
@@ -13619,6 +13683,10 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
13619
13683
|
if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
13620
13684
|
if (config.enabled === false) server.disabled = true;
|
|
13621
13685
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
13686
|
+
if (isRecord(config.tools)) {
|
|
13687
|
+
if (isStringArray(config.tools.include)) server.enabledTools = config.tools.include;
|
|
13688
|
+
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
13689
|
+
}
|
|
13622
13690
|
result[name] = server;
|
|
13623
13691
|
}
|
|
13624
13692
|
return result;
|
|
@@ -15774,17 +15842,232 @@ const PermissionActionSchema = zod_mini.z.enum([
|
|
|
15774
15842
|
*/
|
|
15775
15843
|
const PermissionRulesSchema = zod_mini.z.record(zod_mini.z.string(), PermissionActionSchema);
|
|
15776
15844
|
/**
|
|
15845
|
+
* OpenCode-specific permission value. Unlike the shared canonical block, which
|
|
15846
|
+
* only accepts a pattern-to-action map, OpenCode also allows a bare action
|
|
15847
|
+
* string that applies to the whole category (e.g. `"external_directory": "deny"`).
|
|
15848
|
+
* This mirrors OpenCode's own permission schema in `opencode-permissions.ts`.
|
|
15849
|
+
*/
|
|
15850
|
+
const OpencodeOverridePermissionValueSchema = zod_mini.z.union([PermissionActionSchema, PermissionRulesSchema]);
|
|
15851
|
+
/**
|
|
15852
|
+
* Tool-scoped override block for OpenCode. Permission categories placed here
|
|
15853
|
+
* (e.g. OpenCode-only categories such as `external_directory`) are emitted only
|
|
15854
|
+
* into OpenCode's config and never leak into other tools' permission files. It
|
|
15855
|
+
* also lets a shared category be overridden with an OpenCode-specific value.
|
|
15856
|
+
* Kept `looseObject` so future OpenCode categories are accepted.
|
|
15857
|
+
*
|
|
15858
|
+
* @example
|
|
15859
|
+
* { "permission": { "external_directory": "deny", "webfetch": "allow" } }
|
|
15860
|
+
*/
|
|
15861
|
+
const OpencodePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
15862
|
+
/**
|
|
15863
|
+
* Tool-scoped override block for Hermes Agent. Keys placed here are deep-merged
|
|
15864
|
+
* into Hermes's `~/.hermes/config.yaml` and never leak into other tools' configs.
|
|
15865
|
+
* It carries Hermes-specific approval/security controls that have no canonical
|
|
15866
|
+
* permission category — e.g. `approvals` (`mode`, `cron_mode`, ...),
|
|
15867
|
+
* `security` (`allow_private_urls`, ...), `skills.write_approval`,
|
|
15868
|
+
* `memory.write_approval`. Kept `looseObject` (a verbatim passthrough) so any
|
|
15869
|
+
* current or future Hermes config key can be authored without modeling each one.
|
|
15870
|
+
*
|
|
15871
|
+
* @example
|
|
15872
|
+
* { "approvals": { "mode": "smart" }, "security": { "allow_private_urls": false } }
|
|
15873
|
+
*/
|
|
15874
|
+
const HermesPermissionsOverrideSchema = zod_mini.z.looseObject({});
|
|
15875
|
+
/**
|
|
15876
|
+
* Tool-scoped override block for Cline. Cline's `command-permissions.json`
|
|
15877
|
+
* carries a single global `allowRedirects` boolean (gates shell redirection
|
|
15878
|
+
* operators `>`/`>>`/`<`) that has no per-command dimension and therefore no
|
|
15879
|
+
* canonical permission category. Placing it here lets users author it
|
|
15880
|
+
* declaratively; it is emitted only into Cline's config. Kept `looseObject` so
|
|
15881
|
+
* future Cline-only knobs can be added.
|
|
15882
|
+
*
|
|
15883
|
+
* @example
|
|
15884
|
+
* { "allowRedirects": true }
|
|
15885
|
+
*/
|
|
15886
|
+
const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({ allowRedirects: zod_mini.z.optional(zod_mini.z.boolean()) });
|
|
15887
|
+
/**
|
|
15888
|
+
* Tool-scoped override block for Kilo Code (an OpenCode fork). Kilo's permission
|
|
15889
|
+
* object is a free-form record with tool-specific keys that have no canonical
|
|
15890
|
+
* category — OpenCode-inherited ones (`external_directory`, `doom_loop`, `lsp`,
|
|
15891
|
+
* `question`, `todowrite`, `skill`, `task`, `list`) and Kilo-unique ones
|
|
15892
|
+
* (`agent_manager`, `notebook_read`, `notebook_edit`, `notebook_execute`,
|
|
15893
|
+
* `repo_clone`, `repo_overview`). Placing them here makes them authorable and
|
|
15894
|
+
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
15895
|
+
* override; each value may be a bare action string or a pattern map.
|
|
15896
|
+
*
|
|
15897
|
+
* @example
|
|
15898
|
+
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
15899
|
+
*/
|
|
15900
|
+
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
15901
|
+
/**
|
|
15902
|
+
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
15903
|
+
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
15904
|
+
* permission category — `defaultMode` (the session-start permission mode) and
|
|
15905
|
+
* `additionalDirectories` (extra working directories) being the primary ones.
|
|
15906
|
+
* Fields placed under `claudecode.permissions` are merged into the settings
|
|
15907
|
+
* `permissions` object and emitted only for Claude Code, while the shared
|
|
15908
|
+
* `permission` block continues to drive the `allow`/`ask`/`deny` arrays. Kept a
|
|
15909
|
+
* `looseObject` passthrough so any current or future `permissions` field can be
|
|
15910
|
+
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
15911
|
+
* ignored here (rulesync owns them).
|
|
15912
|
+
*
|
|
15913
|
+
* @example
|
|
15914
|
+
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
15915
|
+
*/
|
|
15916
|
+
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({ permissions: zod_mini.z.optional(zod_mini.z.looseObject({})) });
|
|
15917
|
+
/**
|
|
15918
|
+
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
15919
|
+
* carries a `sensitive_patterns` list — patterns that escalate to ASK even when
|
|
15920
|
+
* the tool's base permission is ALWAYS (allow). The canonical model can only set
|
|
15921
|
+
* a pattern to a single `allow`/`ask`/`deny`, so an "allow by default but ask on
|
|
15922
|
+
* these patterns" escalation cannot be expressed. Entries under
|
|
15923
|
+
* `vibe.permission.<category>.sensitive_patterns` carry that list per canonical
|
|
15924
|
+
* category; the shared `permission` block still drives the base permission and
|
|
15925
|
+
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
15926
|
+
*
|
|
15927
|
+
* @example
|
|
15928
|
+
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
15929
|
+
*/
|
|
15930
|
+
const VibePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.looseObject({ sensitive_patterns: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) }))) });
|
|
15931
|
+
/**
|
|
15932
|
+
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
15933
|
+
* autonomy settings with no canonical permission category — `approvalMode`
|
|
15934
|
+
* (`allowlist` | `auto-review` | `unrestricted`) and a `sandbox` object
|
|
15935
|
+
* (`mode`/`networkAccess`). Fields placed here are merged into the top-level of
|
|
15936
|
+
* `.cursor/cli.json` (project) / `~/.cursor/cli-config.json` (global) and emitted
|
|
15937
|
+
* only for Cursor, while the shared `permission` block continues to drive the
|
|
15938
|
+
* `permissions.allow`/`permissions.deny` arrays. Kept a `looseObject` so extra
|
|
15939
|
+
* `cli.json` keys can be authored (they are merged verbatim on generate);
|
|
15940
|
+
* `sandbox`'s accepted values are not documented so it passes through verbatim.
|
|
15941
|
+
* Note: only `approvalMode` and `sandbox` round-trip back on import — other keys
|
|
15942
|
+
* authored here reach `cli.json` on generate but are not re-extracted.
|
|
15943
|
+
*
|
|
15944
|
+
* @example
|
|
15945
|
+
* { "approvalMode": "auto-review" }
|
|
15946
|
+
*/
|
|
15947
|
+
const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
15948
|
+
approvalMode: zod_mini.z.optional(zod_mini.z.string()),
|
|
15949
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
15950
|
+
});
|
|
15951
|
+
/**
|
|
15952
|
+
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
15953
|
+
* autonomy/sandbox controls with no canonical permission category — under
|
|
15954
|
+
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
15955
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). Fields
|
|
15956
|
+
* placed here are merged into the matching `settings.json` group and emitted
|
|
15957
|
+
* only for Qwen, while the shared `permission` block continues to drive the
|
|
15958
|
+
* `permissions.allow`/`ask`/`deny` arrays. Kept `looseObject` (verbatim
|
|
15959
|
+
* passthrough) so any current or future `tools`/`security` key can be authored.
|
|
15960
|
+
*
|
|
15961
|
+
* @example
|
|
15962
|
+
* { "tools": { "approvalMode": "auto-edit" }, "security": { "folderTrust": { "enabled": true } } }
|
|
15963
|
+
*/
|
|
15964
|
+
const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
15965
|
+
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
15966
|
+
security: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
15967
|
+
});
|
|
15968
|
+
/**
|
|
15969
|
+
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
15970
|
+
* to per-tool allow/ask/deny with no canonical category — the `[sandbox]`
|
|
15971
|
+
* enforcement table (`workspace_root`, `allow_write`, `forbid_read`, `bash`,
|
|
15972
|
+
* `network`) and plan-mode read-only trust lists under `[agent]`
|
|
15973
|
+
* (`plan_mode_allowed_tools`, `plan_mode_read_only_commands`). Fields placed here
|
|
15974
|
+
* are merged into the matching `reasonix.toml` table and emitted only for
|
|
15975
|
+
* Reasonix, while the shared `permission` block continues to drive
|
|
15976
|
+
* `[permissions].allow`/`ask`/`deny`. Kept `looseObject` (verbatim passthrough).
|
|
15977
|
+
* Note: the whole `[sandbox]` table round-trips, but only the plan-mode keys are
|
|
15978
|
+
* re-extracted from `[agent]` on import — other `agent` keys authored here reach
|
|
15979
|
+
* `reasonix.toml` on generate but are not re-extracted back into the override.
|
|
15980
|
+
*
|
|
15981
|
+
* @example
|
|
15982
|
+
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
15983
|
+
*/
|
|
15984
|
+
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
15985
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
15986
|
+
agent: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
15987
|
+
});
|
|
15988
|
+
/**
|
|
15989
|
+
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
15990
|
+
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
15991
|
+
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
15992
|
+
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
15993
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`, `enableDroidShield`,
|
|
15994
|
+
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
15995
|
+
* `interactionMode`). Fields placed here are merged into `settings.json` and
|
|
15996
|
+
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
15997
|
+
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
15998
|
+
*
|
|
15999
|
+
* @example
|
|
16000
|
+
* { "commandBlocklist": ["rm -rf /*"], "sandbox": { "enabled": true } }
|
|
16001
|
+
*/
|
|
16002
|
+
const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({ commandBlocklist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) });
|
|
16003
|
+
/**
|
|
16004
|
+
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
16005
|
+
* file-read/read-only autonomy keys with no canonical per-command allow/ask/deny
|
|
16006
|
+
* slot — `agent_mode_coding_permissions`
|
|
16007
|
+
* (`always_ask_before_reading` | `always_allow_reading` | `allow_reading_specific_files`),
|
|
16008
|
+
* `agent_mode_coding_file_read_allowlist` (a path array), and
|
|
16009
|
+
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
16010
|
+
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
16011
|
+
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
16012
|
+
* `agent_mode_command_execution_allowlist`/`_denylist` command regex arrays.
|
|
16013
|
+
* Warp permissions are global-only.
|
|
16014
|
+
*
|
|
16015
|
+
* @example
|
|
16016
|
+
* { "agent_mode_coding_permissions": "always_allow_reading", "agent_mode_execute_readonly_commands": true }
|
|
16017
|
+
*/
|
|
16018
|
+
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16019
|
+
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.string()),
|
|
16020
|
+
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
16021
|
+
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16022
|
+
});
|
|
16023
|
+
/**
|
|
16024
|
+
* Tool-scoped override block for JetBrains Junie. Junie's `allowlist.json` has
|
|
16025
|
+
* two top-level autonomy knobs with no canonical per-glob slot:
|
|
16026
|
+
* `allowReadonlyCommands` (a boolean auto-allowing read-only commands) and
|
|
16027
|
+
* `defaultBehavior` (the fallback action applied when no rule matches — Junie
|
|
16028
|
+
* documents only `allow`/`ask`). Fields placed here are merged onto the
|
|
16029
|
+
* top level of `allowlist.json`, while the shared `permission` block continues
|
|
16030
|
+
* to drive the per-category `rules` groups.
|
|
16031
|
+
*
|
|
16032
|
+
* @example
|
|
16033
|
+
* { "allowReadonlyCommands": true, "defaultBehavior": "ask" }
|
|
16034
|
+
*/
|
|
16035
|
+
const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16036
|
+
allowReadonlyCommands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
16037
|
+
defaultBehavior: zod_mini.z.optional(zod_mini.z.string())
|
|
16038
|
+
});
|
|
16039
|
+
/**
|
|
15777
16040
|
* Permissions configuration.
|
|
15778
16041
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
15779
16042
|
* Values are pattern-to-action mappings for that tool category.
|
|
15780
16043
|
*
|
|
16044
|
+
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
16045
|
+
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie` keys are tool-scoped
|
|
16046
|
+
* overrides consumed only by their respective translator (see the matching
|
|
16047
|
+
* `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
|
|
16048
|
+
* block and ignores them.
|
|
16049
|
+
*
|
|
15781
16050
|
* @example
|
|
15782
16051
|
* {
|
|
15783
16052
|
* "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
|
|
15784
16053
|
* "edit": { "*": "deny", "src/**": "allow" }
|
|
15785
16054
|
* }
|
|
15786
16055
|
*/
|
|
15787
|
-
const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
16056
|
+
const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
16057
|
+
permission: zod_mini.z.record(zod_mini.z.string(), PermissionRulesSchema),
|
|
16058
|
+
opencode: zod_mini.z.optional(OpencodePermissionsOverrideSchema),
|
|
16059
|
+
hermes: zod_mini.z.optional(HermesPermissionsOverrideSchema),
|
|
16060
|
+
cline: zod_mini.z.optional(ClinePermissionsOverrideSchema),
|
|
16061
|
+
kilo: zod_mini.z.optional(KiloPermissionsOverrideSchema),
|
|
16062
|
+
claudecode: zod_mini.z.optional(ClaudecodePermissionsOverrideSchema),
|
|
16063
|
+
vibe: zod_mini.z.optional(VibePermissionsOverrideSchema),
|
|
16064
|
+
cursor: zod_mini.z.optional(CursorPermissionsOverrideSchema),
|
|
16065
|
+
qwencode: zod_mini.z.optional(QwencodePermissionsOverrideSchema),
|
|
16066
|
+
reasonix: zod_mini.z.optional(ReasonixPermissionsOverrideSchema),
|
|
16067
|
+
factorydroid: zod_mini.z.optional(FactorydroidPermissionsOverrideSchema),
|
|
16068
|
+
warp: zod_mini.z.optional(WarpPermissionsOverrideSchema),
|
|
16069
|
+
junie: zod_mini.z.optional(JuniePermissionsOverrideSchema)
|
|
16070
|
+
});
|
|
15788
16071
|
/**
|
|
15789
16072
|
* Full permissions file schema including optional $schema field.
|
|
15790
16073
|
*/
|
|
@@ -17155,6 +17438,14 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17155
17438
|
}
|
|
17156
17439
|
const config = rulesyncPermissions.getJson();
|
|
17157
17440
|
const { allow, ask, deny } = convertRulesyncToClaudePermissions(config);
|
|
17441
|
+
const overridePermissions = config.claudecode?.permissions;
|
|
17442
|
+
if (overridePermissions && typeof overridePermissions === "object") {
|
|
17443
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
17444
|
+
settings.permissions = {
|
|
17445
|
+
...settings.permissions,
|
|
17446
|
+
...nonListFields
|
|
17447
|
+
};
|
|
17448
|
+
}
|
|
17158
17449
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
17159
17450
|
const merged = applyPermissions({
|
|
17160
17451
|
settings,
|
|
@@ -17187,6 +17478,8 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17187
17478
|
ask: permissions.ask ?? [],
|
|
17188
17479
|
deny: permissions.deny ?? []
|
|
17189
17480
|
});
|
|
17481
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
17482
|
+
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
17190
17483
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17191
17484
|
}
|
|
17192
17485
|
validate() {
|
|
@@ -17360,7 +17653,8 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17360
17653
|
} catch (error) {
|
|
17361
17654
|
throw new Error(`Failed to parse existing Cline command-permissions at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
17362
17655
|
}
|
|
17363
|
-
const
|
|
17656
|
+
const config = rulesyncPermissions.getJson();
|
|
17657
|
+
const { allow, deny, droppedCategories, translatedAskPatterns } = translateClinePermissions(config.permission);
|
|
17364
17658
|
warnClineTranslationNotices({
|
|
17365
17659
|
droppedCategories,
|
|
17366
17660
|
translatedAskPatterns,
|
|
@@ -17376,7 +17670,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17376
17670
|
...existing,
|
|
17377
17671
|
allow: dedupedAllow,
|
|
17378
17672
|
deny: mergedDeny,
|
|
17379
|
-
allowRedirects: existing.allowRedirects ?? false
|
|
17673
|
+
allowRedirects: config.cline?.allowRedirects ?? existing.allowRedirects ?? false
|
|
17380
17674
|
};
|
|
17381
17675
|
return new ClinePermissions({
|
|
17382
17676
|
outputRoot,
|
|
@@ -17400,6 +17694,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17400
17694
|
for (const pattern of parsed.allow ?? []) bashRules[pattern] = "allow";
|
|
17401
17695
|
for (const pattern of parsed.deny ?? []) bashRules[pattern] = "deny";
|
|
17402
17696
|
const config = Object.keys(bashRules).length > 0 ? { permission: { bash: bashRules } } : { permission: {} };
|
|
17697
|
+
if (parsed.allowRedirects === true) config.cline = { allowRedirects: true };
|
|
17403
17698
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17404
17699
|
}
|
|
17405
17700
|
validate() {
|
|
@@ -17836,13 +18131,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
17836
18131
|
WebFetch: "webfetch",
|
|
17837
18132
|
Mcp: "mcp"
|
|
17838
18133
|
};
|
|
17839
|
-
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
18134
|
+
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
17840
18135
|
/**
|
|
17841
18136
|
* Returns true if the canonical category is the per-tool MCP form
|
|
17842
18137
|
* `mcp__<server>__<tool>`.
|
|
17843
18138
|
*/
|
|
17844
18139
|
function isMcpScopedCategory(canonical) {
|
|
17845
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX) && canonical.length > 5;
|
|
18140
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$1) && canonical.length > 5;
|
|
17846
18141
|
}
|
|
17847
18142
|
function toCursorType(canonical) {
|
|
17848
18143
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -17873,7 +18168,7 @@ function toCursorPattern(canonical, pattern) {
|
|
|
17873
18168
|
function toCanonicalCategory$1(cursorType, pattern) {
|
|
17874
18169
|
if (cursorType === "Mcp") {
|
|
17875
18170
|
const match = pattern.match(/^([^:()]+):([^:()]+)$/);
|
|
17876
|
-
if (match) return `${MCP_CANONICAL_PREFIX}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
18171
|
+
if (match) return `${MCP_CANONICAL_PREFIX$1}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
17877
18172
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
17878
18173
|
}
|
|
17879
18174
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
@@ -18007,8 +18302,10 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
18007
18302
|
mergedPermissions.allow = mergedAllow;
|
|
18008
18303
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
18009
18304
|
else delete mergedPermissions.deny;
|
|
18305
|
+
const cursorOverride = config.cursor;
|
|
18010
18306
|
const merged = {
|
|
18011
18307
|
...settings,
|
|
18308
|
+
...cursorOverride,
|
|
18012
18309
|
version: settings.version ?? 1,
|
|
18013
18310
|
editor: {
|
|
18014
18311
|
...existingEditor,
|
|
@@ -18034,11 +18331,15 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
18034
18331
|
}
|
|
18035
18332
|
const permissionsRaw = settings.permissions;
|
|
18036
18333
|
const permissions = permissionsRaw !== null && typeof permissionsRaw === "object" && !Array.isArray(permissionsRaw) ? permissionsRaw : {};
|
|
18037
|
-
const
|
|
18334
|
+
const result = { ...convertCursorToRulesyncPermissions({
|
|
18038
18335
|
allow: asCursorPermissionEntryArray(permissions.allow),
|
|
18039
18336
|
deny: asCursorPermissionEntryArray(permissions.deny)
|
|
18040
|
-
});
|
|
18041
|
-
|
|
18337
|
+
}) };
|
|
18338
|
+
const cursorOverride = {};
|
|
18339
|
+
if (settings.approvalMode !== void 0) cursorOverride.approvalMode = settings.approvalMode;
|
|
18340
|
+
if (settings.sandbox !== null && typeof settings.sandbox === "object" && !Array.isArray(settings.sandbox)) cursorOverride.sandbox = settings.sandbox;
|
|
18341
|
+
if (Object.keys(cursorOverride).length > 0) result.cursor = cursorOverride;
|
|
18342
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18042
18343
|
}
|
|
18043
18344
|
validate() {
|
|
18044
18345
|
return {
|
|
@@ -18097,7 +18398,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
18097
18398
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
18098
18399
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
18099
18400
|
if (!permission[canonical]) permission[canonical] = {};
|
|
18100
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX) ? "*" : pattern;
|
|
18401
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$1) ? "*" : pattern;
|
|
18101
18402
|
permission[canonical][canonicalPattern] = action;
|
|
18102
18403
|
}
|
|
18103
18404
|
};
|
|
@@ -18346,6 +18647,16 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18346
18647
|
}
|
|
18347
18648
|
//#endregion
|
|
18348
18649
|
//#region src/features/permissions/factorydroid-permissions.ts
|
|
18650
|
+
const FACTORYDROID_OVERRIDE_KEYS = [
|
|
18651
|
+
"commandBlocklist",
|
|
18652
|
+
"networkPolicy",
|
|
18653
|
+
"sandbox",
|
|
18654
|
+
"mcpPolicy",
|
|
18655
|
+
"enableDroidShield",
|
|
18656
|
+
"sessionDefaultSettings",
|
|
18657
|
+
"maxAutonomyLevel",
|
|
18658
|
+
"interactionMode"
|
|
18659
|
+
];
|
|
18349
18660
|
/**
|
|
18350
18661
|
* Permissions adapter for Factory Droid.
|
|
18351
18662
|
*
|
|
@@ -18363,18 +18674,14 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18363
18674
|
* skipped (with a warning when they carry `deny` rules, to surface the gap).
|
|
18364
18675
|
*
|
|
18365
18676
|
* Factory Droid also has a stronger `commandBlocklist` tier — commands that can
|
|
18366
|
-
* never run, not even under full autonomy
|
|
18367
|
-
*
|
|
18368
|
-
*
|
|
18369
|
-
*
|
|
18370
|
-
*
|
|
18371
|
-
*
|
|
18372
|
-
*
|
|
18373
|
-
*
|
|
18374
|
-
* `commandBlocklist` and re-exporting it to a *fresh* config writes it back as
|
|
18375
|
-
* `commandDenylist`, not `commandBlocklist` — the hard-block tier is not
|
|
18376
|
-
* reconstructed. Re-running over the original file keeps it intact via the
|
|
18377
|
-
* verbatim preservation above.)
|
|
18677
|
+
* never run, not even under full autonomy — plus other security controls
|
|
18678
|
+
* (`networkPolicy`, `sandbox`, `mcpPolicy`, `enableDroidShield`, autonomy
|
|
18679
|
+
* settings) that do not fit the canonical `allow | ask | deny` per-command
|
|
18680
|
+
* model. These are authored and round-tripped through the `factorydroid`
|
|
18681
|
+
* override namespace (see `FactorydroidPermissionsOverrideSchema`): on **import**
|
|
18682
|
+
* they are lifted from `settings.json` into the override, and on **export** they
|
|
18683
|
+
* are merged back in — so `commandBlocklist`'s never-runs guarantee is preserved
|
|
18684
|
+
* faithfully rather than being collapsed onto an approvable `deny`.
|
|
18378
18685
|
*/
|
|
18379
18686
|
var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissions {
|
|
18380
18687
|
constructor(params) {
|
|
@@ -18417,11 +18724,16 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18417
18724
|
} catch (error) {
|
|
18418
18725
|
throw new Error(`Failed to parse existing Factory Droid settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18419
18726
|
}
|
|
18727
|
+
const config = rulesyncPermissions.getJson();
|
|
18420
18728
|
const { allow, deny } = convertRulesyncToFactorydroidPermissions({
|
|
18421
|
-
config
|
|
18729
|
+
config,
|
|
18422
18730
|
logger
|
|
18423
18731
|
});
|
|
18424
|
-
const
|
|
18732
|
+
const override = config.factorydroid;
|
|
18733
|
+
const merged = {
|
|
18734
|
+
...settings,
|
|
18735
|
+
...override !== void 0 && typeof override === "object" ? override : {}
|
|
18736
|
+
};
|
|
18425
18737
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
18426
18738
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
18427
18739
|
if (mergedAllow.length > 0) merged.commandAllowlist = mergedAllow;
|
|
@@ -18445,10 +18757,13 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18445
18757
|
}
|
|
18446
18758
|
const config = convertFactorydroidToRulesyncPermissions({
|
|
18447
18759
|
allow: Array.isArray(settings.commandAllowlist) ? settings.commandAllowlist : [],
|
|
18448
|
-
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18449
|
-
block: Array.isArray(settings.commandBlocklist) ? settings.commandBlocklist : []
|
|
18760
|
+
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18450
18761
|
});
|
|
18451
|
-
|
|
18762
|
+
const factorydroidOverride = {};
|
|
18763
|
+
for (const key of FACTORYDROID_OVERRIDE_KEYS) if (settings[key] !== void 0) factorydroidOverride[key] = settings[key];
|
|
18764
|
+
const result = { ...config };
|
|
18765
|
+
if (Object.keys(factorydroidOverride).length > 0) result.factorydroid = factorydroidOverride;
|
|
18766
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18452
18767
|
}
|
|
18453
18768
|
validate() {
|
|
18454
18769
|
return {
|
|
@@ -18495,18 +18810,18 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
18495
18810
|
};
|
|
18496
18811
|
}
|
|
18497
18812
|
/**
|
|
18498
|
-
* Convert Factory Droid allow/deny
|
|
18499
|
-
*
|
|
18813
|
+
* Convert Factory Droid allow/deny command lists back to rulesync config under
|
|
18814
|
+
* the `bash` category.
|
|
18500
18815
|
*
|
|
18501
|
-
* `commandBlocklist` (hard
|
|
18502
|
-
*
|
|
18503
|
-
* the
|
|
18816
|
+
* `commandBlocklist` (the hard-block tier) is no longer collapsed here — it has
|
|
18817
|
+
* no canonical equivalent and now round-trips through the `factorydroid`
|
|
18818
|
+
* override so the never-runs guarantee is preserved instead of being weakened to
|
|
18819
|
+
* an approvable `deny`.
|
|
18504
18820
|
*/
|
|
18505
|
-
function convertFactorydroidToRulesyncPermissions({ allow, deny
|
|
18821
|
+
function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
18506
18822
|
const bash = {};
|
|
18507
18823
|
for (const pattern of allow) bash[pattern] = "allow";
|
|
18508
18824
|
for (const pattern of deny) bash[pattern] = "deny";
|
|
18509
|
-
for (const pattern of block) bash[pattern] = "deny";
|
|
18510
18825
|
return { permission: Object.keys(bash).length > 0 ? { bash } : {} };
|
|
18511
18826
|
}
|
|
18512
18827
|
//#endregion
|
|
@@ -18700,30 +19015,114 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
18700
19015
|
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
18701
19016
|
const GROKCLI_UI_KEY = "ui";
|
|
18702
19017
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
19018
|
+
const GROKCLI_PERMISSION_KEY = "permission";
|
|
18703
19019
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
19020
|
+
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
19021
|
+
const CATEGORY_TO_GROK_TOOL = {
|
|
19022
|
+
bash: "Bash",
|
|
19023
|
+
read: "Read",
|
|
19024
|
+
edit: "Edit",
|
|
19025
|
+
write: "Edit",
|
|
19026
|
+
grep: "Grep",
|
|
19027
|
+
webfetch: "WebFetch"
|
|
19028
|
+
};
|
|
19029
|
+
const GROK_TOOL_TO_CATEGORY = {
|
|
19030
|
+
Bash: "bash",
|
|
19031
|
+
Read: "read",
|
|
19032
|
+
Edit: "edit",
|
|
19033
|
+
Grep: "grep",
|
|
19034
|
+
WebFetch: "webfetch"
|
|
19035
|
+
};
|
|
19036
|
+
const GROK_MCP_TOOL = "MCPTool";
|
|
19037
|
+
/**
|
|
19038
|
+
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
19039
|
+
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
19040
|
+
* for categories Grok cannot express so callers can skip them.
|
|
19041
|
+
*
|
|
19042
|
+
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
19043
|
+
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
19044
|
+
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
19045
|
+
* concrete pattern emits `Tool(pattern)`.
|
|
19046
|
+
*/
|
|
19047
|
+
function buildGrokEntry(category, pattern) {
|
|
19048
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
19049
|
+
const remainder = category.slice(5);
|
|
19050
|
+
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
19051
|
+
}
|
|
19052
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
19053
|
+
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
19054
|
+
if (tool === void 0) return null;
|
|
19055
|
+
return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
19056
|
+
}
|
|
19057
|
+
/**
|
|
19058
|
+
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
19059
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`). Returns
|
|
19060
|
+
* `null` for tool prefixes with no canonical equivalent (e.g. `any`).
|
|
19061
|
+
*/
|
|
19062
|
+
function parseGrokEntry(entry) {
|
|
19063
|
+
const trimmed = entry.trim();
|
|
19064
|
+
const parenIndex = trimmed.indexOf("(");
|
|
19065
|
+
let tool;
|
|
19066
|
+
let inner;
|
|
19067
|
+
if (parenIndex === -1 || !trimmed.endsWith(")")) {
|
|
19068
|
+
tool = trimmed;
|
|
19069
|
+
inner = "";
|
|
19070
|
+
} else {
|
|
19071
|
+
tool = trimmed.slice(0, parenIndex);
|
|
19072
|
+
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
19073
|
+
}
|
|
19074
|
+
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
19075
|
+
category: `${MCP_CANONICAL_PREFIX}${inner}`,
|
|
19076
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19077
|
+
} : {
|
|
19078
|
+
category: "mcp",
|
|
19079
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19080
|
+
};
|
|
19081
|
+
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
19082
|
+
if (category === void 0) return null;
|
|
19083
|
+
return {
|
|
19084
|
+
category,
|
|
19085
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$2
|
|
19086
|
+
};
|
|
19087
|
+
}
|
|
18704
19088
|
/**
|
|
18705
19089
|
* Permissions adapter for the xAI Grok Build CLI (`grokcli`).
|
|
18706
19090
|
*
|
|
18707
|
-
* Grok
|
|
18708
|
-
*
|
|
18709
|
-
*
|
|
18710
|
-
*
|
|
18711
|
-
*
|
|
18712
|
-
*
|
|
18713
|
-
*
|
|
18714
|
-
*
|
|
18715
|
-
*
|
|
18716
|
-
*
|
|
18717
|
-
* `
|
|
18718
|
-
*
|
|
18719
|
-
*
|
|
18720
|
-
*
|
|
18721
|
-
* the
|
|
18722
|
-
*
|
|
18723
|
-
*
|
|
18724
|
-
*
|
|
18725
|
-
*
|
|
18726
|
-
*
|
|
19091
|
+
* Grok Build CLI ships a Claude-style rule system under `[permission]` in
|
|
19092
|
+
* `~/.grok/config.toml`: `allow` / `deny` / `ask` arrays of entries such as
|
|
19093
|
+
* `Bash(git *)`, `Read(src/**)`, `Edit`, `Grep`, `MCPTool(server__tool)`, and
|
|
19094
|
+
* `WebFetch`, evaluated with precedence `deny > ask > allow`
|
|
19095
|
+
* (https://docs.x.ai/build/settings/reference). rulesync's canonical
|
|
19096
|
+
* per-category, per-pattern model maps almost 1:1:
|
|
19097
|
+
* - Generate: each `permission.<category>.<pattern> = allow|ask|deny` becomes
|
|
19098
|
+
* the matching Grok entry and is bucketed into the `[permission]` array for
|
|
19099
|
+
* that action. `bash|read|edit|grep|webfetch` map to their Grok tool;
|
|
19100
|
+
* `write` collapses onto `Edit` (Grok has no `Write` tool); `mcp__*` maps to
|
|
19101
|
+
* `MCPTool(...)` (a scoped MCP category folds its address into the
|
|
19102
|
+
* parentheses, so a non-`*` argument pattern on it is not represented).
|
|
19103
|
+
* Categories with no Grok tool (`websearch`, `glob`, `notebookedit`,
|
|
19104
|
+
* `agent`) are skipped (with a warning when they carry a `deny` rule, to
|
|
19105
|
+
* surface the gap). When two canonical rules collapse onto the same Grok
|
|
19106
|
+
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
19107
|
+
* the strictest wins (`deny > ask > allow`) and a warning is logged, so the
|
|
19108
|
+
* entry never lands contradictorily in two arrays.
|
|
19109
|
+
* - Import: the `[permission]` arrays are parsed back into canonical
|
|
19110
|
+
* categories. When no `[permission]` section is present (older configs), the
|
|
19111
|
+
* coarse `[ui] permission_mode` is used as a fallback.
|
|
19112
|
+
*
|
|
19113
|
+
* The coarse `[ui] permission_mode` toggle is still written for backward
|
|
19114
|
+
* compatibility with older Grok versions: `always-approve` when the config is
|
|
19115
|
+
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
19116
|
+
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
19117
|
+
* arrays).
|
|
19118
|
+
*
|
|
19119
|
+
* This surface is **global only** — the adapter syncs the user-level
|
|
19120
|
+
* `~/.grok/config.toml`. (Grok also supports a project-scoped `[permission]`
|
|
19121
|
+
* file; project scope is not modeled here.) The shared config is merged in
|
|
19122
|
+
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
19123
|
+
* tools it models and the `[ui] permission_mode` value, while every other key
|
|
19124
|
+
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
19125
|
+
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
18727
19126
|
* file is never deleted.
|
|
18728
19127
|
*/
|
|
18729
19128
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -18755,7 +19154,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18755
19154
|
global: true
|
|
18756
19155
|
});
|
|
18757
19156
|
}
|
|
18758
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
19157
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
18759
19158
|
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
18760
19159
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
18761
19160
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
@@ -18766,7 +19165,16 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18766
19165
|
} catch (error) {
|
|
18767
19166
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18768
19167
|
}
|
|
18769
|
-
const
|
|
19168
|
+
const config = rulesyncPermissions.getJson();
|
|
19169
|
+
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
19170
|
+
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
19171
|
+
parsed[GROKCLI_PERMISSION_KEY] = {
|
|
19172
|
+
...existingPermission,
|
|
19173
|
+
allow: buckets.allow,
|
|
19174
|
+
deny: buckets.deny,
|
|
19175
|
+
ask: buckets.ask
|
|
19176
|
+
};
|
|
19177
|
+
const mode = deriveGrokPermissionMode(config);
|
|
18770
19178
|
const existingUi = isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
18771
19179
|
parsed[GROKCLI_UI_KEY] = {
|
|
18772
19180
|
...existingUi,
|
|
@@ -18789,8 +19197,8 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18789
19197
|
} catch (error) {
|
|
18790
19198
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18791
19199
|
}
|
|
18792
|
-
const
|
|
18793
|
-
const rulesyncConfig = { permission: { bash: { [CATCH_ALL_PATTERN$2]:
|
|
19200
|
+
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
19201
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$2]: legacyModeAction(parsed) } } };
|
|
18794
19202
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
18795
19203
|
}
|
|
18796
19204
|
validate() {
|
|
@@ -18810,6 +19218,84 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18810
19218
|
});
|
|
18811
19219
|
}
|
|
18812
19220
|
};
|
|
19221
|
+
const ACTION_RANK = {
|
|
19222
|
+
allow: 0,
|
|
19223
|
+
ask: 1,
|
|
19224
|
+
deny: 2
|
|
19225
|
+
};
|
|
19226
|
+
/**
|
|
19227
|
+
* Collect user-authored entries from an existing `[permission]` array whose
|
|
19228
|
+
* tool prefix rulesync cannot model (e.g. `WebSearch`, `any`). Such entries are
|
|
19229
|
+
* preserved verbatim so replacing the arrays does not silently drop them
|
|
19230
|
+
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
19231
|
+
*/
|
|
19232
|
+
function unmanagedEntries(existingPermission, key) {
|
|
19233
|
+
return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
19234
|
+
}
|
|
19235
|
+
/**
|
|
19236
|
+
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
19237
|
+
* Claude-style entries, resolving collapse collisions via `deny > ask > allow`.
|
|
19238
|
+
* Categories Grok cannot express are skipped (with a warning when they carry a
|
|
19239
|
+
* `deny` rule); entries the user authored for tools rulesync cannot model are
|
|
19240
|
+
* preserved from `existingPermission`.
|
|
19241
|
+
*/
|
|
19242
|
+
function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
19243
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
19244
|
+
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19245
|
+
const entry = buildGrokEntry(category, pattern);
|
|
19246
|
+
if (entry === null) {
|
|
19247
|
+
if (action === "deny" && logger) logger.warn(`Grok CLI has no permission tool for the '${category}' category; its 'deny' rule could not be represented and was skipped.`);
|
|
19248
|
+
continue;
|
|
19249
|
+
}
|
|
19250
|
+
const existing = ranked.get(entry);
|
|
19251
|
+
if (existing !== void 0 && existing !== action) logger?.warn(`Grok permission entry '${entry}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
|
|
19252
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) ranked.set(entry, action);
|
|
19253
|
+
}
|
|
19254
|
+
const allow = unmanagedEntries(existingPermission, "allow");
|
|
19255
|
+
const deny = unmanagedEntries(existingPermission, "deny");
|
|
19256
|
+
const ask = unmanagedEntries(existingPermission, "ask");
|
|
19257
|
+
for (const [entry, action] of ranked) if (action === "allow") allow.push(entry);
|
|
19258
|
+
else if (action === "deny") deny.push(entry);
|
|
19259
|
+
else ask.push(entry);
|
|
19260
|
+
return {
|
|
19261
|
+
allow: (0, es_toolkit.uniq)(allow.toSorted()),
|
|
19262
|
+
deny: (0, es_toolkit.uniq)(deny.toSorted()),
|
|
19263
|
+
ask: (0, es_toolkit.uniq)(ask.toSorted())
|
|
19264
|
+
};
|
|
19265
|
+
}
|
|
19266
|
+
/**
|
|
19267
|
+
* Parse Grok's `[permission]` allow/deny/ask arrays back into a canonical
|
|
19268
|
+
* permission map. Returns `null` when the section defines none of the three
|
|
19269
|
+
* arrays, so the caller can fall back to the coarse `permission_mode`.
|
|
19270
|
+
* Precedence `deny > ask > allow` is applied so a tool listed in multiple
|
|
19271
|
+
* arrays resolves to the strictest action.
|
|
19272
|
+
*/
|
|
19273
|
+
function parseGrokPermissionArrays(permission) {
|
|
19274
|
+
const allow = isStringArray(permission.allow) ? permission.allow : void 0;
|
|
19275
|
+
const deny = isStringArray(permission.deny) ? permission.deny : void 0;
|
|
19276
|
+
const ask = isStringArray(permission.ask) ? permission.ask : void 0;
|
|
19277
|
+
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
|
|
19278
|
+
const result = {};
|
|
19279
|
+
const apply = (entries, action) => {
|
|
19280
|
+
for (const entry of entries ?? []) {
|
|
19281
|
+
const parsed = parseGrokEntry(entry);
|
|
19282
|
+
if (parsed === null) continue;
|
|
19283
|
+
const bucket = result[parsed.category] ??= {};
|
|
19284
|
+
bucket[parsed.pattern] = action;
|
|
19285
|
+
}
|
|
19286
|
+
};
|
|
19287
|
+
apply(allow, "allow");
|
|
19288
|
+
apply(ask, "ask");
|
|
19289
|
+
apply(deny, "deny");
|
|
19290
|
+
return result;
|
|
19291
|
+
}
|
|
19292
|
+
/**
|
|
19293
|
+
* Legacy coarse fallback: map `[ui] permission_mode` to a canonical action.
|
|
19294
|
+
* `always-approve` ⇒ `allow`; anything else (including a missing mode) ⇒ `ask`.
|
|
19295
|
+
*/
|
|
19296
|
+
function legacyModeAction(parsed) {
|
|
19297
|
+
return (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
19298
|
+
}
|
|
18813
19299
|
/**
|
|
18814
19300
|
* Collapse a rulesync permissions config into Grok's single coarse mode.
|
|
18815
19301
|
* Any `deny`/`ask` rule anywhere keeps prompting (`ask`); otherwise an existing
|
|
@@ -18825,6 +19311,10 @@ function deriveGrokPermissionMode(config) {
|
|
|
18825
19311
|
}
|
|
18826
19312
|
//#endregion
|
|
18827
19313
|
//#region src/features/permissions/hermesagent-permissions.ts
|
|
19314
|
+
/** Collect the glob patterns in a canonical category that carry a given action. */
|
|
19315
|
+
function patternsByAction(category, action) {
|
|
19316
|
+
return Object.entries(category ?? {}).filter(([, value]) => value === action).map(([pattern]) => pattern);
|
|
19317
|
+
}
|
|
18828
19318
|
var HermesagentPermissions = class HermesagentPermissions extends ToolPermissions {
|
|
18829
19319
|
static getSettablePaths() {
|
|
18830
19320
|
return {
|
|
@@ -18848,7 +19338,11 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18848
19338
|
return true;
|
|
18849
19339
|
}
|
|
18850
19340
|
setFileContent(fileContent) {
|
|
18851
|
-
|
|
19341
|
+
const existing = parseHermesConfig(fileContent);
|
|
19342
|
+
const generated = parseHermesConfig(this.fileContent);
|
|
19343
|
+
const merged = deepMergeHermesConfig(existing, generated);
|
|
19344
|
+
if (generated.permissions !== void 0) merged.permissions = generated.permissions;
|
|
19345
|
+
this.fileContent = stringifyHermesConfig(merged);
|
|
18852
19346
|
}
|
|
18853
19347
|
toRulesyncPermissions() {
|
|
18854
19348
|
const config = parseHermesConfig(this.getFileContent());
|
|
@@ -18861,12 +19355,22 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18861
19355
|
}
|
|
18862
19356
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions }) {
|
|
18863
19357
|
const permissions = rulesyncPermissions.getJson();
|
|
19358
|
+
const permissionBlock = permissions.permission ?? {};
|
|
19359
|
+
const commandAllowlist = Object.entries(permissionBlock).flatMap(([, patterns]) => patternsByAction(patterns, "allow"));
|
|
19360
|
+
const bashDeny = patternsByAction(permissionBlock.bash, "deny");
|
|
19361
|
+
const webfetchDeny = patternsByAction(permissionBlock.webfetch, "deny");
|
|
19362
|
+
let config = {};
|
|
19363
|
+
if (commandAllowlist.length > 0) config.command_allowlist = commandAllowlist;
|
|
19364
|
+
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
19365
|
+
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
19366
|
+
enabled: true,
|
|
19367
|
+
domains: webfetchDeny
|
|
19368
|
+
} };
|
|
19369
|
+
if (permissions.hermes && typeof permissions.hermes === "object") config = deepMergeHermesConfig(config, permissions.hermes);
|
|
19370
|
+
config.permissions = { rulesync: permissions };
|
|
18864
19371
|
return new HermesagentPermissions({
|
|
18865
19372
|
outputRoot,
|
|
18866
|
-
fileContent: stringifyHermesConfig(
|
|
18867
|
-
command_allowlist: Object.entries(permissions.permission ?? {}).flatMap(([, patterns]) => Object.entries(patterns).filter(([, action]) => action === "allow").map(([command]) => command)),
|
|
18868
|
-
permissions: { rulesync: permissions }
|
|
18869
|
-
})
|
|
19373
|
+
fileContent: stringifyHermesConfig(config)
|
|
18870
19374
|
});
|
|
18871
19375
|
}
|
|
18872
19376
|
};
|
|
@@ -18887,14 +19391,18 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18887
19391
|
* "executables": [ { "prefix": "git ", "action": "allow" } ],
|
|
18888
19392
|
* "fileEditing": [ { "pattern": "src/**", "action": "allow" } ],
|
|
18889
19393
|
* "mcpTools": [ { "prefix": "search", "action": "allow" } ],
|
|
18890
|
-
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "
|
|
19394
|
+
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "ask" } ]
|
|
18891
19395
|
* }
|
|
18892
19396
|
* }
|
|
18893
19397
|
* ```
|
|
18894
19398
|
*
|
|
18895
19399
|
* Each rule carries a literal `prefix` (matches commands that start with it) or
|
|
18896
|
-
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action
|
|
18897
|
-
*
|
|
19400
|
+
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action`. Junie
|
|
19401
|
+
* documents only `allow` and `ask` as valid actions — there is **no `deny`**
|
|
19402
|
+
* (https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html). rulesync's
|
|
19403
|
+
* canonical `allow`/`ask` map 1:1; a canonical `deny` has no Junie equivalent
|
|
19404
|
+
* and is mapped to the nearest valid action, `ask` (still withholds
|
|
19405
|
+
* auto-approval), with a warning so the downgrade is surfaced.
|
|
18898
19406
|
*
|
|
18899
19407
|
* Category mapping (rulesync canonical <-> Junie rule group):
|
|
18900
19408
|
* - `bash` <-> `executables`
|
|
@@ -18904,8 +19412,11 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18904
19412
|
*
|
|
18905
19413
|
* Categories Junie cannot represent (e.g. `webfetch`) are skipped on export
|
|
18906
19414
|
* (with a warning when they carry rules). The top-level `defaultBehavior` and
|
|
18907
|
-
* `allowReadonlyCommands` settings have no canonical
|
|
18908
|
-
*
|
|
19415
|
+
* `allowReadonlyCommands` settings have no canonical per-glob slot: they are
|
|
19416
|
+
* authored and round-tripped through the `junie` override namespace (see
|
|
19417
|
+
* `JuniePermissionsOverrideSchema`) — lifted into the override on import and
|
|
19418
|
+
* merged back onto the top level on export — and any other unmodeled top-level
|
|
19419
|
+
* key is preserved verbatim.
|
|
18909
19420
|
*
|
|
18910
19421
|
* @see https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html
|
|
18911
19422
|
*/
|
|
@@ -18928,7 +19439,6 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
18928
19439
|
mcpTools: "mcp",
|
|
18929
19440
|
readOutsideProject: "read"
|
|
18930
19441
|
};
|
|
18931
|
-
const JUNIE_DEFAULT_BEHAVIOR = "ask";
|
|
18932
19442
|
function isPermissionAction$1(value) {
|
|
18933
19443
|
return PermissionActionSchema.safeParse(value).success;
|
|
18934
19444
|
}
|
|
@@ -18978,13 +19488,16 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
18978
19488
|
} catch (error) {
|
|
18979
19489
|
throw new Error(`Failed to parse existing Junie allowlist at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18980
19490
|
}
|
|
19491
|
+
const config = rulesyncPermissions.getJson();
|
|
18981
19492
|
const rules = convertRulesyncToJunieRules({
|
|
18982
|
-
config
|
|
19493
|
+
config,
|
|
18983
19494
|
logger
|
|
18984
19495
|
});
|
|
19496
|
+
const override = config.junie;
|
|
19497
|
+
const overrideObj = override !== void 0 && typeof override === "object" ? override : {};
|
|
18985
19498
|
const merged = {
|
|
18986
19499
|
...existing,
|
|
18987
|
-
|
|
19500
|
+
...overrideObj,
|
|
18988
19501
|
rules
|
|
18989
19502
|
};
|
|
18990
19503
|
return new JuniePermissions({
|
|
@@ -19004,7 +19517,12 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
19004
19517
|
throw new Error(`Failed to parse Junie permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
19005
19518
|
}
|
|
19006
19519
|
const config = convertJunieToRulesyncPermissions({ allowlist });
|
|
19007
|
-
|
|
19520
|
+
const junieOverride = {};
|
|
19521
|
+
if (typeof allowlist.allowReadonlyCommands === "boolean") junieOverride.allowReadonlyCommands = allowlist.allowReadonlyCommands;
|
|
19522
|
+
if (typeof allowlist.defaultBehavior === "string") junieOverride.defaultBehavior = allowlist.defaultBehavior;
|
|
19523
|
+
const result = { ...config };
|
|
19524
|
+
if (Object.keys(junieOverride).length > 0) result.junie = junieOverride;
|
|
19525
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19008
19526
|
}
|
|
19009
19527
|
validate() {
|
|
19010
19528
|
return {
|
|
@@ -19036,12 +19554,13 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19036
19554
|
continue;
|
|
19037
19555
|
}
|
|
19038
19556
|
for (const [pattern, action] of Object.entries(patterns)) {
|
|
19557
|
+
const junieAction = toJunieAction(action, category, pattern, logger);
|
|
19039
19558
|
const rule = isGlobPattern(pattern) ? {
|
|
19040
19559
|
pattern,
|
|
19041
|
-
action
|
|
19560
|
+
action: junieAction
|
|
19042
19561
|
} : {
|
|
19043
19562
|
prefix: pattern,
|
|
19044
|
-
action
|
|
19563
|
+
action: junieAction
|
|
19045
19564
|
};
|
|
19046
19565
|
(rules[group] ??= []).push(rule);
|
|
19047
19566
|
}
|
|
@@ -19049,6 +19568,19 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19049
19568
|
return rules;
|
|
19050
19569
|
}
|
|
19051
19570
|
/**
|
|
19571
|
+
* Map a canonical action onto a valid Junie allowlist action. Junie supports
|
|
19572
|
+
* only `allow` and `ask`; a canonical `deny` is downgraded to `ask` (the
|
|
19573
|
+
* nearest valid action — both withhold auto-approval) with a warning, so
|
|
19574
|
+
* rulesync never emits a `deny` that Junie would silently ignore.
|
|
19575
|
+
*/
|
|
19576
|
+
function toJunieAction(action, category, pattern, logger) {
|
|
19577
|
+
if (action === "deny") {
|
|
19578
|
+
logger?.warn(`Junie's allowlist supports only 'allow'/'ask' actions; the '${category}' deny rule for '${pattern}' was downgraded to 'ask' (Junie has no 'deny').`);
|
|
19579
|
+
return "ask";
|
|
19580
|
+
}
|
|
19581
|
+
return action;
|
|
19582
|
+
}
|
|
19583
|
+
/**
|
|
19052
19584
|
* Convert a Junie allowlist back into rulesync permissions config. The
|
|
19053
19585
|
* top-level `defaultBehavior` / `allowReadonlyCommands` settings have no
|
|
19054
19586
|
* canonical equivalent and are not imported.
|
|
@@ -19082,6 +19614,31 @@ const KiloPermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
19082
19614
|
]))]);
|
|
19083
19615
|
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), KiloPermissionSchema)) });
|
|
19084
19616
|
/**
|
|
19617
|
+
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
19618
|
+
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
19619
|
+
* keys such as `external_directory`, `doom_loop`, `notebook_edit`, ...) is
|
|
19620
|
+
* routed into the `kilo` override on import so it does not leak into other
|
|
19621
|
+
* tools' configs. Kilo folds `write` into `edit`, uses `notebook_edit`/`task`
|
|
19622
|
+
* rather than the canonical `notebookedit`/`agent`, and has no `mcp` key (MCP is
|
|
19623
|
+
* addressed via `mcp__*` tool-name keys), so those canonical names are not Kilo
|
|
19624
|
+
* keys in practice — they simply never appear on the shared side.
|
|
19625
|
+
*/
|
|
19626
|
+
const KILO_SHARED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
19627
|
+
"bash",
|
|
19628
|
+
"read",
|
|
19629
|
+
"edit",
|
|
19630
|
+
"write",
|
|
19631
|
+
"webfetch",
|
|
19632
|
+
"websearch",
|
|
19633
|
+
"grep",
|
|
19634
|
+
"glob",
|
|
19635
|
+
"notebookedit",
|
|
19636
|
+
"agent"
|
|
19637
|
+
]);
|
|
19638
|
+
function isSharedKiloCategory(key) {
|
|
19639
|
+
return key === "*" || KILO_SHARED_CATEGORIES.has(key) || key.startsWith("mcp__");
|
|
19640
|
+
}
|
|
19641
|
+
/**
|
|
19085
19642
|
* Parse a JSONC string and throw on syntax errors. The `jsonc-parser` `parse()` function is
|
|
19086
19643
|
* non-throwing best-effort: invalid input silently yields a partial value (often `undefined`,
|
|
19087
19644
|
* coerced to `{}` by callers). That behavior would silently drop a user's existing `deny` rules
|
|
@@ -19193,12 +19750,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19193
19750
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
19194
19751
|
const parsedPermission = parsed.permission;
|
|
19195
19752
|
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
19196
|
-
const
|
|
19753
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
19754
|
+
const kiloOverride = rulesyncJson.kilo;
|
|
19755
|
+
const incomingPermission = {
|
|
19756
|
+
...rulesyncJson.permission,
|
|
19757
|
+
...kiloOverride?.permission
|
|
19758
|
+
};
|
|
19197
19759
|
const droppedDenyByKey = {};
|
|
19198
|
-
for (const key of Object.
|
|
19199
|
-
const
|
|
19200
|
-
const
|
|
19201
|
-
const nextDenyPatterns = new Set(collectKiloDenyPatterns(rulesyncPermission[key]));
|
|
19760
|
+
for (const [key, value] of Object.entries(incomingPermission)) {
|
|
19761
|
+
const previousDenyPatterns = collectKiloDenyPatterns(existingPermission[key]);
|
|
19762
|
+
const nextDenyPatterns = new Set(collectKiloDenyPatterns(value));
|
|
19202
19763
|
const dropped = previousDenyPatterns.filter((p) => !nextDenyPatterns.has(p));
|
|
19203
19764
|
if (dropped.length > 0) droppedDenyByKey[key] = dropped;
|
|
19204
19765
|
}
|
|
@@ -19206,8 +19767,10 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19206
19767
|
const summary = Object.entries(droppedDenyByKey).map(([key, patterns]) => `${key}: [${patterns.join(", ")}]`).join("; ");
|
|
19207
19768
|
logger?.warn(`WARNING: Kilo permissions regeneration drops existing 'deny' rule(s) because rulesync output owns these tool keys. Dropped — ${summary}. To preserve these denies, add them to '.rulesync/permissions.json'.`);
|
|
19208
19769
|
}
|
|
19209
|
-
const mergedPermission = {
|
|
19210
|
-
|
|
19770
|
+
const mergedPermission = {
|
|
19771
|
+
...existingPermission,
|
|
19772
|
+
...incomingPermission
|
|
19773
|
+
};
|
|
19211
19774
|
const nextJson = {
|
|
19212
19775
|
...parsed,
|
|
19213
19776
|
permission: mergedPermission
|
|
@@ -19221,8 +19784,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19221
19784
|
});
|
|
19222
19785
|
}
|
|
19223
19786
|
toRulesyncPermissions() {
|
|
19224
|
-
const
|
|
19225
|
-
|
|
19787
|
+
const rawPermission = this.json.permission ?? {};
|
|
19788
|
+
const shared = {};
|
|
19789
|
+
const overrideOnly = {};
|
|
19790
|
+
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
19791
|
+
else overrideOnly[key] = value;
|
|
19792
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
19793
|
+
permission: shared,
|
|
19794
|
+
kilo: { permission: overrideOnly }
|
|
19795
|
+
} : { permission: shared };
|
|
19796
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19226
19797
|
}
|
|
19227
19798
|
validate() {
|
|
19228
19799
|
try {
|
|
@@ -19252,10 +19823,6 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19252
19823
|
validate: false
|
|
19253
19824
|
});
|
|
19254
19825
|
}
|
|
19255
|
-
normalizePermission(permission) {
|
|
19256
|
-
if (!permission) return {};
|
|
19257
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
19258
|
-
}
|
|
19259
19826
|
};
|
|
19260
19827
|
//#endregion
|
|
19261
19828
|
//#region src/features/permissions/kiro-permissions.ts
|
|
@@ -19313,29 +19880,16 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19313
19880
|
}
|
|
19314
19881
|
const permission = {};
|
|
19315
19882
|
const toolsSettings = parsed.toolsSettings ?? {};
|
|
19316
|
-
const
|
|
19317
|
-
|
|
19318
|
-
const
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
const readDeny = asStringArray(readSettings.deniedPaths);
|
|
19327
|
-
if (readAllow.length > 0 || readDeny.length > 0) {
|
|
19328
|
-
permission.read = {};
|
|
19329
|
-
for (const pattern of readAllow) permission.read[pattern] = "allow";
|
|
19330
|
-
for (const pattern of readDeny) permission.read[pattern] = "deny";
|
|
19331
|
-
}
|
|
19332
|
-
const writeSettings = asRecord$1(toolsSettings.write);
|
|
19333
|
-
const writeAllow = asStringArray(writeSettings.allowedPaths);
|
|
19334
|
-
const writeDeny = asStringArray(writeSettings.deniedPaths);
|
|
19335
|
-
if (writeAllow.length > 0 || writeDeny.length > 0) {
|
|
19336
|
-
permission.write = {};
|
|
19337
|
-
for (const pattern of writeAllow) permission.write[pattern] = "allow";
|
|
19338
|
-
for (const pattern of writeDeny) permission.write[pattern] = "deny";
|
|
19883
|
+
const shellRules = rulesFromArrays(asRecord$1(toolsSettings.shell), "allowedCommands", "deniedCommands");
|
|
19884
|
+
if (Object.keys(shellRules).length > 0) permission.bash = shellRules;
|
|
19885
|
+
for (const category of [
|
|
19886
|
+
"read",
|
|
19887
|
+
"write",
|
|
19888
|
+
"grep",
|
|
19889
|
+
"glob"
|
|
19890
|
+
]) {
|
|
19891
|
+
const rules = rulesFromArrays(asRecord$1(toolsSettings[category]), "allowedPaths", "deniedPaths");
|
|
19892
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
19339
19893
|
}
|
|
19340
19894
|
const allowedTools = new Set(parsed.allowedTools ?? []);
|
|
19341
19895
|
if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
|
|
@@ -19361,45 +19915,63 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19361
19915
|
function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
|
|
19362
19916
|
const nextAllowedTools = new Set(existing.allowedTools ?? []);
|
|
19363
19917
|
const nextToolsSettings = { ...asRecord$1(existing.toolsSettings) };
|
|
19918
|
+
const pathBuckets = {};
|
|
19919
|
+
const pushPath = (key, action, pattern) => {
|
|
19920
|
+
const bucket = pathBuckets[key] ??= {
|
|
19921
|
+
allow: [],
|
|
19922
|
+
deny: []
|
|
19923
|
+
};
|
|
19924
|
+
(action === "allow" ? bucket.allow : bucket.deny).push(pattern);
|
|
19925
|
+
};
|
|
19364
19926
|
const shell = {
|
|
19365
19927
|
allowedCommands: [],
|
|
19366
19928
|
deniedCommands: []
|
|
19367
19929
|
};
|
|
19368
|
-
const read = {
|
|
19369
|
-
allowedPaths: [],
|
|
19370
|
-
deniedPaths: []
|
|
19371
|
-
};
|
|
19372
|
-
const write = {
|
|
19373
|
-
allowedPaths: [],
|
|
19374
|
-
deniedPaths: []
|
|
19375
|
-
};
|
|
19376
19930
|
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19377
19931
|
if (action === "ask") {
|
|
19378
19932
|
logger?.warn(`Kiro permissions do not support "ask". Skipping ${category}:${pattern}`);
|
|
19379
19933
|
continue;
|
|
19380
19934
|
}
|
|
19381
19935
|
if (category === "bash") (action === "allow" ? shell.allowedCommands : shell.deniedCommands).push(pattern);
|
|
19382
|
-
else if (category === "read"
|
|
19383
|
-
else if (category === "edit" || category === "write") (
|
|
19384
|
-
else if (category === "webfetch" || category === "websearch") {
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
} else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
19936
|
+
else if (category === "read" || category === "grep" || category === "glob") pushPath(category, action, pattern);
|
|
19937
|
+
else if (category === "edit" || category === "write") pushPath("write", action, pattern);
|
|
19938
|
+
else if (category === "webfetch" || category === "websearch") applyKiroWebPermission({
|
|
19939
|
+
category,
|
|
19940
|
+
pattern,
|
|
19941
|
+
action,
|
|
19942
|
+
nextAllowedTools,
|
|
19943
|
+
logger
|
|
19944
|
+
});
|
|
19945
|
+
else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
19393
19946
|
}
|
|
19394
19947
|
nextToolsSettings.shell = shell;
|
|
19395
|
-
nextToolsSettings.read = read;
|
|
19396
|
-
nextToolsSettings.write = write;
|
|
19948
|
+
nextToolsSettings.read = pathTable(pathBuckets.read);
|
|
19949
|
+
nextToolsSettings.write = pathTable(pathBuckets.write);
|
|
19950
|
+
for (const key of ["grep", "glob"]) {
|
|
19951
|
+
const bucket = pathBuckets[key];
|
|
19952
|
+
if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
|
|
19953
|
+
}
|
|
19397
19954
|
return {
|
|
19398
19955
|
...existing,
|
|
19399
19956
|
allowedTools: [...nextAllowedTools].toSorted(),
|
|
19400
19957
|
toolsSettings: nextToolsSettings
|
|
19401
19958
|
};
|
|
19402
19959
|
}
|
|
19960
|
+
function pathTable(bucket) {
|
|
19961
|
+
return {
|
|
19962
|
+
allowedPaths: bucket?.allow ?? [],
|
|
19963
|
+
deniedPaths: bucket?.deny ?? []
|
|
19964
|
+
};
|
|
19965
|
+
}
|
|
19966
|
+
function applyKiroWebPermission({ category, pattern, action, nextAllowedTools, logger }) {
|
|
19967
|
+
if (pattern !== "*") {
|
|
19968
|
+
logger?.warn(`Kiro ${category} supports only wildcard (*) via allowedTools. Skipping rule: ${pattern}`);
|
|
19969
|
+
return;
|
|
19970
|
+
}
|
|
19971
|
+
const toolName = category === "webfetch" ? "web_fetch" : "web_search";
|
|
19972
|
+
if (action === "allow") nextAllowedTools.add(toolName);
|
|
19973
|
+
else nextAllowedTools.delete(toolName);
|
|
19974
|
+
}
|
|
19403
19975
|
function asRecord$1(value) {
|
|
19404
19976
|
const result = UnknownRecordSchema.safeParse(value);
|
|
19405
19977
|
return result.success ? result.data : {};
|
|
@@ -19407,6 +19979,17 @@ function asRecord$1(value) {
|
|
|
19407
19979
|
function asStringArray(value) {
|
|
19408
19980
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19409
19981
|
}
|
|
19982
|
+
/**
|
|
19983
|
+
* Build a canonical `{ pattern: action }` map from a Kiro tool settings record's
|
|
19984
|
+
* allow/deny string arrays (e.g. `allowedPaths`/`deniedPaths` or
|
|
19985
|
+
* `allowedCommands`/`deniedCommands`).
|
|
19986
|
+
*/
|
|
19987
|
+
function rulesFromArrays(settings, allowKey, denyKey) {
|
|
19988
|
+
const rules = {};
|
|
19989
|
+
for (const pattern of asStringArray(settings[allowKey])) rules[pattern] = "allow";
|
|
19990
|
+
for (const pattern of asStringArray(settings[denyKey])) rules[pattern] = "deny";
|
|
19991
|
+
return rules;
|
|
19992
|
+
}
|
|
19410
19993
|
//#endregion
|
|
19411
19994
|
//#region src/features/permissions/opencode-permissions.ts
|
|
19412
19995
|
const OpencodePermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
@@ -19418,6 +20001,28 @@ const OpencodePermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
19418
20001
|
"ask",
|
|
19419
20002
|
"deny"
|
|
19420
20003
|
]))]);
|
|
20004
|
+
/**
|
|
20005
|
+
* Canonical rulesync permission categories that carry a cross-tool meaning (see
|
|
20006
|
+
* the "Supported tool categories" list in `docs/reference/file-formats.md`).
|
|
20007
|
+
* On import, any OpenCode category outside this set — plus MCP tool names — is
|
|
20008
|
+
* treated as OpenCode-only and routed into the `opencode` override block so a
|
|
20009
|
+
* subsequent `rulesync generate` does not leak it into other tools' configs.
|
|
20010
|
+
*/
|
|
20011
|
+
const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
20012
|
+
"bash",
|
|
20013
|
+
"read",
|
|
20014
|
+
"edit",
|
|
20015
|
+
"write",
|
|
20016
|
+
"webfetch",
|
|
20017
|
+
"websearch",
|
|
20018
|
+
"grep",
|
|
20019
|
+
"glob",
|
|
20020
|
+
"notebookedit",
|
|
20021
|
+
"agent"
|
|
20022
|
+
]);
|
|
20023
|
+
function isSharedPermissionCategory(category) {
|
|
20024
|
+
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
20025
|
+
}
|
|
19421
20026
|
const OpencodePermissionsConfigSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.enum([
|
|
19422
20027
|
"allow",
|
|
19423
20028
|
"ask",
|
|
@@ -19479,9 +20084,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19479
20084
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
19480
20085
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
19481
20086
|
}
|
|
20087
|
+
const parsed = (0, jsonc_parser.parse)(fileContent ?? "{}");
|
|
20088
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20089
|
+
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
19482
20090
|
const nextJson = {
|
|
19483
|
-
...
|
|
19484
|
-
permission:
|
|
20091
|
+
...parsed,
|
|
20092
|
+
permission: {
|
|
20093
|
+
...rulesyncJson.permission,
|
|
20094
|
+
...overridePermission
|
|
20095
|
+
}
|
|
19485
20096
|
};
|
|
19486
20097
|
return new OpencodePermissions({
|
|
19487
20098
|
outputRoot,
|
|
@@ -19492,8 +20103,20 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19492
20103
|
});
|
|
19493
20104
|
}
|
|
19494
20105
|
toRulesyncPermissions() {
|
|
19495
|
-
const
|
|
19496
|
-
|
|
20106
|
+
const rawPermission = this.json.permission;
|
|
20107
|
+
if (rawPermission === void 0 || typeof rawPermission === "string") {
|
|
20108
|
+
const permission = this.normalizePermission(rawPermission);
|
|
20109
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
20110
|
+
}
|
|
20111
|
+
const shared = {};
|
|
20112
|
+
const overrideOnly = {};
|
|
20113
|
+
for (const [category, value] of Object.entries(rawPermission)) if (isSharedPermissionCategory(category)) shared[category] = typeof value === "string" ? { "*": value } : value;
|
|
20114
|
+
else overrideOnly[category] = value;
|
|
20115
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20116
|
+
permission: shared,
|
|
20117
|
+
opencode: { permission: overrideOnly }
|
|
20118
|
+
} : { permission: shared };
|
|
20119
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19497
20120
|
}
|
|
19498
20121
|
validate() {
|
|
19499
20122
|
try {
|
|
@@ -19523,10 +20146,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19523
20146
|
validate: false
|
|
19524
20147
|
});
|
|
19525
20148
|
}
|
|
20149
|
+
/**
|
|
20150
|
+
* Normalize the uniform/undefined forms of OpenCode's `permission` field into
|
|
20151
|
+
* the canonical rulesync shape. The object form is handled directly in
|
|
20152
|
+
* `toRulesyncPermissions` (it needs to split shared vs OpenCode-only
|
|
20153
|
+
* categories), so this only covers the two remaining cases.
|
|
20154
|
+
*/
|
|
19526
20155
|
normalizePermission(permission) {
|
|
19527
20156
|
if (!permission) return {};
|
|
19528
|
-
|
|
19529
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
20157
|
+
return { "*": { "*": permission } };
|
|
19530
20158
|
}
|
|
19531
20159
|
};
|
|
19532
20160
|
//#endregion
|
|
@@ -19600,6 +20228,24 @@ function buildQwenPermissionEntry(toolName, pattern) {
|
|
|
19600
20228
|
if (pattern === "*") return toolName;
|
|
19601
20229
|
return `${toolName}(${pattern})`;
|
|
19602
20230
|
}
|
|
20231
|
+
const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
20232
|
+
"approvalMode",
|
|
20233
|
+
"autoAccept",
|
|
20234
|
+
"sandbox",
|
|
20235
|
+
"sandboxImage",
|
|
20236
|
+
"disabled"
|
|
20237
|
+
];
|
|
20238
|
+
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
20239
|
+
function asPlainRecord(value) {
|
|
20240
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20241
|
+
}
|
|
20242
|
+
/** Pick the override-managed keys out of a settings group into a fresh record. */
|
|
20243
|
+
function pickQwenOverrideKeys(group, keys) {
|
|
20244
|
+
const source = asPlainRecord(group);
|
|
20245
|
+
const picked = {};
|
|
20246
|
+
for (const key of keys) if (source[key] !== void 0) picked[key] = source[key];
|
|
20247
|
+
return picked;
|
|
20248
|
+
}
|
|
19603
20249
|
var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
19604
20250
|
constructor(params) {
|
|
19605
20251
|
super({
|
|
@@ -19665,6 +20311,15 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19665
20311
|
...settings,
|
|
19666
20312
|
permissions: mergedPermissions
|
|
19667
20313
|
};
|
|
20314
|
+
const override = config.qwencode;
|
|
20315
|
+
if (override?.tools !== void 0) merged.tools = {
|
|
20316
|
+
...asPlainRecord(settings.tools),
|
|
20317
|
+
...asPlainRecord(override.tools)
|
|
20318
|
+
};
|
|
20319
|
+
if (override?.security !== void 0) merged.security = {
|
|
20320
|
+
...asPlainRecord(settings.security),
|
|
20321
|
+
...asPlainRecord(override.security)
|
|
20322
|
+
};
|
|
19668
20323
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
19669
20324
|
return new QwencodePermissions({
|
|
19670
20325
|
outputRoot,
|
|
@@ -19690,7 +20345,14 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19690
20345
|
ask: permissions.ask ?? [],
|
|
19691
20346
|
deny: permissions.deny ?? []
|
|
19692
20347
|
});
|
|
19693
|
-
|
|
20348
|
+
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
20349
|
+
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
20350
|
+
const qwencodeOverride = {};
|
|
20351
|
+
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
20352
|
+
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
20353
|
+
const result = { ...config };
|
|
20354
|
+
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
20355
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19694
20356
|
}
|
|
19695
20357
|
validate() {
|
|
19696
20358
|
try {
|
|
@@ -19851,6 +20513,16 @@ function toPermissionsTable(value) {
|
|
|
19851
20513
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
19852
20514
|
return { ...value };
|
|
19853
20515
|
}
|
|
20516
|
+
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_allowed_tools", "plan_mode_read_only_commands"];
|
|
20517
|
+
function asReasonixRecord(value) {
|
|
20518
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20519
|
+
}
|
|
20520
|
+
function pickReasonixKeys(source, keys) {
|
|
20521
|
+
const record = asReasonixRecord(source);
|
|
20522
|
+
const picked = {};
|
|
20523
|
+
for (const key of keys) if (record[key] !== void 0) picked[key] = record[key];
|
|
20524
|
+
return picked;
|
|
20525
|
+
}
|
|
19854
20526
|
var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
19855
20527
|
toml;
|
|
19856
20528
|
constructor(params) {
|
|
@@ -19912,6 +20584,15 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19912
20584
|
...parsed,
|
|
19913
20585
|
permissions: mergedPermissions
|
|
19914
20586
|
};
|
|
20587
|
+
const override = config.reasonix;
|
|
20588
|
+
if (override?.sandbox !== void 0) merged.sandbox = {
|
|
20589
|
+
...asReasonixRecord(parsed.sandbox),
|
|
20590
|
+
...asReasonixRecord(override.sandbox)
|
|
20591
|
+
};
|
|
20592
|
+
if (override?.agent !== void 0) merged.agent = {
|
|
20593
|
+
...asReasonixRecord(parsed.agent),
|
|
20594
|
+
...asReasonixRecord(override.agent)
|
|
20595
|
+
};
|
|
19915
20596
|
const fileContent = smol_toml.stringify(merged);
|
|
19916
20597
|
return new ReasonixPermissions({
|
|
19917
20598
|
outputRoot,
|
|
@@ -19928,7 +20609,14 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19928
20609
|
ask: toStringArray$1(permissions.ask),
|
|
19929
20610
|
deny: toStringArray$1(permissions.deny)
|
|
19930
20611
|
});
|
|
19931
|
-
|
|
20612
|
+
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
20613
|
+
const agentPlanMode = pickReasonixKeys(this.toml.agent, REASONIX_OVERRIDE_AGENT_KEYS);
|
|
20614
|
+
const reasonixOverride = {};
|
|
20615
|
+
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
20616
|
+
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
20617
|
+
const result = { ...config };
|
|
20618
|
+
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
20619
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19932
20620
|
}
|
|
19933
20621
|
validate() {
|
|
19934
20622
|
try {
|
|
@@ -20492,6 +21180,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20492
21180
|
if (deny.size > 0) nextTool.denylist = [...deny].toSorted();
|
|
20493
21181
|
tools[vibeToolName] = nextTool;
|
|
20494
21182
|
}
|
|
21183
|
+
applyVibeSensitivePatterns(tools, rulesyncPermissions.getJson().vibe);
|
|
20495
21184
|
config.tools = tools;
|
|
20496
21185
|
if (enabledTools.size > 0) config.enabled_tools = [...enabledTools].toSorted();
|
|
20497
21186
|
else delete config.enabled_tools;
|
|
@@ -20508,16 +21197,25 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20508
21197
|
}
|
|
20509
21198
|
toRulesyncPermissions() {
|
|
20510
21199
|
const permission = {};
|
|
21200
|
+
const vibeOverridePermission = {};
|
|
20511
21201
|
for (const tool of toStringArray(this.toml.enabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "allow";
|
|
20512
21202
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
20513
21203
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
20514
|
-
const
|
|
21204
|
+
const category = toCanonicalToolName$1(vibeToolName);
|
|
21205
|
+
const rules = ensurePermission(permission, category);
|
|
20515
21206
|
const action = fromVibePermission(toolConfig.permission);
|
|
20516
21207
|
if (action !== void 0) rules["*"] = action;
|
|
20517
21208
|
for (const pattern of toStringArray(toolConfig.allow ?? toolConfig.allowlist)) rules[pattern] = "allow";
|
|
20518
21209
|
for (const pattern of toStringArray(toolConfig.deny ?? toolConfig.denylist)) rules[pattern] = "deny";
|
|
21210
|
+
const sensitivePatterns = toStringArray(toolConfig.sensitive_patterns);
|
|
21211
|
+
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
20519
21212
|
}
|
|
20520
|
-
|
|
21213
|
+
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
21214
|
+
const json = Object.keys(vibeOverridePermission).length > 0 ? {
|
|
21215
|
+
permission,
|
|
21216
|
+
vibe: { permission: vibeOverridePermission }
|
|
21217
|
+
} : { permission };
|
|
21218
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
20521
21219
|
}
|
|
20522
21220
|
validate() {
|
|
20523
21221
|
try {
|
|
@@ -20544,6 +21242,22 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20544
21242
|
});
|
|
20545
21243
|
}
|
|
20546
21244
|
};
|
|
21245
|
+
/**
|
|
21246
|
+
* Apply the Vibe-scoped override's per-tool `sensitive_patterns` (patterns that
|
|
21247
|
+
* escalate to ASK even when the base permission is ALWAYS). rulesync owns this
|
|
21248
|
+
* list for any category the override names: a present list is set, an empty
|
|
21249
|
+
* one clears it. Categories not named keep whatever the existing file had.
|
|
21250
|
+
*/
|
|
21251
|
+
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
21252
|
+
for (const [category, toolOverride] of Object.entries(vibeOverride?.permission ?? {})) {
|
|
21253
|
+
const vibeToolName = toVibeToolName(category);
|
|
21254
|
+
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
21255
|
+
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
21256
|
+
if (patterns.length > 0) nextTool.sensitive_patterns = [...patterns].toSorted();
|
|
21257
|
+
else delete nextTool.sensitive_patterns;
|
|
21258
|
+
tools[vibeToolName] = nextTool;
|
|
21259
|
+
}
|
|
21260
|
+
}
|
|
20547
21261
|
function parseVibeConfig(fileContent) {
|
|
20548
21262
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
20549
21263
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -20588,6 +21302,11 @@ function ensurePermission(permission, category) {
|
|
|
20588
21302
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
20589
21303
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
20590
21304
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
21305
|
+
const WARP_OVERRIDE_KEYS = [
|
|
21306
|
+
"agent_mode_coding_permissions",
|
|
21307
|
+
"agent_mode_coding_file_read_allowlist",
|
|
21308
|
+
"agent_mode_execute_readonly_commands"
|
|
21309
|
+
];
|
|
20591
21310
|
/**
|
|
20592
21311
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
20593
21312
|
* channel). The home directory is resolved by the processor through
|
|
@@ -20623,8 +21342,16 @@ function warpSettingsDir() {
|
|
|
20623
21342
|
* patterns as regexes when targeting Warp (mirrors the Zed permissions
|
|
20624
21343
|
* adapter). Warp has no per-command "ask" list, so `ask` rules are dropped; and
|
|
20625
21344
|
* the command lists only model shell commands, so non-`bash` categories are
|
|
20626
|
-
* skipped (with a warning when they carry `deny` rules).
|
|
20627
|
-
*
|
|
21345
|
+
* skipped (with a warning when they carry `deny` rules).
|
|
21346
|
+
*
|
|
21347
|
+
* Warp's `[agents.profiles]` table also exposes file-read/read-only autonomy
|
|
21348
|
+
* knobs that do not fit the canonical `allow | ask | deny` per-command model:
|
|
21349
|
+
* `agent_mode_coding_permissions`, `agent_mode_coding_file_read_allowlist`, and
|
|
21350
|
+
* `agent_mode_execute_readonly_commands`. These are authored and round-tripped
|
|
21351
|
+
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
21352
|
+
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
21353
|
+
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
21354
|
+
* MCP allow/deny is a separate surface not modeled here.
|
|
20628
21355
|
*
|
|
20629
21356
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
20630
21357
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -20669,12 +21396,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20669
21396
|
} catch (error) {
|
|
20670
21397
|
throw new Error(`Failed to parse existing Warp settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
20671
21398
|
}
|
|
21399
|
+
const config = rulesyncPermissions.getJson();
|
|
20672
21400
|
const { allow, deny } = convertRulesyncToWarpPermissions({
|
|
20673
|
-
config
|
|
21401
|
+
config,
|
|
20674
21402
|
logger
|
|
20675
21403
|
});
|
|
20676
21404
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
20677
21405
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
21406
|
+
const override = config.warp;
|
|
21407
|
+
if (isRecord(override)) Object.assign(profiles, override);
|
|
20678
21408
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
20679
21409
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
20680
21410
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -20705,7 +21435,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20705
21435
|
allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
20706
21436
|
deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
20707
21437
|
});
|
|
20708
|
-
|
|
21438
|
+
const warpOverride = {};
|
|
21439
|
+
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
21440
|
+
const result = { ...config };
|
|
21441
|
+
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
21442
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
20709
21443
|
}
|
|
20710
21444
|
validate() {
|
|
20711
21445
|
return {
|
|
@@ -24071,8 +24805,9 @@ const DevinSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
24071
24805
|
* Represents a Devin (now Devin Desktop) skill directory.
|
|
24072
24806
|
* Devin supports skills in both project mode under .devin/skills/
|
|
24073
24807
|
* (preferred since the Devin Desktop rebrand; .devin/skills/ is the legacy
|
|
24074
|
-
* fallback the tool still reads) and global mode under
|
|
24075
|
-
* (
|
|
24808
|
+
* fallback the tool still reads) and global mode under the Devin-native
|
|
24809
|
+
* ~/.config/devin/skills/ (consistent with Devin's global agents/rules paths;
|
|
24810
|
+
* the legacy channel-dependent ~/.codeium/<channel>/skills/ is no longer emitted).
|
|
24076
24811
|
*/
|
|
24077
24812
|
var DevinSkill = class DevinSkill extends ToolSkill {
|
|
24078
24813
|
constructor({ outputRoot = process.cwd(), relativeDirPath = DevinSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -24094,7 +24829,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
|
|
|
24094
24829
|
}
|
|
24095
24830
|
}
|
|
24096
24831
|
static getSettablePaths({ global = false } = {}) {
|
|
24097
|
-
if (global) return { relativeDirPath:
|
|
24832
|
+
if (global) return { relativeDirPath: DEVIN_GLOBAL_SKILLS_DIR_PATH };
|
|
24098
24833
|
return { relativeDirPath: DEVIN_SKILLS_DIR_PATH };
|
|
24099
24834
|
}
|
|
24100
24835
|
getFrontmatter() {
|
|
@@ -27153,7 +27888,7 @@ const QwencodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
27153
27888
|
disallowedTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
27154
27889
|
maxTurns: zod_mini.z.optional(zod_mini.z.number()),
|
|
27155
27890
|
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
27156
|
-
mcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
27891
|
+
mcpServers: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.array(zod_mini.z.string()), zod_mini.z.record(zod_mini.z.string(), zod_mini.z.looseObject({}))])),
|
|
27157
27892
|
hooks: zod_mini.z.optional(zod_mini.z.unknown())
|
|
27158
27893
|
});
|
|
27159
27894
|
var QwencodeSubagent = class QwencodeSubagent extends ToolSubagent {
|