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
|
@@ -3003,8 +3003,8 @@ const DEVIN_SKILLS_DIR_PATH = join(DEVIN_DIR, "skills");
|
|
|
3003
3003
|
const DEVIN_AGENTS_DIR_PATH = join(DEVIN_DIR, "agents");
|
|
3004
3004
|
const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
|
|
3005
3005
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
3006
|
+
const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
3006
3007
|
const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "global_workflows");
|
|
3007
|
-
const CODEIUM_WINDSURF_SKILLS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "skills");
|
|
3008
3008
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
3009
3009
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
3010
3010
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
@@ -5688,6 +5688,8 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
5688
5688
|
"sessionEnd",
|
|
5689
5689
|
"beforeSubmitPrompt",
|
|
5690
5690
|
"permissionRequest",
|
|
5691
|
+
"preToolUse",
|
|
5692
|
+
"postToolUse",
|
|
5691
5693
|
"postToolUseFailure",
|
|
5692
5694
|
"stop",
|
|
5693
5695
|
"preCompact",
|
|
@@ -5713,7 +5715,13 @@ const CODEXCLI_HOOK_EVENTS = [
|
|
|
5713
5715
|
* Goose adopts the Open Plugins hooks spec: each plugin's `hooks/hooks.json`
|
|
5714
5716
|
* maps PascalCase event names to matcher/handler arrays. Every Goose event has a
|
|
5715
5717
|
* 1:1 canonical equivalent, so no new canonical events are required.
|
|
5716
|
-
*
|
|
5718
|
+
*
|
|
5719
|
+
* Goose's `HookEvent` enum defines exactly these 11 events (v1.41.0). Notably it
|
|
5720
|
+
* has NO `SubagentStart`/`SubagentStop` arms — emitting them would write keys
|
|
5721
|
+
* Goose silently ignores, so `subagentStart`/`subagentStop` are intentionally
|
|
5722
|
+
* excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
|
|
5723
|
+
* @see https://github.com/block/goose/blob/v1.41.0/crates/goose/src/hooks/mod.rs
|
|
5724
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
5717
5725
|
*/
|
|
5718
5726
|
const GOOSE_HOOK_EVENTS = [
|
|
5719
5727
|
"sessionStart",
|
|
@@ -5726,9 +5734,7 @@ const GOOSE_HOOK_EVENTS = [
|
|
|
5726
5734
|
"beforeReadFile",
|
|
5727
5735
|
"afterFileEdit",
|
|
5728
5736
|
"beforeShellExecution",
|
|
5729
|
-
"afterShellExecution"
|
|
5730
|
-
"subagentStart",
|
|
5731
|
-
"subagentStop"
|
|
5737
|
+
"afterShellExecution"
|
|
5732
5738
|
];
|
|
5733
5739
|
/** Hook events supported by Kiro CLI. */
|
|
5734
5740
|
const KIRO_HOOK_EVENTS = [
|
|
@@ -6181,9 +6187,7 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
6181
6187
|
beforeReadFile: "BeforeReadFile",
|
|
6182
6188
|
afterFileEdit: "AfterFileEdit",
|
|
6183
6189
|
beforeShellExecution: "BeforeShellExecution",
|
|
6184
|
-
afterShellExecution: "AfterShellExecution"
|
|
6185
|
-
subagentStart: "SubagentStart",
|
|
6186
|
-
subagentStop: "SubagentStop"
|
|
6190
|
+
afterShellExecution: "AfterShellExecution"
|
|
6187
6191
|
};
|
|
6188
6192
|
/**
|
|
6189
6193
|
* Map Goose PascalCase event names to canonical camelCase.
|
|
@@ -6197,6 +6201,8 @@ const CANONICAL_TO_DEEPAGENTS_EVENT_NAMES = {
|
|
|
6197
6201
|
sessionEnd: "session.end",
|
|
6198
6202
|
beforeSubmitPrompt: "user.prompt",
|
|
6199
6203
|
permissionRequest: "permission.request",
|
|
6204
|
+
preToolUse: "tool.use",
|
|
6205
|
+
postToolUse: "tool.result",
|
|
6200
6206
|
postToolUseFailure: "tool.error",
|
|
6201
6207
|
stop: "task.complete",
|
|
6202
6208
|
preCompact: "context.compact",
|
|
@@ -8191,7 +8197,7 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
8191
8197
|
*
|
|
8192
8198
|
* The JSON shape matches Claude Code's: each PascalCase event maps to an array of
|
|
8193
8199
|
* `{ matcher, hooks: [{ type: "command", command }] }` entries.
|
|
8194
|
-
* @see https://
|
|
8200
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
8195
8201
|
*/
|
|
8196
8202
|
var GooseHooks = class GooseHooks extends ToolHooks {
|
|
8197
8203
|
constructor(params) {
|
|
@@ -8242,7 +8248,7 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8242
8248
|
throw new Error(`Failed to parse Goose hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
8243
8249
|
}
|
|
8244
8250
|
const hooks = toolHooksToCanonical({
|
|
8245
|
-
hooks: parsed.hooks,
|
|
8251
|
+
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,
|
|
8246
8252
|
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
8247
8253
|
});
|
|
8248
8254
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
@@ -8297,6 +8303,24 @@ function mergeHermesConfig(fileContent, patch) {
|
|
|
8297
8303
|
...patch
|
|
8298
8304
|
});
|
|
8299
8305
|
}
|
|
8306
|
+
/**
|
|
8307
|
+
* Recursively merge `patch` into `base` (patch wins). Nested plain objects are
|
|
8308
|
+
* merged key-by-key; every other value (arrays, scalars) is replaced wholesale.
|
|
8309
|
+
* Used to overlay the Hermes-scoped permission override onto the natively
|
|
8310
|
+
* emitted `approvals`/`security` structures without one clobbering the other
|
|
8311
|
+
* (e.g. `approvals.deny` from canonical deny rules coexisting with an
|
|
8312
|
+
* `approvals.mode` from the override). Prototype-pollution keys are dropped.
|
|
8313
|
+
*/
|
|
8314
|
+
function deepMergeHermesConfig(base, patch) {
|
|
8315
|
+
const result = { ...base };
|
|
8316
|
+
for (const [key, patchValue] of Object.entries(patch)) {
|
|
8317
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8318
|
+
const baseValue = result[key];
|
|
8319
|
+
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = deepMergeHermesConfig(baseValue, patchValue);
|
|
8320
|
+
else result[key] = sanitizeHermesConfigValue(patchValue);
|
|
8321
|
+
}
|
|
8322
|
+
return result;
|
|
8323
|
+
}
|
|
8300
8324
|
//#endregion
|
|
8301
8325
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8302
8326
|
/**
|
|
@@ -12279,6 +12303,38 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
12279
12303
|
envVars: "env_vars"
|
|
12280
12304
|
};
|
|
12281
12305
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
12306
|
+
/**
|
|
12307
|
+
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
12308
|
+
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
12309
|
+
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
12310
|
+
* falls back to dynamic client registration and fails for providers that do not
|
|
12311
|
+
* support it (e.g. Slack, see #2158). The canonical `clientId` is kept alongside
|
|
12312
|
+
* the added `client_id` so tools that read the camelCase shape keep working and
|
|
12313
|
+
* the round-trip stays stable.
|
|
12314
|
+
*/
|
|
12315
|
+
function mapOauthToCodex(oauth) {
|
|
12316
|
+
const result = omitPrototypePollutionKeys(oauth);
|
|
12317
|
+
if (typeof oauth["clientId"] === "string" && !("client_id" in result)) result["client_id"] = oauth["clientId"];
|
|
12318
|
+
return result;
|
|
12319
|
+
}
|
|
12320
|
+
/**
|
|
12321
|
+
* Reverse of {@link mapOauthToCodex}: collapse Codex's `oauth.client_id` back to
|
|
12322
|
+
* the canonical `clientId` on import. When both keys are present (the shape
|
|
12323
|
+
* rulesync itself emits) the canonical `clientId` wins and `client_id` is
|
|
12324
|
+
* dropped so a subsequent generate does not accumulate duplicates.
|
|
12325
|
+
*/
|
|
12326
|
+
function mapOauthFromCodex(oauth) {
|
|
12327
|
+
const result = {};
|
|
12328
|
+
for (const [key, value] of Object.entries(oauth)) {
|
|
12329
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12330
|
+
if (key === "client_id") {
|
|
12331
|
+
if (!("clientId" in oauth)) result["clientId"] = value;
|
|
12332
|
+
continue;
|
|
12333
|
+
}
|
|
12334
|
+
result[key] = value;
|
|
12335
|
+
}
|
|
12336
|
+
return result;
|
|
12337
|
+
}
|
|
12282
12338
|
function convertFromCodexFormat(codexMcp) {
|
|
12283
12339
|
const result = {};
|
|
12284
12340
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
@@ -12288,7 +12344,8 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
12288
12344
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12289
12345
|
if (key === "enabled") {
|
|
12290
12346
|
if (value === false) converted["disabled"] = true;
|
|
12291
|
-
} else if (
|
|
12347
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
12348
|
+
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
12292
12349
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
12293
12350
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12294
12351
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
@@ -12308,7 +12365,8 @@ function convertToCodexFormat(mcpServers) {
|
|
|
12308
12365
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12309
12366
|
if (key === "disabled") {
|
|
12310
12367
|
if (value === true) converted["enabled"] = false;
|
|
12311
|
-
} else if (
|
|
12368
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
12369
|
+
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
12312
12370
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
12313
12371
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12314
12372
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
@@ -13524,10 +13582,12 @@ function resolveHermesTimeout(config) {
|
|
|
13524
13582
|
*
|
|
13525
13583
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
13526
13584
|
* executable string (an array's tail folds into `args`), a server is disabled
|
|
13527
|
-
* via `enabled: false` (not the canonical `disabled: true`),
|
|
13528
|
-
*
|
|
13529
|
-
* `
|
|
13530
|
-
*
|
|
13585
|
+
* via `enabled: false` (not the canonical `disabled: true`), remote servers use
|
|
13586
|
+
* `url`/`headers`, and per-server tool scoping lives under a `tools: { include,
|
|
13587
|
+
* exclude }` block (from the canonical `enabledTools`/`disabledTools`). Only
|
|
13588
|
+
* fields Hermes understands are emitted, so the shared `config.yaml` is not
|
|
13589
|
+
* polluted with canonical-only aliases (`type`, `transport`, `httpUrl`,
|
|
13590
|
+
* `networkTimeout`, ...).
|
|
13531
13591
|
*/
|
|
13532
13592
|
function convertServerToHermes(config) {
|
|
13533
13593
|
const out = {};
|
|
@@ -13551,6 +13611,10 @@ function convertServerToHermes(config) {
|
|
|
13551
13611
|
if (config.disabled === true) out.enabled = false;
|
|
13552
13612
|
const timeout = resolveHermesTimeout(config);
|
|
13553
13613
|
if (timeout !== void 0) out.timeout = timeout;
|
|
13614
|
+
const tools = {};
|
|
13615
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
13616
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
13617
|
+
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
13554
13618
|
return out;
|
|
13555
13619
|
}
|
|
13556
13620
|
/**
|
|
@@ -13592,6 +13656,10 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
13592
13656
|
if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
13593
13657
|
if (config.enabled === false) server.disabled = true;
|
|
13594
13658
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
13659
|
+
if (isRecord(config.tools)) {
|
|
13660
|
+
if (isStringArray(config.tools.include)) server.enabledTools = config.tools.include;
|
|
13661
|
+
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
13662
|
+
}
|
|
13595
13663
|
result[name] = server;
|
|
13596
13664
|
}
|
|
13597
13665
|
return result;
|
|
@@ -15747,17 +15815,232 @@ const PermissionActionSchema = z.enum([
|
|
|
15747
15815
|
*/
|
|
15748
15816
|
const PermissionRulesSchema = z.record(z.string(), PermissionActionSchema);
|
|
15749
15817
|
/**
|
|
15818
|
+
* OpenCode-specific permission value. Unlike the shared canonical block, which
|
|
15819
|
+
* only accepts a pattern-to-action map, OpenCode also allows a bare action
|
|
15820
|
+
* string that applies to the whole category (e.g. `"external_directory": "deny"`).
|
|
15821
|
+
* This mirrors OpenCode's own permission schema in `opencode-permissions.ts`.
|
|
15822
|
+
*/
|
|
15823
|
+
const OpencodeOverridePermissionValueSchema = z.union([PermissionActionSchema, PermissionRulesSchema]);
|
|
15824
|
+
/**
|
|
15825
|
+
* Tool-scoped override block for OpenCode. Permission categories placed here
|
|
15826
|
+
* (e.g. OpenCode-only categories such as `external_directory`) are emitted only
|
|
15827
|
+
* into OpenCode's config and never leak into other tools' permission files. It
|
|
15828
|
+
* also lets a shared category be overridden with an OpenCode-specific value.
|
|
15829
|
+
* Kept `looseObject` so future OpenCode categories are accepted.
|
|
15830
|
+
*
|
|
15831
|
+
* @example
|
|
15832
|
+
* { "permission": { "external_directory": "deny", "webfetch": "allow" } }
|
|
15833
|
+
*/
|
|
15834
|
+
const OpencodePermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.record(z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
15835
|
+
/**
|
|
15836
|
+
* Tool-scoped override block for Hermes Agent. Keys placed here are deep-merged
|
|
15837
|
+
* into Hermes's `~/.hermes/config.yaml` and never leak into other tools' configs.
|
|
15838
|
+
* It carries Hermes-specific approval/security controls that have no canonical
|
|
15839
|
+
* permission category — e.g. `approvals` (`mode`, `cron_mode`, ...),
|
|
15840
|
+
* `security` (`allow_private_urls`, ...), `skills.write_approval`,
|
|
15841
|
+
* `memory.write_approval`. Kept `looseObject` (a verbatim passthrough) so any
|
|
15842
|
+
* current or future Hermes config key can be authored without modeling each one.
|
|
15843
|
+
*
|
|
15844
|
+
* @example
|
|
15845
|
+
* { "approvals": { "mode": "smart" }, "security": { "allow_private_urls": false } }
|
|
15846
|
+
*/
|
|
15847
|
+
const HermesPermissionsOverrideSchema = z.looseObject({});
|
|
15848
|
+
/**
|
|
15849
|
+
* Tool-scoped override block for Cline. Cline's `command-permissions.json`
|
|
15850
|
+
* carries a single global `allowRedirects` boolean (gates shell redirection
|
|
15851
|
+
* operators `>`/`>>`/`<`) that has no per-command dimension and therefore no
|
|
15852
|
+
* canonical permission category. Placing it here lets users author it
|
|
15853
|
+
* declaratively; it is emitted only into Cline's config. Kept `looseObject` so
|
|
15854
|
+
* future Cline-only knobs can be added.
|
|
15855
|
+
*
|
|
15856
|
+
* @example
|
|
15857
|
+
* { "allowRedirects": true }
|
|
15858
|
+
*/
|
|
15859
|
+
const ClinePermissionsOverrideSchema = z.looseObject({ allowRedirects: z.optional(z.boolean()) });
|
|
15860
|
+
/**
|
|
15861
|
+
* Tool-scoped override block for Kilo Code (an OpenCode fork). Kilo's permission
|
|
15862
|
+
* object is a free-form record with tool-specific keys that have no canonical
|
|
15863
|
+
* category — OpenCode-inherited ones (`external_directory`, `doom_loop`, `lsp`,
|
|
15864
|
+
* `question`, `todowrite`, `skill`, `task`, `list`) and Kilo-unique ones
|
|
15865
|
+
* (`agent_manager`, `notebook_read`, `notebook_edit`, `notebook_execute`,
|
|
15866
|
+
* `repo_clone`, `repo_overview`). Placing them here makes them authorable and
|
|
15867
|
+
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
15868
|
+
* override; each value may be a bare action string or a pattern map.
|
|
15869
|
+
*
|
|
15870
|
+
* @example
|
|
15871
|
+
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
15872
|
+
*/
|
|
15873
|
+
const KiloPermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.record(z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
15874
|
+
/**
|
|
15875
|
+
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
15876
|
+
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
15877
|
+
* permission category — `defaultMode` (the session-start permission mode) and
|
|
15878
|
+
* `additionalDirectories` (extra working directories) being the primary ones.
|
|
15879
|
+
* Fields placed under `claudecode.permissions` are merged into the settings
|
|
15880
|
+
* `permissions` object and emitted only for Claude Code, while the shared
|
|
15881
|
+
* `permission` block continues to drive the `allow`/`ask`/`deny` arrays. Kept a
|
|
15882
|
+
* `looseObject` passthrough so any current or future `permissions` field can be
|
|
15883
|
+
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
15884
|
+
* ignored here (rulesync owns them).
|
|
15885
|
+
*
|
|
15886
|
+
* @example
|
|
15887
|
+
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
15888
|
+
*/
|
|
15889
|
+
const ClaudecodePermissionsOverrideSchema = z.looseObject({ permissions: z.optional(z.looseObject({})) });
|
|
15890
|
+
/**
|
|
15891
|
+
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
15892
|
+
* carries a `sensitive_patterns` list — patterns that escalate to ASK even when
|
|
15893
|
+
* the tool's base permission is ALWAYS (allow). The canonical model can only set
|
|
15894
|
+
* a pattern to a single `allow`/`ask`/`deny`, so an "allow by default but ask on
|
|
15895
|
+
* these patterns" escalation cannot be expressed. Entries under
|
|
15896
|
+
* `vibe.permission.<category>.sensitive_patterns` carry that list per canonical
|
|
15897
|
+
* category; the shared `permission` block still drives the base permission and
|
|
15898
|
+
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
15899
|
+
*
|
|
15900
|
+
* @example
|
|
15901
|
+
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
15902
|
+
*/
|
|
15903
|
+
const VibePermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.record(z.string(), z.looseObject({ sensitive_patterns: z.optional(z.array(z.string())) }))) });
|
|
15904
|
+
/**
|
|
15905
|
+
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
15906
|
+
* autonomy settings with no canonical permission category — `approvalMode`
|
|
15907
|
+
* (`allowlist` | `auto-review` | `unrestricted`) and a `sandbox` object
|
|
15908
|
+
* (`mode`/`networkAccess`). Fields placed here are merged into the top-level of
|
|
15909
|
+
* `.cursor/cli.json` (project) / `~/.cursor/cli-config.json` (global) and emitted
|
|
15910
|
+
* only for Cursor, while the shared `permission` block continues to drive the
|
|
15911
|
+
* `permissions.allow`/`permissions.deny` arrays. Kept a `looseObject` so extra
|
|
15912
|
+
* `cli.json` keys can be authored (they are merged verbatim on generate);
|
|
15913
|
+
* `sandbox`'s accepted values are not documented so it passes through verbatim.
|
|
15914
|
+
* Note: only `approvalMode` and `sandbox` round-trip back on import — other keys
|
|
15915
|
+
* authored here reach `cli.json` on generate but are not re-extracted.
|
|
15916
|
+
*
|
|
15917
|
+
* @example
|
|
15918
|
+
* { "approvalMode": "auto-review" }
|
|
15919
|
+
*/
|
|
15920
|
+
const CursorPermissionsOverrideSchema = z.looseObject({
|
|
15921
|
+
approvalMode: z.optional(z.string()),
|
|
15922
|
+
sandbox: z.optional(z.looseObject({}))
|
|
15923
|
+
});
|
|
15924
|
+
/**
|
|
15925
|
+
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
15926
|
+
* autonomy/sandbox controls with no canonical permission category — under
|
|
15927
|
+
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
15928
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). Fields
|
|
15929
|
+
* placed here are merged into the matching `settings.json` group and emitted
|
|
15930
|
+
* only for Qwen, while the shared `permission` block continues to drive the
|
|
15931
|
+
* `permissions.allow`/`ask`/`deny` arrays. Kept `looseObject` (verbatim
|
|
15932
|
+
* passthrough) so any current or future `tools`/`security` key can be authored.
|
|
15933
|
+
*
|
|
15934
|
+
* @example
|
|
15935
|
+
* { "tools": { "approvalMode": "auto-edit" }, "security": { "folderTrust": { "enabled": true } } }
|
|
15936
|
+
*/
|
|
15937
|
+
const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
15938
|
+
tools: z.optional(z.looseObject({})),
|
|
15939
|
+
security: z.optional(z.looseObject({}))
|
|
15940
|
+
});
|
|
15941
|
+
/**
|
|
15942
|
+
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
15943
|
+
* to per-tool allow/ask/deny with no canonical category — the `[sandbox]`
|
|
15944
|
+
* enforcement table (`workspace_root`, `allow_write`, `forbid_read`, `bash`,
|
|
15945
|
+
* `network`) and plan-mode read-only trust lists under `[agent]`
|
|
15946
|
+
* (`plan_mode_allowed_tools`, `plan_mode_read_only_commands`). Fields placed here
|
|
15947
|
+
* are merged into the matching `reasonix.toml` table and emitted only for
|
|
15948
|
+
* Reasonix, while the shared `permission` block continues to drive
|
|
15949
|
+
* `[permissions].allow`/`ask`/`deny`. Kept `looseObject` (verbatim passthrough).
|
|
15950
|
+
* Note: the whole `[sandbox]` table round-trips, but only the plan-mode keys are
|
|
15951
|
+
* re-extracted from `[agent]` on import — other `agent` keys authored here reach
|
|
15952
|
+
* `reasonix.toml` on generate but are not re-extracted back into the override.
|
|
15953
|
+
*
|
|
15954
|
+
* @example
|
|
15955
|
+
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
15956
|
+
*/
|
|
15957
|
+
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
15958
|
+
sandbox: z.optional(z.looseObject({})),
|
|
15959
|
+
agent: z.optional(z.looseObject({}))
|
|
15960
|
+
});
|
|
15961
|
+
/**
|
|
15962
|
+
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
15963
|
+
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
15964
|
+
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
15965
|
+
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
15966
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`, `enableDroidShield`,
|
|
15967
|
+
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
15968
|
+
* `interactionMode`). Fields placed here are merged into `settings.json` and
|
|
15969
|
+
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
15970
|
+
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
15971
|
+
*
|
|
15972
|
+
* @example
|
|
15973
|
+
* { "commandBlocklist": ["rm -rf /*"], "sandbox": { "enabled": true } }
|
|
15974
|
+
*/
|
|
15975
|
+
const FactorydroidPermissionsOverrideSchema = z.looseObject({ commandBlocklist: z.optional(z.array(z.string())) });
|
|
15976
|
+
/**
|
|
15977
|
+
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
15978
|
+
* file-read/read-only autonomy keys with no canonical per-command allow/ask/deny
|
|
15979
|
+
* slot — `agent_mode_coding_permissions`
|
|
15980
|
+
* (`always_ask_before_reading` | `always_allow_reading` | `allow_reading_specific_files`),
|
|
15981
|
+
* `agent_mode_coding_file_read_allowlist` (a path array), and
|
|
15982
|
+
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
15983
|
+
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
15984
|
+
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
15985
|
+
* `agent_mode_command_execution_allowlist`/`_denylist` command regex arrays.
|
|
15986
|
+
* Warp permissions are global-only.
|
|
15987
|
+
*
|
|
15988
|
+
* @example
|
|
15989
|
+
* { "agent_mode_coding_permissions": "always_allow_reading", "agent_mode_execute_readonly_commands": true }
|
|
15990
|
+
*/
|
|
15991
|
+
const WarpPermissionsOverrideSchema = z.looseObject({
|
|
15992
|
+
agent_mode_coding_permissions: z.optional(z.string()),
|
|
15993
|
+
agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
|
|
15994
|
+
agent_mode_execute_readonly_commands: z.optional(z.boolean())
|
|
15995
|
+
});
|
|
15996
|
+
/**
|
|
15997
|
+
* Tool-scoped override block for JetBrains Junie. Junie's `allowlist.json` has
|
|
15998
|
+
* two top-level autonomy knobs with no canonical per-glob slot:
|
|
15999
|
+
* `allowReadonlyCommands` (a boolean auto-allowing read-only commands) and
|
|
16000
|
+
* `defaultBehavior` (the fallback action applied when no rule matches — Junie
|
|
16001
|
+
* documents only `allow`/`ask`). Fields placed here are merged onto the
|
|
16002
|
+
* top level of `allowlist.json`, while the shared `permission` block continues
|
|
16003
|
+
* to drive the per-category `rules` groups.
|
|
16004
|
+
*
|
|
16005
|
+
* @example
|
|
16006
|
+
* { "allowReadonlyCommands": true, "defaultBehavior": "ask" }
|
|
16007
|
+
*/
|
|
16008
|
+
const JuniePermissionsOverrideSchema = z.looseObject({
|
|
16009
|
+
allowReadonlyCommands: z.optional(z.boolean()),
|
|
16010
|
+
defaultBehavior: z.optional(z.string())
|
|
16011
|
+
});
|
|
16012
|
+
/**
|
|
15750
16013
|
* Permissions configuration.
|
|
15751
16014
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
15752
16015
|
* Values are pattern-to-action mappings for that tool category.
|
|
15753
16016
|
*
|
|
16017
|
+
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
16018
|
+
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie` keys are tool-scoped
|
|
16019
|
+
* overrides consumed only by their respective translator (see the matching
|
|
16020
|
+
* `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
|
|
16021
|
+
* block and ignores them.
|
|
16022
|
+
*
|
|
15754
16023
|
* @example
|
|
15755
16024
|
* {
|
|
15756
16025
|
* "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
|
|
15757
16026
|
* "edit": { "*": "deny", "src/**": "allow" }
|
|
15758
16027
|
* }
|
|
15759
16028
|
*/
|
|
15760
|
-
const PermissionsConfigSchema = z.looseObject({
|
|
16029
|
+
const PermissionsConfigSchema = z.looseObject({
|
|
16030
|
+
permission: z.record(z.string(), PermissionRulesSchema),
|
|
16031
|
+
opencode: z.optional(OpencodePermissionsOverrideSchema),
|
|
16032
|
+
hermes: z.optional(HermesPermissionsOverrideSchema),
|
|
16033
|
+
cline: z.optional(ClinePermissionsOverrideSchema),
|
|
16034
|
+
kilo: z.optional(KiloPermissionsOverrideSchema),
|
|
16035
|
+
claudecode: z.optional(ClaudecodePermissionsOverrideSchema),
|
|
16036
|
+
vibe: z.optional(VibePermissionsOverrideSchema),
|
|
16037
|
+
cursor: z.optional(CursorPermissionsOverrideSchema),
|
|
16038
|
+
qwencode: z.optional(QwencodePermissionsOverrideSchema),
|
|
16039
|
+
reasonix: z.optional(ReasonixPermissionsOverrideSchema),
|
|
16040
|
+
factorydroid: z.optional(FactorydroidPermissionsOverrideSchema),
|
|
16041
|
+
warp: z.optional(WarpPermissionsOverrideSchema),
|
|
16042
|
+
junie: z.optional(JuniePermissionsOverrideSchema)
|
|
16043
|
+
});
|
|
15761
16044
|
/**
|
|
15762
16045
|
* Full permissions file schema including optional $schema field.
|
|
15763
16046
|
*/
|
|
@@ -17128,6 +17411,14 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17128
17411
|
}
|
|
17129
17412
|
const config = rulesyncPermissions.getJson();
|
|
17130
17413
|
const { allow, ask, deny } = convertRulesyncToClaudePermissions(config);
|
|
17414
|
+
const overridePermissions = config.claudecode?.permissions;
|
|
17415
|
+
if (overridePermissions && typeof overridePermissions === "object") {
|
|
17416
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
17417
|
+
settings.permissions = {
|
|
17418
|
+
...settings.permissions,
|
|
17419
|
+
...nonListFields
|
|
17420
|
+
};
|
|
17421
|
+
}
|
|
17131
17422
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
17132
17423
|
const merged = applyPermissions({
|
|
17133
17424
|
settings,
|
|
@@ -17160,6 +17451,8 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17160
17451
|
ask: permissions.ask ?? [],
|
|
17161
17452
|
deny: permissions.deny ?? []
|
|
17162
17453
|
});
|
|
17454
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
17455
|
+
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
17163
17456
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17164
17457
|
}
|
|
17165
17458
|
validate() {
|
|
@@ -17333,7 +17626,8 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17333
17626
|
} catch (error) {
|
|
17334
17627
|
throw new Error(`Failed to parse existing Cline command-permissions at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
17335
17628
|
}
|
|
17336
|
-
const
|
|
17629
|
+
const config = rulesyncPermissions.getJson();
|
|
17630
|
+
const { allow, deny, droppedCategories, translatedAskPatterns } = translateClinePermissions(config.permission);
|
|
17337
17631
|
warnClineTranslationNotices({
|
|
17338
17632
|
droppedCategories,
|
|
17339
17633
|
translatedAskPatterns,
|
|
@@ -17349,7 +17643,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17349
17643
|
...existing,
|
|
17350
17644
|
allow: dedupedAllow,
|
|
17351
17645
|
deny: mergedDeny,
|
|
17352
|
-
allowRedirects: existing.allowRedirects ?? false
|
|
17646
|
+
allowRedirects: config.cline?.allowRedirects ?? existing.allowRedirects ?? false
|
|
17353
17647
|
};
|
|
17354
17648
|
return new ClinePermissions({
|
|
17355
17649
|
outputRoot,
|
|
@@ -17373,6 +17667,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17373
17667
|
for (const pattern of parsed.allow ?? []) bashRules[pattern] = "allow";
|
|
17374
17668
|
for (const pattern of parsed.deny ?? []) bashRules[pattern] = "deny";
|
|
17375
17669
|
const config = Object.keys(bashRules).length > 0 ? { permission: { bash: bashRules } } : { permission: {} };
|
|
17670
|
+
if (parsed.allowRedirects === true) config.cline = { allowRedirects: true };
|
|
17376
17671
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17377
17672
|
}
|
|
17378
17673
|
validate() {
|
|
@@ -17809,13 +18104,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
17809
18104
|
WebFetch: "webfetch",
|
|
17810
18105
|
Mcp: "mcp"
|
|
17811
18106
|
};
|
|
17812
|
-
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
18107
|
+
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
17813
18108
|
/**
|
|
17814
18109
|
* Returns true if the canonical category is the per-tool MCP form
|
|
17815
18110
|
* `mcp__<server>__<tool>`.
|
|
17816
18111
|
*/
|
|
17817
18112
|
function isMcpScopedCategory(canonical) {
|
|
17818
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX) && canonical.length > 5;
|
|
18113
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$1) && canonical.length > 5;
|
|
17819
18114
|
}
|
|
17820
18115
|
function toCursorType(canonical) {
|
|
17821
18116
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -17846,7 +18141,7 @@ function toCursorPattern(canonical, pattern) {
|
|
|
17846
18141
|
function toCanonicalCategory$1(cursorType, pattern) {
|
|
17847
18142
|
if (cursorType === "Mcp") {
|
|
17848
18143
|
const match = pattern.match(/^([^:()]+):([^:()]+)$/);
|
|
17849
|
-
if (match) return `${MCP_CANONICAL_PREFIX}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
18144
|
+
if (match) return `${MCP_CANONICAL_PREFIX$1}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
17850
18145
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
17851
18146
|
}
|
|
17852
18147
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
@@ -17980,8 +18275,10 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
17980
18275
|
mergedPermissions.allow = mergedAllow;
|
|
17981
18276
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
17982
18277
|
else delete mergedPermissions.deny;
|
|
18278
|
+
const cursorOverride = config.cursor;
|
|
17983
18279
|
const merged = {
|
|
17984
18280
|
...settings,
|
|
18281
|
+
...cursorOverride,
|
|
17985
18282
|
version: settings.version ?? 1,
|
|
17986
18283
|
editor: {
|
|
17987
18284
|
...existingEditor,
|
|
@@ -18007,11 +18304,15 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
18007
18304
|
}
|
|
18008
18305
|
const permissionsRaw = settings.permissions;
|
|
18009
18306
|
const permissions = permissionsRaw !== null && typeof permissionsRaw === "object" && !Array.isArray(permissionsRaw) ? permissionsRaw : {};
|
|
18010
|
-
const
|
|
18307
|
+
const result = { ...convertCursorToRulesyncPermissions({
|
|
18011
18308
|
allow: asCursorPermissionEntryArray(permissions.allow),
|
|
18012
18309
|
deny: asCursorPermissionEntryArray(permissions.deny)
|
|
18013
|
-
});
|
|
18014
|
-
|
|
18310
|
+
}) };
|
|
18311
|
+
const cursorOverride = {};
|
|
18312
|
+
if (settings.approvalMode !== void 0) cursorOverride.approvalMode = settings.approvalMode;
|
|
18313
|
+
if (settings.sandbox !== null && typeof settings.sandbox === "object" && !Array.isArray(settings.sandbox)) cursorOverride.sandbox = settings.sandbox;
|
|
18314
|
+
if (Object.keys(cursorOverride).length > 0) result.cursor = cursorOverride;
|
|
18315
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18015
18316
|
}
|
|
18016
18317
|
validate() {
|
|
18017
18318
|
return {
|
|
@@ -18070,7 +18371,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
18070
18371
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
18071
18372
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
18072
18373
|
if (!permission[canonical]) permission[canonical] = {};
|
|
18073
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX) ? "*" : pattern;
|
|
18374
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$1) ? "*" : pattern;
|
|
18074
18375
|
permission[canonical][canonicalPattern] = action;
|
|
18075
18376
|
}
|
|
18076
18377
|
};
|
|
@@ -18319,6 +18620,16 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18319
18620
|
}
|
|
18320
18621
|
//#endregion
|
|
18321
18622
|
//#region src/features/permissions/factorydroid-permissions.ts
|
|
18623
|
+
const FACTORYDROID_OVERRIDE_KEYS = [
|
|
18624
|
+
"commandBlocklist",
|
|
18625
|
+
"networkPolicy",
|
|
18626
|
+
"sandbox",
|
|
18627
|
+
"mcpPolicy",
|
|
18628
|
+
"enableDroidShield",
|
|
18629
|
+
"sessionDefaultSettings",
|
|
18630
|
+
"maxAutonomyLevel",
|
|
18631
|
+
"interactionMode"
|
|
18632
|
+
];
|
|
18322
18633
|
/**
|
|
18323
18634
|
* Permissions adapter for Factory Droid.
|
|
18324
18635
|
*
|
|
@@ -18336,18 +18647,14 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18336
18647
|
* skipped (with a warning when they carry `deny` rules, to surface the gap).
|
|
18337
18648
|
*
|
|
18338
18649
|
* Factory Droid also has a stronger `commandBlocklist` tier — commands that can
|
|
18339
|
-
* never run, not even under full autonomy
|
|
18340
|
-
*
|
|
18341
|
-
*
|
|
18342
|
-
*
|
|
18343
|
-
*
|
|
18344
|
-
*
|
|
18345
|
-
*
|
|
18346
|
-
*
|
|
18347
|
-
* `commandBlocklist` and re-exporting it to a *fresh* config writes it back as
|
|
18348
|
-
* `commandDenylist`, not `commandBlocklist` — the hard-block tier is not
|
|
18349
|
-
* reconstructed. Re-running over the original file keeps it intact via the
|
|
18350
|
-
* verbatim preservation above.)
|
|
18650
|
+
* never run, not even under full autonomy — plus other security controls
|
|
18651
|
+
* (`networkPolicy`, `sandbox`, `mcpPolicy`, `enableDroidShield`, autonomy
|
|
18652
|
+
* settings) that do not fit the canonical `allow | ask | deny` per-command
|
|
18653
|
+
* model. These are authored and round-tripped through the `factorydroid`
|
|
18654
|
+
* override namespace (see `FactorydroidPermissionsOverrideSchema`): on **import**
|
|
18655
|
+
* they are lifted from `settings.json` into the override, and on **export** they
|
|
18656
|
+
* are merged back in — so `commandBlocklist`'s never-runs guarantee is preserved
|
|
18657
|
+
* faithfully rather than being collapsed onto an approvable `deny`.
|
|
18351
18658
|
*/
|
|
18352
18659
|
var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissions {
|
|
18353
18660
|
constructor(params) {
|
|
@@ -18390,11 +18697,16 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18390
18697
|
} catch (error) {
|
|
18391
18698
|
throw new Error(`Failed to parse existing Factory Droid settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18392
18699
|
}
|
|
18700
|
+
const config = rulesyncPermissions.getJson();
|
|
18393
18701
|
const { allow, deny } = convertRulesyncToFactorydroidPermissions({
|
|
18394
|
-
config
|
|
18702
|
+
config,
|
|
18395
18703
|
logger
|
|
18396
18704
|
});
|
|
18397
|
-
const
|
|
18705
|
+
const override = config.factorydroid;
|
|
18706
|
+
const merged = {
|
|
18707
|
+
...settings,
|
|
18708
|
+
...override !== void 0 && typeof override === "object" ? override : {}
|
|
18709
|
+
};
|
|
18398
18710
|
const mergedAllow = uniq(allow.toSorted());
|
|
18399
18711
|
const mergedDeny = uniq(deny.toSorted());
|
|
18400
18712
|
if (mergedAllow.length > 0) merged.commandAllowlist = mergedAllow;
|
|
@@ -18418,10 +18730,13 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18418
18730
|
}
|
|
18419
18731
|
const config = convertFactorydroidToRulesyncPermissions({
|
|
18420
18732
|
allow: Array.isArray(settings.commandAllowlist) ? settings.commandAllowlist : [],
|
|
18421
|
-
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18422
|
-
block: Array.isArray(settings.commandBlocklist) ? settings.commandBlocklist : []
|
|
18733
|
+
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18423
18734
|
});
|
|
18424
|
-
|
|
18735
|
+
const factorydroidOverride = {};
|
|
18736
|
+
for (const key of FACTORYDROID_OVERRIDE_KEYS) if (settings[key] !== void 0) factorydroidOverride[key] = settings[key];
|
|
18737
|
+
const result = { ...config };
|
|
18738
|
+
if (Object.keys(factorydroidOverride).length > 0) result.factorydroid = factorydroidOverride;
|
|
18739
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18425
18740
|
}
|
|
18426
18741
|
validate() {
|
|
18427
18742
|
return {
|
|
@@ -18468,18 +18783,18 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
18468
18783
|
};
|
|
18469
18784
|
}
|
|
18470
18785
|
/**
|
|
18471
|
-
* Convert Factory Droid allow/deny
|
|
18472
|
-
*
|
|
18786
|
+
* Convert Factory Droid allow/deny command lists back to rulesync config under
|
|
18787
|
+
* the `bash` category.
|
|
18473
18788
|
*
|
|
18474
|
-
* `commandBlocklist` (hard
|
|
18475
|
-
*
|
|
18476
|
-
* the
|
|
18789
|
+
* `commandBlocklist` (the hard-block tier) is no longer collapsed here — it has
|
|
18790
|
+
* no canonical equivalent and now round-trips through the `factorydroid`
|
|
18791
|
+
* override so the never-runs guarantee is preserved instead of being weakened to
|
|
18792
|
+
* an approvable `deny`.
|
|
18477
18793
|
*/
|
|
18478
|
-
function convertFactorydroidToRulesyncPermissions({ allow, deny
|
|
18794
|
+
function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
18479
18795
|
const bash = {};
|
|
18480
18796
|
for (const pattern of allow) bash[pattern] = "allow";
|
|
18481
18797
|
for (const pattern of deny) bash[pattern] = "deny";
|
|
18482
|
-
for (const pattern of block) bash[pattern] = "deny";
|
|
18483
18798
|
return { permission: Object.keys(bash).length > 0 ? { bash } : {} };
|
|
18484
18799
|
}
|
|
18485
18800
|
//#endregion
|
|
@@ -18673,30 +18988,114 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
18673
18988
|
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
18674
18989
|
const GROKCLI_UI_KEY = "ui";
|
|
18675
18990
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
18991
|
+
const GROKCLI_PERMISSION_KEY = "permission";
|
|
18676
18992
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
18993
|
+
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
18994
|
+
const CATEGORY_TO_GROK_TOOL = {
|
|
18995
|
+
bash: "Bash",
|
|
18996
|
+
read: "Read",
|
|
18997
|
+
edit: "Edit",
|
|
18998
|
+
write: "Edit",
|
|
18999
|
+
grep: "Grep",
|
|
19000
|
+
webfetch: "WebFetch"
|
|
19001
|
+
};
|
|
19002
|
+
const GROK_TOOL_TO_CATEGORY = {
|
|
19003
|
+
Bash: "bash",
|
|
19004
|
+
Read: "read",
|
|
19005
|
+
Edit: "edit",
|
|
19006
|
+
Grep: "grep",
|
|
19007
|
+
WebFetch: "webfetch"
|
|
19008
|
+
};
|
|
19009
|
+
const GROK_MCP_TOOL = "MCPTool";
|
|
19010
|
+
/**
|
|
19011
|
+
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
19012
|
+
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
19013
|
+
* for categories Grok cannot express so callers can skip them.
|
|
19014
|
+
*
|
|
19015
|
+
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
19016
|
+
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
19017
|
+
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
19018
|
+
* concrete pattern emits `Tool(pattern)`.
|
|
19019
|
+
*/
|
|
19020
|
+
function buildGrokEntry(category, pattern) {
|
|
19021
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
19022
|
+
const remainder = category.slice(5);
|
|
19023
|
+
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
19024
|
+
}
|
|
19025
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
19026
|
+
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
19027
|
+
if (tool === void 0) return null;
|
|
19028
|
+
return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
19029
|
+
}
|
|
19030
|
+
/**
|
|
19031
|
+
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
19032
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`). Returns
|
|
19033
|
+
* `null` for tool prefixes with no canonical equivalent (e.g. `any`).
|
|
19034
|
+
*/
|
|
19035
|
+
function parseGrokEntry(entry) {
|
|
19036
|
+
const trimmed = entry.trim();
|
|
19037
|
+
const parenIndex = trimmed.indexOf("(");
|
|
19038
|
+
let tool;
|
|
19039
|
+
let inner;
|
|
19040
|
+
if (parenIndex === -1 || !trimmed.endsWith(")")) {
|
|
19041
|
+
tool = trimmed;
|
|
19042
|
+
inner = "";
|
|
19043
|
+
} else {
|
|
19044
|
+
tool = trimmed.slice(0, parenIndex);
|
|
19045
|
+
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
19046
|
+
}
|
|
19047
|
+
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
19048
|
+
category: `${MCP_CANONICAL_PREFIX}${inner}`,
|
|
19049
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19050
|
+
} : {
|
|
19051
|
+
category: "mcp",
|
|
19052
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19053
|
+
};
|
|
19054
|
+
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
19055
|
+
if (category === void 0) return null;
|
|
19056
|
+
return {
|
|
19057
|
+
category,
|
|
19058
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$2
|
|
19059
|
+
};
|
|
19060
|
+
}
|
|
18677
19061
|
/**
|
|
18678
19062
|
* Permissions adapter for the xAI Grok Build CLI (`grokcli`).
|
|
18679
19063
|
*
|
|
18680
|
-
* Grok
|
|
18681
|
-
*
|
|
18682
|
-
*
|
|
18683
|
-
*
|
|
18684
|
-
*
|
|
18685
|
-
*
|
|
18686
|
-
*
|
|
18687
|
-
*
|
|
18688
|
-
*
|
|
18689
|
-
*
|
|
18690
|
-
* `
|
|
18691
|
-
*
|
|
18692
|
-
*
|
|
18693
|
-
*
|
|
18694
|
-
* the
|
|
18695
|
-
*
|
|
18696
|
-
*
|
|
18697
|
-
*
|
|
18698
|
-
*
|
|
18699
|
-
*
|
|
19064
|
+
* Grok Build CLI ships a Claude-style rule system under `[permission]` in
|
|
19065
|
+
* `~/.grok/config.toml`: `allow` / `deny` / `ask` arrays of entries such as
|
|
19066
|
+
* `Bash(git *)`, `Read(src/**)`, `Edit`, `Grep`, `MCPTool(server__tool)`, and
|
|
19067
|
+
* `WebFetch`, evaluated with precedence `deny > ask > allow`
|
|
19068
|
+
* (https://docs.x.ai/build/settings/reference). rulesync's canonical
|
|
19069
|
+
* per-category, per-pattern model maps almost 1:1:
|
|
19070
|
+
* - Generate: each `permission.<category>.<pattern> = allow|ask|deny` becomes
|
|
19071
|
+
* the matching Grok entry and is bucketed into the `[permission]` array for
|
|
19072
|
+
* that action. `bash|read|edit|grep|webfetch` map to their Grok tool;
|
|
19073
|
+
* `write` collapses onto `Edit` (Grok has no `Write` tool); `mcp__*` maps to
|
|
19074
|
+
* `MCPTool(...)` (a scoped MCP category folds its address into the
|
|
19075
|
+
* parentheses, so a non-`*` argument pattern on it is not represented).
|
|
19076
|
+
* Categories with no Grok tool (`websearch`, `glob`, `notebookedit`,
|
|
19077
|
+
* `agent`) are skipped (with a warning when they carry a `deny` rule, to
|
|
19078
|
+
* surface the gap). When two canonical rules collapse onto the same Grok
|
|
19079
|
+
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
19080
|
+
* the strictest wins (`deny > ask > allow`) and a warning is logged, so the
|
|
19081
|
+
* entry never lands contradictorily in two arrays.
|
|
19082
|
+
* - Import: the `[permission]` arrays are parsed back into canonical
|
|
19083
|
+
* categories. When no `[permission]` section is present (older configs), the
|
|
19084
|
+
* coarse `[ui] permission_mode` is used as a fallback.
|
|
19085
|
+
*
|
|
19086
|
+
* The coarse `[ui] permission_mode` toggle is still written for backward
|
|
19087
|
+
* compatibility with older Grok versions: `always-approve` when the config is
|
|
19088
|
+
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
19089
|
+
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
19090
|
+
* arrays).
|
|
19091
|
+
*
|
|
19092
|
+
* This surface is **global only** — the adapter syncs the user-level
|
|
19093
|
+
* `~/.grok/config.toml`. (Grok also supports a project-scoped `[permission]`
|
|
19094
|
+
* file; project scope is not modeled here.) The shared config is merged in
|
|
19095
|
+
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
19096
|
+
* tools it models and the `[ui] permission_mode` value, while every other key
|
|
19097
|
+
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
19098
|
+
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
18700
19099
|
* file is never deleted.
|
|
18701
19100
|
*/
|
|
18702
19101
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -18728,7 +19127,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18728
19127
|
global: true
|
|
18729
19128
|
});
|
|
18730
19129
|
}
|
|
18731
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
19130
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
18732
19131
|
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
18733
19132
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
18734
19133
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
@@ -18739,7 +19138,16 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18739
19138
|
} catch (error) {
|
|
18740
19139
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18741
19140
|
}
|
|
18742
|
-
const
|
|
19141
|
+
const config = rulesyncPermissions.getJson();
|
|
19142
|
+
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
19143
|
+
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
19144
|
+
parsed[GROKCLI_PERMISSION_KEY] = {
|
|
19145
|
+
...existingPermission,
|
|
19146
|
+
allow: buckets.allow,
|
|
19147
|
+
deny: buckets.deny,
|
|
19148
|
+
ask: buckets.ask
|
|
19149
|
+
};
|
|
19150
|
+
const mode = deriveGrokPermissionMode(config);
|
|
18743
19151
|
const existingUi = isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
18744
19152
|
parsed[GROKCLI_UI_KEY] = {
|
|
18745
19153
|
...existingUi,
|
|
@@ -18762,8 +19170,8 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18762
19170
|
} catch (error) {
|
|
18763
19171
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18764
19172
|
}
|
|
18765
|
-
const
|
|
18766
|
-
const rulesyncConfig = { permission: { bash: { [CATCH_ALL_PATTERN$2]:
|
|
19173
|
+
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
19174
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$2]: legacyModeAction(parsed) } } };
|
|
18767
19175
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
18768
19176
|
}
|
|
18769
19177
|
validate() {
|
|
@@ -18783,6 +19191,84 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18783
19191
|
});
|
|
18784
19192
|
}
|
|
18785
19193
|
};
|
|
19194
|
+
const ACTION_RANK = {
|
|
19195
|
+
allow: 0,
|
|
19196
|
+
ask: 1,
|
|
19197
|
+
deny: 2
|
|
19198
|
+
};
|
|
19199
|
+
/**
|
|
19200
|
+
* Collect user-authored entries from an existing `[permission]` array whose
|
|
19201
|
+
* tool prefix rulesync cannot model (e.g. `WebSearch`, `any`). Such entries are
|
|
19202
|
+
* preserved verbatim so replacing the arrays does not silently drop them
|
|
19203
|
+
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
19204
|
+
*/
|
|
19205
|
+
function unmanagedEntries(existingPermission, key) {
|
|
19206
|
+
return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
19207
|
+
}
|
|
19208
|
+
/**
|
|
19209
|
+
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
19210
|
+
* Claude-style entries, resolving collapse collisions via `deny > ask > allow`.
|
|
19211
|
+
* Categories Grok cannot express are skipped (with a warning when they carry a
|
|
19212
|
+
* `deny` rule); entries the user authored for tools rulesync cannot model are
|
|
19213
|
+
* preserved from `existingPermission`.
|
|
19214
|
+
*/
|
|
19215
|
+
function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
19216
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
19217
|
+
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19218
|
+
const entry = buildGrokEntry(category, pattern);
|
|
19219
|
+
if (entry === null) {
|
|
19220
|
+
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.`);
|
|
19221
|
+
continue;
|
|
19222
|
+
}
|
|
19223
|
+
const existing = ranked.get(entry);
|
|
19224
|
+
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).`);
|
|
19225
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) ranked.set(entry, action);
|
|
19226
|
+
}
|
|
19227
|
+
const allow = unmanagedEntries(existingPermission, "allow");
|
|
19228
|
+
const deny = unmanagedEntries(existingPermission, "deny");
|
|
19229
|
+
const ask = unmanagedEntries(existingPermission, "ask");
|
|
19230
|
+
for (const [entry, action] of ranked) if (action === "allow") allow.push(entry);
|
|
19231
|
+
else if (action === "deny") deny.push(entry);
|
|
19232
|
+
else ask.push(entry);
|
|
19233
|
+
return {
|
|
19234
|
+
allow: uniq(allow.toSorted()),
|
|
19235
|
+
deny: uniq(deny.toSorted()),
|
|
19236
|
+
ask: uniq(ask.toSorted())
|
|
19237
|
+
};
|
|
19238
|
+
}
|
|
19239
|
+
/**
|
|
19240
|
+
* Parse Grok's `[permission]` allow/deny/ask arrays back into a canonical
|
|
19241
|
+
* permission map. Returns `null` when the section defines none of the three
|
|
19242
|
+
* arrays, so the caller can fall back to the coarse `permission_mode`.
|
|
19243
|
+
* Precedence `deny > ask > allow` is applied so a tool listed in multiple
|
|
19244
|
+
* arrays resolves to the strictest action.
|
|
19245
|
+
*/
|
|
19246
|
+
function parseGrokPermissionArrays(permission) {
|
|
19247
|
+
const allow = isStringArray(permission.allow) ? permission.allow : void 0;
|
|
19248
|
+
const deny = isStringArray(permission.deny) ? permission.deny : void 0;
|
|
19249
|
+
const ask = isStringArray(permission.ask) ? permission.ask : void 0;
|
|
19250
|
+
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
|
|
19251
|
+
const result = {};
|
|
19252
|
+
const apply = (entries, action) => {
|
|
19253
|
+
for (const entry of entries ?? []) {
|
|
19254
|
+
const parsed = parseGrokEntry(entry);
|
|
19255
|
+
if (parsed === null) continue;
|
|
19256
|
+
const bucket = result[parsed.category] ??= {};
|
|
19257
|
+
bucket[parsed.pattern] = action;
|
|
19258
|
+
}
|
|
19259
|
+
};
|
|
19260
|
+
apply(allow, "allow");
|
|
19261
|
+
apply(ask, "ask");
|
|
19262
|
+
apply(deny, "deny");
|
|
19263
|
+
return result;
|
|
19264
|
+
}
|
|
19265
|
+
/**
|
|
19266
|
+
* Legacy coarse fallback: map `[ui] permission_mode` to a canonical action.
|
|
19267
|
+
* `always-approve` ⇒ `allow`; anything else (including a missing mode) ⇒ `ask`.
|
|
19268
|
+
*/
|
|
19269
|
+
function legacyModeAction(parsed) {
|
|
19270
|
+
return (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
19271
|
+
}
|
|
18786
19272
|
/**
|
|
18787
19273
|
* Collapse a rulesync permissions config into Grok's single coarse mode.
|
|
18788
19274
|
* Any `deny`/`ask` rule anywhere keeps prompting (`ask`); otherwise an existing
|
|
@@ -18798,6 +19284,10 @@ function deriveGrokPermissionMode(config) {
|
|
|
18798
19284
|
}
|
|
18799
19285
|
//#endregion
|
|
18800
19286
|
//#region src/features/permissions/hermesagent-permissions.ts
|
|
19287
|
+
/** Collect the glob patterns in a canonical category that carry a given action. */
|
|
19288
|
+
function patternsByAction(category, action) {
|
|
19289
|
+
return Object.entries(category ?? {}).filter(([, value]) => value === action).map(([pattern]) => pattern);
|
|
19290
|
+
}
|
|
18801
19291
|
var HermesagentPermissions = class HermesagentPermissions extends ToolPermissions {
|
|
18802
19292
|
static getSettablePaths() {
|
|
18803
19293
|
return {
|
|
@@ -18821,7 +19311,11 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18821
19311
|
return true;
|
|
18822
19312
|
}
|
|
18823
19313
|
setFileContent(fileContent) {
|
|
18824
|
-
|
|
19314
|
+
const existing = parseHermesConfig(fileContent);
|
|
19315
|
+
const generated = parseHermesConfig(this.fileContent);
|
|
19316
|
+
const merged = deepMergeHermesConfig(existing, generated);
|
|
19317
|
+
if (generated.permissions !== void 0) merged.permissions = generated.permissions;
|
|
19318
|
+
this.fileContent = stringifyHermesConfig(merged);
|
|
18825
19319
|
}
|
|
18826
19320
|
toRulesyncPermissions() {
|
|
18827
19321
|
const config = parseHermesConfig(this.getFileContent());
|
|
@@ -18834,12 +19328,22 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18834
19328
|
}
|
|
18835
19329
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions }) {
|
|
18836
19330
|
const permissions = rulesyncPermissions.getJson();
|
|
19331
|
+
const permissionBlock = permissions.permission ?? {};
|
|
19332
|
+
const commandAllowlist = Object.entries(permissionBlock).flatMap(([, patterns]) => patternsByAction(patterns, "allow"));
|
|
19333
|
+
const bashDeny = patternsByAction(permissionBlock.bash, "deny");
|
|
19334
|
+
const webfetchDeny = patternsByAction(permissionBlock.webfetch, "deny");
|
|
19335
|
+
let config = {};
|
|
19336
|
+
if (commandAllowlist.length > 0) config.command_allowlist = commandAllowlist;
|
|
19337
|
+
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
19338
|
+
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
19339
|
+
enabled: true,
|
|
19340
|
+
domains: webfetchDeny
|
|
19341
|
+
} };
|
|
19342
|
+
if (permissions.hermes && typeof permissions.hermes === "object") config = deepMergeHermesConfig(config, permissions.hermes);
|
|
19343
|
+
config.permissions = { rulesync: permissions };
|
|
18837
19344
|
return new HermesagentPermissions({
|
|
18838
19345
|
outputRoot,
|
|
18839
|
-
fileContent: stringifyHermesConfig(
|
|
18840
|
-
command_allowlist: Object.entries(permissions.permission ?? {}).flatMap(([, patterns]) => Object.entries(patterns).filter(([, action]) => action === "allow").map(([command]) => command)),
|
|
18841
|
-
permissions: { rulesync: permissions }
|
|
18842
|
-
})
|
|
19346
|
+
fileContent: stringifyHermesConfig(config)
|
|
18843
19347
|
});
|
|
18844
19348
|
}
|
|
18845
19349
|
};
|
|
@@ -18860,14 +19364,18 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18860
19364
|
* "executables": [ { "prefix": "git ", "action": "allow" } ],
|
|
18861
19365
|
* "fileEditing": [ { "pattern": "src/**", "action": "allow" } ],
|
|
18862
19366
|
* "mcpTools": [ { "prefix": "search", "action": "allow" } ],
|
|
18863
|
-
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "
|
|
19367
|
+
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "ask" } ]
|
|
18864
19368
|
* }
|
|
18865
19369
|
* }
|
|
18866
19370
|
* ```
|
|
18867
19371
|
*
|
|
18868
19372
|
* Each rule carries a literal `prefix` (matches commands that start with it) or
|
|
18869
|
-
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action
|
|
18870
|
-
*
|
|
19373
|
+
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action`. Junie
|
|
19374
|
+
* documents only `allow` and `ask` as valid actions — there is **no `deny`**
|
|
19375
|
+
* (https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html). rulesync's
|
|
19376
|
+
* canonical `allow`/`ask` map 1:1; a canonical `deny` has no Junie equivalent
|
|
19377
|
+
* and is mapped to the nearest valid action, `ask` (still withholds
|
|
19378
|
+
* auto-approval), with a warning so the downgrade is surfaced.
|
|
18871
19379
|
*
|
|
18872
19380
|
* Category mapping (rulesync canonical <-> Junie rule group):
|
|
18873
19381
|
* - `bash` <-> `executables`
|
|
@@ -18877,8 +19385,11 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18877
19385
|
*
|
|
18878
19386
|
* Categories Junie cannot represent (e.g. `webfetch`) are skipped on export
|
|
18879
19387
|
* (with a warning when they carry rules). The top-level `defaultBehavior` and
|
|
18880
|
-
* `allowReadonlyCommands` settings have no canonical
|
|
18881
|
-
*
|
|
19388
|
+
* `allowReadonlyCommands` settings have no canonical per-glob slot: they are
|
|
19389
|
+
* authored and round-tripped through the `junie` override namespace (see
|
|
19390
|
+
* `JuniePermissionsOverrideSchema`) — lifted into the override on import and
|
|
19391
|
+
* merged back onto the top level on export — and any other unmodeled top-level
|
|
19392
|
+
* key is preserved verbatim.
|
|
18882
19393
|
*
|
|
18883
19394
|
* @see https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html
|
|
18884
19395
|
*/
|
|
@@ -18901,7 +19412,6 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
18901
19412
|
mcpTools: "mcp",
|
|
18902
19413
|
readOutsideProject: "read"
|
|
18903
19414
|
};
|
|
18904
|
-
const JUNIE_DEFAULT_BEHAVIOR = "ask";
|
|
18905
19415
|
function isPermissionAction$1(value) {
|
|
18906
19416
|
return PermissionActionSchema.safeParse(value).success;
|
|
18907
19417
|
}
|
|
@@ -18951,13 +19461,16 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
18951
19461
|
} catch (error) {
|
|
18952
19462
|
throw new Error(`Failed to parse existing Junie allowlist at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18953
19463
|
}
|
|
19464
|
+
const config = rulesyncPermissions.getJson();
|
|
18954
19465
|
const rules = convertRulesyncToJunieRules({
|
|
18955
|
-
config
|
|
19466
|
+
config,
|
|
18956
19467
|
logger
|
|
18957
19468
|
});
|
|
19469
|
+
const override = config.junie;
|
|
19470
|
+
const overrideObj = override !== void 0 && typeof override === "object" ? override : {};
|
|
18958
19471
|
const merged = {
|
|
18959
19472
|
...existing,
|
|
18960
|
-
|
|
19473
|
+
...overrideObj,
|
|
18961
19474
|
rules
|
|
18962
19475
|
};
|
|
18963
19476
|
return new JuniePermissions({
|
|
@@ -18977,7 +19490,12 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
18977
19490
|
throw new Error(`Failed to parse Junie permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18978
19491
|
}
|
|
18979
19492
|
const config = convertJunieToRulesyncPermissions({ allowlist });
|
|
18980
|
-
|
|
19493
|
+
const junieOverride = {};
|
|
19494
|
+
if (typeof allowlist.allowReadonlyCommands === "boolean") junieOverride.allowReadonlyCommands = allowlist.allowReadonlyCommands;
|
|
19495
|
+
if (typeof allowlist.defaultBehavior === "string") junieOverride.defaultBehavior = allowlist.defaultBehavior;
|
|
19496
|
+
const result = { ...config };
|
|
19497
|
+
if (Object.keys(junieOverride).length > 0) result.junie = junieOverride;
|
|
19498
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18981
19499
|
}
|
|
18982
19500
|
validate() {
|
|
18983
19501
|
return {
|
|
@@ -19009,12 +19527,13 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19009
19527
|
continue;
|
|
19010
19528
|
}
|
|
19011
19529
|
for (const [pattern, action] of Object.entries(patterns)) {
|
|
19530
|
+
const junieAction = toJunieAction(action, category, pattern, logger);
|
|
19012
19531
|
const rule = isGlobPattern(pattern) ? {
|
|
19013
19532
|
pattern,
|
|
19014
|
-
action
|
|
19533
|
+
action: junieAction
|
|
19015
19534
|
} : {
|
|
19016
19535
|
prefix: pattern,
|
|
19017
|
-
action
|
|
19536
|
+
action: junieAction
|
|
19018
19537
|
};
|
|
19019
19538
|
(rules[group] ??= []).push(rule);
|
|
19020
19539
|
}
|
|
@@ -19022,6 +19541,19 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19022
19541
|
return rules;
|
|
19023
19542
|
}
|
|
19024
19543
|
/**
|
|
19544
|
+
* Map a canonical action onto a valid Junie allowlist action. Junie supports
|
|
19545
|
+
* only `allow` and `ask`; a canonical `deny` is downgraded to `ask` (the
|
|
19546
|
+
* nearest valid action — both withhold auto-approval) with a warning, so
|
|
19547
|
+
* rulesync never emits a `deny` that Junie would silently ignore.
|
|
19548
|
+
*/
|
|
19549
|
+
function toJunieAction(action, category, pattern, logger) {
|
|
19550
|
+
if (action === "deny") {
|
|
19551
|
+
logger?.warn(`Junie's allowlist supports only 'allow'/'ask' actions; the '${category}' deny rule for '${pattern}' was downgraded to 'ask' (Junie has no 'deny').`);
|
|
19552
|
+
return "ask";
|
|
19553
|
+
}
|
|
19554
|
+
return action;
|
|
19555
|
+
}
|
|
19556
|
+
/**
|
|
19025
19557
|
* Convert a Junie allowlist back into rulesync permissions config. The
|
|
19026
19558
|
* top-level `defaultBehavior` / `allowReadonlyCommands` settings have no
|
|
19027
19559
|
* canonical equivalent and are not imported.
|
|
@@ -19055,6 +19587,31 @@ const KiloPermissionSchema = z.union([z.enum([
|
|
|
19055
19587
|
]))]);
|
|
19056
19588
|
const KiloPermissionsConfigSchema = z.looseObject({ permission: z.optional(z.record(z.string(), KiloPermissionSchema)) });
|
|
19057
19589
|
/**
|
|
19590
|
+
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
19591
|
+
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
19592
|
+
* keys such as `external_directory`, `doom_loop`, `notebook_edit`, ...) is
|
|
19593
|
+
* routed into the `kilo` override on import so it does not leak into other
|
|
19594
|
+
* tools' configs. Kilo folds `write` into `edit`, uses `notebook_edit`/`task`
|
|
19595
|
+
* rather than the canonical `notebookedit`/`agent`, and has no `mcp` key (MCP is
|
|
19596
|
+
* addressed via `mcp__*` tool-name keys), so those canonical names are not Kilo
|
|
19597
|
+
* keys in practice — they simply never appear on the shared side.
|
|
19598
|
+
*/
|
|
19599
|
+
const KILO_SHARED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
19600
|
+
"bash",
|
|
19601
|
+
"read",
|
|
19602
|
+
"edit",
|
|
19603
|
+
"write",
|
|
19604
|
+
"webfetch",
|
|
19605
|
+
"websearch",
|
|
19606
|
+
"grep",
|
|
19607
|
+
"glob",
|
|
19608
|
+
"notebookedit",
|
|
19609
|
+
"agent"
|
|
19610
|
+
]);
|
|
19611
|
+
function isSharedKiloCategory(key) {
|
|
19612
|
+
return key === "*" || KILO_SHARED_CATEGORIES.has(key) || key.startsWith("mcp__");
|
|
19613
|
+
}
|
|
19614
|
+
/**
|
|
19058
19615
|
* Parse a JSONC string and throw on syntax errors. The `jsonc-parser` `parse()` function is
|
|
19059
19616
|
* non-throwing best-effort: invalid input silently yields a partial value (often `undefined`,
|
|
19060
19617
|
* coerced to `{}` by callers). That behavior would silently drop a user's existing `deny` rules
|
|
@@ -19166,12 +19723,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19166
19723
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
19167
19724
|
const parsedPermission = parsed.permission;
|
|
19168
19725
|
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
19169
|
-
const
|
|
19726
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
19727
|
+
const kiloOverride = rulesyncJson.kilo;
|
|
19728
|
+
const incomingPermission = {
|
|
19729
|
+
...rulesyncJson.permission,
|
|
19730
|
+
...kiloOverride?.permission
|
|
19731
|
+
};
|
|
19170
19732
|
const droppedDenyByKey = {};
|
|
19171
|
-
for (const key of Object.
|
|
19172
|
-
const
|
|
19173
|
-
const
|
|
19174
|
-
const nextDenyPatterns = new Set(collectKiloDenyPatterns(rulesyncPermission[key]));
|
|
19733
|
+
for (const [key, value] of Object.entries(incomingPermission)) {
|
|
19734
|
+
const previousDenyPatterns = collectKiloDenyPatterns(existingPermission[key]);
|
|
19735
|
+
const nextDenyPatterns = new Set(collectKiloDenyPatterns(value));
|
|
19175
19736
|
const dropped = previousDenyPatterns.filter((p) => !nextDenyPatterns.has(p));
|
|
19176
19737
|
if (dropped.length > 0) droppedDenyByKey[key] = dropped;
|
|
19177
19738
|
}
|
|
@@ -19179,8 +19740,10 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19179
19740
|
const summary = Object.entries(droppedDenyByKey).map(([key, patterns]) => `${key}: [${patterns.join(", ")}]`).join("; ");
|
|
19180
19741
|
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'.`);
|
|
19181
19742
|
}
|
|
19182
|
-
const mergedPermission = {
|
|
19183
|
-
|
|
19743
|
+
const mergedPermission = {
|
|
19744
|
+
...existingPermission,
|
|
19745
|
+
...incomingPermission
|
|
19746
|
+
};
|
|
19184
19747
|
const nextJson = {
|
|
19185
19748
|
...parsed,
|
|
19186
19749
|
permission: mergedPermission
|
|
@@ -19194,8 +19757,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19194
19757
|
});
|
|
19195
19758
|
}
|
|
19196
19759
|
toRulesyncPermissions() {
|
|
19197
|
-
const
|
|
19198
|
-
|
|
19760
|
+
const rawPermission = this.json.permission ?? {};
|
|
19761
|
+
const shared = {};
|
|
19762
|
+
const overrideOnly = {};
|
|
19763
|
+
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
19764
|
+
else overrideOnly[key] = value;
|
|
19765
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
19766
|
+
permission: shared,
|
|
19767
|
+
kilo: { permission: overrideOnly }
|
|
19768
|
+
} : { permission: shared };
|
|
19769
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19199
19770
|
}
|
|
19200
19771
|
validate() {
|
|
19201
19772
|
try {
|
|
@@ -19225,10 +19796,6 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19225
19796
|
validate: false
|
|
19226
19797
|
});
|
|
19227
19798
|
}
|
|
19228
|
-
normalizePermission(permission) {
|
|
19229
|
-
if (!permission) return {};
|
|
19230
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
19231
|
-
}
|
|
19232
19799
|
};
|
|
19233
19800
|
//#endregion
|
|
19234
19801
|
//#region src/features/permissions/kiro-permissions.ts
|
|
@@ -19286,29 +19853,16 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19286
19853
|
}
|
|
19287
19854
|
const permission = {};
|
|
19288
19855
|
const toolsSettings = parsed.toolsSettings ?? {};
|
|
19289
|
-
const
|
|
19290
|
-
|
|
19291
|
-
const
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
const readDeny = asStringArray(readSettings.deniedPaths);
|
|
19300
|
-
if (readAllow.length > 0 || readDeny.length > 0) {
|
|
19301
|
-
permission.read = {};
|
|
19302
|
-
for (const pattern of readAllow) permission.read[pattern] = "allow";
|
|
19303
|
-
for (const pattern of readDeny) permission.read[pattern] = "deny";
|
|
19304
|
-
}
|
|
19305
|
-
const writeSettings = asRecord$1(toolsSettings.write);
|
|
19306
|
-
const writeAllow = asStringArray(writeSettings.allowedPaths);
|
|
19307
|
-
const writeDeny = asStringArray(writeSettings.deniedPaths);
|
|
19308
|
-
if (writeAllow.length > 0 || writeDeny.length > 0) {
|
|
19309
|
-
permission.write = {};
|
|
19310
|
-
for (const pattern of writeAllow) permission.write[pattern] = "allow";
|
|
19311
|
-
for (const pattern of writeDeny) permission.write[pattern] = "deny";
|
|
19856
|
+
const shellRules = rulesFromArrays(asRecord$1(toolsSettings.shell), "allowedCommands", "deniedCommands");
|
|
19857
|
+
if (Object.keys(shellRules).length > 0) permission.bash = shellRules;
|
|
19858
|
+
for (const category of [
|
|
19859
|
+
"read",
|
|
19860
|
+
"write",
|
|
19861
|
+
"grep",
|
|
19862
|
+
"glob"
|
|
19863
|
+
]) {
|
|
19864
|
+
const rules = rulesFromArrays(asRecord$1(toolsSettings[category]), "allowedPaths", "deniedPaths");
|
|
19865
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
19312
19866
|
}
|
|
19313
19867
|
const allowedTools = new Set(parsed.allowedTools ?? []);
|
|
19314
19868
|
if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
|
|
@@ -19334,45 +19888,63 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19334
19888
|
function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
|
|
19335
19889
|
const nextAllowedTools = new Set(existing.allowedTools ?? []);
|
|
19336
19890
|
const nextToolsSettings = { ...asRecord$1(existing.toolsSettings) };
|
|
19891
|
+
const pathBuckets = {};
|
|
19892
|
+
const pushPath = (key, action, pattern) => {
|
|
19893
|
+
const bucket = pathBuckets[key] ??= {
|
|
19894
|
+
allow: [],
|
|
19895
|
+
deny: []
|
|
19896
|
+
};
|
|
19897
|
+
(action === "allow" ? bucket.allow : bucket.deny).push(pattern);
|
|
19898
|
+
};
|
|
19337
19899
|
const shell = {
|
|
19338
19900
|
allowedCommands: [],
|
|
19339
19901
|
deniedCommands: []
|
|
19340
19902
|
};
|
|
19341
|
-
const read = {
|
|
19342
|
-
allowedPaths: [],
|
|
19343
|
-
deniedPaths: []
|
|
19344
|
-
};
|
|
19345
|
-
const write = {
|
|
19346
|
-
allowedPaths: [],
|
|
19347
|
-
deniedPaths: []
|
|
19348
|
-
};
|
|
19349
19903
|
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19350
19904
|
if (action === "ask") {
|
|
19351
19905
|
logger?.warn(`Kiro permissions do not support "ask". Skipping ${category}:${pattern}`);
|
|
19352
19906
|
continue;
|
|
19353
19907
|
}
|
|
19354
19908
|
if (category === "bash") (action === "allow" ? shell.allowedCommands : shell.deniedCommands).push(pattern);
|
|
19355
|
-
else if (category === "read"
|
|
19356
|
-
else if (category === "edit" || category === "write") (
|
|
19357
|
-
else if (category === "webfetch" || category === "websearch") {
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
} else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
19909
|
+
else if (category === "read" || category === "grep" || category === "glob") pushPath(category, action, pattern);
|
|
19910
|
+
else if (category === "edit" || category === "write") pushPath("write", action, pattern);
|
|
19911
|
+
else if (category === "webfetch" || category === "websearch") applyKiroWebPermission({
|
|
19912
|
+
category,
|
|
19913
|
+
pattern,
|
|
19914
|
+
action,
|
|
19915
|
+
nextAllowedTools,
|
|
19916
|
+
logger
|
|
19917
|
+
});
|
|
19918
|
+
else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
19366
19919
|
}
|
|
19367
19920
|
nextToolsSettings.shell = shell;
|
|
19368
|
-
nextToolsSettings.read = read;
|
|
19369
|
-
nextToolsSettings.write = write;
|
|
19921
|
+
nextToolsSettings.read = pathTable(pathBuckets.read);
|
|
19922
|
+
nextToolsSettings.write = pathTable(pathBuckets.write);
|
|
19923
|
+
for (const key of ["grep", "glob"]) {
|
|
19924
|
+
const bucket = pathBuckets[key];
|
|
19925
|
+
if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
|
|
19926
|
+
}
|
|
19370
19927
|
return {
|
|
19371
19928
|
...existing,
|
|
19372
19929
|
allowedTools: [...nextAllowedTools].toSorted(),
|
|
19373
19930
|
toolsSettings: nextToolsSettings
|
|
19374
19931
|
};
|
|
19375
19932
|
}
|
|
19933
|
+
function pathTable(bucket) {
|
|
19934
|
+
return {
|
|
19935
|
+
allowedPaths: bucket?.allow ?? [],
|
|
19936
|
+
deniedPaths: bucket?.deny ?? []
|
|
19937
|
+
};
|
|
19938
|
+
}
|
|
19939
|
+
function applyKiroWebPermission({ category, pattern, action, nextAllowedTools, logger }) {
|
|
19940
|
+
if (pattern !== "*") {
|
|
19941
|
+
logger?.warn(`Kiro ${category} supports only wildcard (*) via allowedTools. Skipping rule: ${pattern}`);
|
|
19942
|
+
return;
|
|
19943
|
+
}
|
|
19944
|
+
const toolName = category === "webfetch" ? "web_fetch" : "web_search";
|
|
19945
|
+
if (action === "allow") nextAllowedTools.add(toolName);
|
|
19946
|
+
else nextAllowedTools.delete(toolName);
|
|
19947
|
+
}
|
|
19376
19948
|
function asRecord$1(value) {
|
|
19377
19949
|
const result = UnknownRecordSchema.safeParse(value);
|
|
19378
19950
|
return result.success ? result.data : {};
|
|
@@ -19380,6 +19952,17 @@ function asRecord$1(value) {
|
|
|
19380
19952
|
function asStringArray(value) {
|
|
19381
19953
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19382
19954
|
}
|
|
19955
|
+
/**
|
|
19956
|
+
* Build a canonical `{ pattern: action }` map from a Kiro tool settings record's
|
|
19957
|
+
* allow/deny string arrays (e.g. `allowedPaths`/`deniedPaths` or
|
|
19958
|
+
* `allowedCommands`/`deniedCommands`).
|
|
19959
|
+
*/
|
|
19960
|
+
function rulesFromArrays(settings, allowKey, denyKey) {
|
|
19961
|
+
const rules = {};
|
|
19962
|
+
for (const pattern of asStringArray(settings[allowKey])) rules[pattern] = "allow";
|
|
19963
|
+
for (const pattern of asStringArray(settings[denyKey])) rules[pattern] = "deny";
|
|
19964
|
+
return rules;
|
|
19965
|
+
}
|
|
19383
19966
|
//#endregion
|
|
19384
19967
|
//#region src/features/permissions/opencode-permissions.ts
|
|
19385
19968
|
const OpencodePermissionSchema = z.union([z.enum([
|
|
@@ -19391,6 +19974,28 @@ const OpencodePermissionSchema = z.union([z.enum([
|
|
|
19391
19974
|
"ask",
|
|
19392
19975
|
"deny"
|
|
19393
19976
|
]))]);
|
|
19977
|
+
/**
|
|
19978
|
+
* Canonical rulesync permission categories that carry a cross-tool meaning (see
|
|
19979
|
+
* the "Supported tool categories" list in `docs/reference/file-formats.md`).
|
|
19980
|
+
* On import, any OpenCode category outside this set — plus MCP tool names — is
|
|
19981
|
+
* treated as OpenCode-only and routed into the `opencode` override block so a
|
|
19982
|
+
* subsequent `rulesync generate` does not leak it into other tools' configs.
|
|
19983
|
+
*/
|
|
19984
|
+
const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
19985
|
+
"bash",
|
|
19986
|
+
"read",
|
|
19987
|
+
"edit",
|
|
19988
|
+
"write",
|
|
19989
|
+
"webfetch",
|
|
19990
|
+
"websearch",
|
|
19991
|
+
"grep",
|
|
19992
|
+
"glob",
|
|
19993
|
+
"notebookedit",
|
|
19994
|
+
"agent"
|
|
19995
|
+
]);
|
|
19996
|
+
function isSharedPermissionCategory(category) {
|
|
19997
|
+
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
19998
|
+
}
|
|
19394
19999
|
const OpencodePermissionsConfigSchema = z.looseObject({ permission: z.optional(z.union([z.enum([
|
|
19395
20000
|
"allow",
|
|
19396
20001
|
"ask",
|
|
@@ -19452,9 +20057,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19452
20057
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
19453
20058
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
19454
20059
|
}
|
|
20060
|
+
const parsed = parse(fileContent ?? "{}");
|
|
20061
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20062
|
+
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
19455
20063
|
const nextJson = {
|
|
19456
|
-
...
|
|
19457
|
-
permission:
|
|
20064
|
+
...parsed,
|
|
20065
|
+
permission: {
|
|
20066
|
+
...rulesyncJson.permission,
|
|
20067
|
+
...overridePermission
|
|
20068
|
+
}
|
|
19458
20069
|
};
|
|
19459
20070
|
return new OpencodePermissions({
|
|
19460
20071
|
outputRoot,
|
|
@@ -19465,8 +20076,20 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19465
20076
|
});
|
|
19466
20077
|
}
|
|
19467
20078
|
toRulesyncPermissions() {
|
|
19468
|
-
const
|
|
19469
|
-
|
|
20079
|
+
const rawPermission = this.json.permission;
|
|
20080
|
+
if (rawPermission === void 0 || typeof rawPermission === "string") {
|
|
20081
|
+
const permission = this.normalizePermission(rawPermission);
|
|
20082
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
20083
|
+
}
|
|
20084
|
+
const shared = {};
|
|
20085
|
+
const overrideOnly = {};
|
|
20086
|
+
for (const [category, value] of Object.entries(rawPermission)) if (isSharedPermissionCategory(category)) shared[category] = typeof value === "string" ? { "*": value } : value;
|
|
20087
|
+
else overrideOnly[category] = value;
|
|
20088
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20089
|
+
permission: shared,
|
|
20090
|
+
opencode: { permission: overrideOnly }
|
|
20091
|
+
} : { permission: shared };
|
|
20092
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19470
20093
|
}
|
|
19471
20094
|
validate() {
|
|
19472
20095
|
try {
|
|
@@ -19496,10 +20119,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19496
20119
|
validate: false
|
|
19497
20120
|
});
|
|
19498
20121
|
}
|
|
20122
|
+
/**
|
|
20123
|
+
* Normalize the uniform/undefined forms of OpenCode's `permission` field into
|
|
20124
|
+
* the canonical rulesync shape. The object form is handled directly in
|
|
20125
|
+
* `toRulesyncPermissions` (it needs to split shared vs OpenCode-only
|
|
20126
|
+
* categories), so this only covers the two remaining cases.
|
|
20127
|
+
*/
|
|
19499
20128
|
normalizePermission(permission) {
|
|
19500
20129
|
if (!permission) return {};
|
|
19501
|
-
|
|
19502
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
20130
|
+
return { "*": { "*": permission } };
|
|
19503
20131
|
}
|
|
19504
20132
|
};
|
|
19505
20133
|
//#endregion
|
|
@@ -19573,6 +20201,24 @@ function buildQwenPermissionEntry(toolName, pattern) {
|
|
|
19573
20201
|
if (pattern === "*") return toolName;
|
|
19574
20202
|
return `${toolName}(${pattern})`;
|
|
19575
20203
|
}
|
|
20204
|
+
const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
20205
|
+
"approvalMode",
|
|
20206
|
+
"autoAccept",
|
|
20207
|
+
"sandbox",
|
|
20208
|
+
"sandboxImage",
|
|
20209
|
+
"disabled"
|
|
20210
|
+
];
|
|
20211
|
+
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
20212
|
+
function asPlainRecord(value) {
|
|
20213
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20214
|
+
}
|
|
20215
|
+
/** Pick the override-managed keys out of a settings group into a fresh record. */
|
|
20216
|
+
function pickQwenOverrideKeys(group, keys) {
|
|
20217
|
+
const source = asPlainRecord(group);
|
|
20218
|
+
const picked = {};
|
|
20219
|
+
for (const key of keys) if (source[key] !== void 0) picked[key] = source[key];
|
|
20220
|
+
return picked;
|
|
20221
|
+
}
|
|
19576
20222
|
var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
19577
20223
|
constructor(params) {
|
|
19578
20224
|
super({
|
|
@@ -19638,6 +20284,15 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19638
20284
|
...settings,
|
|
19639
20285
|
permissions: mergedPermissions
|
|
19640
20286
|
};
|
|
20287
|
+
const override = config.qwencode;
|
|
20288
|
+
if (override?.tools !== void 0) merged.tools = {
|
|
20289
|
+
...asPlainRecord(settings.tools),
|
|
20290
|
+
...asPlainRecord(override.tools)
|
|
20291
|
+
};
|
|
20292
|
+
if (override?.security !== void 0) merged.security = {
|
|
20293
|
+
...asPlainRecord(settings.security),
|
|
20294
|
+
...asPlainRecord(override.security)
|
|
20295
|
+
};
|
|
19641
20296
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
19642
20297
|
return new QwencodePermissions({
|
|
19643
20298
|
outputRoot,
|
|
@@ -19663,7 +20318,14 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19663
20318
|
ask: permissions.ask ?? [],
|
|
19664
20319
|
deny: permissions.deny ?? []
|
|
19665
20320
|
});
|
|
19666
|
-
|
|
20321
|
+
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
20322
|
+
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
20323
|
+
const qwencodeOverride = {};
|
|
20324
|
+
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
20325
|
+
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
20326
|
+
const result = { ...config };
|
|
20327
|
+
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
20328
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19667
20329
|
}
|
|
19668
20330
|
validate() {
|
|
19669
20331
|
try {
|
|
@@ -19824,6 +20486,16 @@ function toPermissionsTable(value) {
|
|
|
19824
20486
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
19825
20487
|
return { ...value };
|
|
19826
20488
|
}
|
|
20489
|
+
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_allowed_tools", "plan_mode_read_only_commands"];
|
|
20490
|
+
function asReasonixRecord(value) {
|
|
20491
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20492
|
+
}
|
|
20493
|
+
function pickReasonixKeys(source, keys) {
|
|
20494
|
+
const record = asReasonixRecord(source);
|
|
20495
|
+
const picked = {};
|
|
20496
|
+
for (const key of keys) if (record[key] !== void 0) picked[key] = record[key];
|
|
20497
|
+
return picked;
|
|
20498
|
+
}
|
|
19827
20499
|
var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
19828
20500
|
toml;
|
|
19829
20501
|
constructor(params) {
|
|
@@ -19885,6 +20557,15 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19885
20557
|
...parsed,
|
|
19886
20558
|
permissions: mergedPermissions
|
|
19887
20559
|
};
|
|
20560
|
+
const override = config.reasonix;
|
|
20561
|
+
if (override?.sandbox !== void 0) merged.sandbox = {
|
|
20562
|
+
...asReasonixRecord(parsed.sandbox),
|
|
20563
|
+
...asReasonixRecord(override.sandbox)
|
|
20564
|
+
};
|
|
20565
|
+
if (override?.agent !== void 0) merged.agent = {
|
|
20566
|
+
...asReasonixRecord(parsed.agent),
|
|
20567
|
+
...asReasonixRecord(override.agent)
|
|
20568
|
+
};
|
|
19888
20569
|
const fileContent = smolToml.stringify(merged);
|
|
19889
20570
|
return new ReasonixPermissions({
|
|
19890
20571
|
outputRoot,
|
|
@@ -19901,7 +20582,14 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19901
20582
|
ask: toStringArray$1(permissions.ask),
|
|
19902
20583
|
deny: toStringArray$1(permissions.deny)
|
|
19903
20584
|
});
|
|
19904
|
-
|
|
20585
|
+
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
20586
|
+
const agentPlanMode = pickReasonixKeys(this.toml.agent, REASONIX_OVERRIDE_AGENT_KEYS);
|
|
20587
|
+
const reasonixOverride = {};
|
|
20588
|
+
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
20589
|
+
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
20590
|
+
const result = { ...config };
|
|
20591
|
+
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
20592
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19905
20593
|
}
|
|
19906
20594
|
validate() {
|
|
19907
20595
|
try {
|
|
@@ -20465,6 +21153,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20465
21153
|
if (deny.size > 0) nextTool.denylist = [...deny].toSorted();
|
|
20466
21154
|
tools[vibeToolName] = nextTool;
|
|
20467
21155
|
}
|
|
21156
|
+
applyVibeSensitivePatterns(tools, rulesyncPermissions.getJson().vibe);
|
|
20468
21157
|
config.tools = tools;
|
|
20469
21158
|
if (enabledTools.size > 0) config.enabled_tools = [...enabledTools].toSorted();
|
|
20470
21159
|
else delete config.enabled_tools;
|
|
@@ -20481,16 +21170,25 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20481
21170
|
}
|
|
20482
21171
|
toRulesyncPermissions() {
|
|
20483
21172
|
const permission = {};
|
|
21173
|
+
const vibeOverridePermission = {};
|
|
20484
21174
|
for (const tool of toStringArray(this.toml.enabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "allow";
|
|
20485
21175
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
20486
21176
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
20487
|
-
const
|
|
21177
|
+
const category = toCanonicalToolName$1(vibeToolName);
|
|
21178
|
+
const rules = ensurePermission(permission, category);
|
|
20488
21179
|
const action = fromVibePermission(toolConfig.permission);
|
|
20489
21180
|
if (action !== void 0) rules["*"] = action;
|
|
20490
21181
|
for (const pattern of toStringArray(toolConfig.allow ?? toolConfig.allowlist)) rules[pattern] = "allow";
|
|
20491
21182
|
for (const pattern of toStringArray(toolConfig.deny ?? toolConfig.denylist)) rules[pattern] = "deny";
|
|
21183
|
+
const sensitivePatterns = toStringArray(toolConfig.sensitive_patterns);
|
|
21184
|
+
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
20492
21185
|
}
|
|
20493
|
-
|
|
21186
|
+
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
21187
|
+
const json = Object.keys(vibeOverridePermission).length > 0 ? {
|
|
21188
|
+
permission,
|
|
21189
|
+
vibe: { permission: vibeOverridePermission }
|
|
21190
|
+
} : { permission };
|
|
21191
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
20494
21192
|
}
|
|
20495
21193
|
validate() {
|
|
20496
21194
|
try {
|
|
@@ -20517,6 +21215,22 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20517
21215
|
});
|
|
20518
21216
|
}
|
|
20519
21217
|
};
|
|
21218
|
+
/**
|
|
21219
|
+
* Apply the Vibe-scoped override's per-tool `sensitive_patterns` (patterns that
|
|
21220
|
+
* escalate to ASK even when the base permission is ALWAYS). rulesync owns this
|
|
21221
|
+
* list for any category the override names: a present list is set, an empty
|
|
21222
|
+
* one clears it. Categories not named keep whatever the existing file had.
|
|
21223
|
+
*/
|
|
21224
|
+
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
21225
|
+
for (const [category, toolOverride] of Object.entries(vibeOverride?.permission ?? {})) {
|
|
21226
|
+
const vibeToolName = toVibeToolName(category);
|
|
21227
|
+
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
21228
|
+
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
21229
|
+
if (patterns.length > 0) nextTool.sensitive_patterns = [...patterns].toSorted();
|
|
21230
|
+
else delete nextTool.sensitive_patterns;
|
|
21231
|
+
tools[vibeToolName] = nextTool;
|
|
21232
|
+
}
|
|
21233
|
+
}
|
|
20520
21234
|
function parseVibeConfig(fileContent) {
|
|
20521
21235
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
20522
21236
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -20561,6 +21275,11 @@ function ensurePermission(permission, category) {
|
|
|
20561
21275
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
20562
21276
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
20563
21277
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
21278
|
+
const WARP_OVERRIDE_KEYS = [
|
|
21279
|
+
"agent_mode_coding_permissions",
|
|
21280
|
+
"agent_mode_coding_file_read_allowlist",
|
|
21281
|
+
"agent_mode_execute_readonly_commands"
|
|
21282
|
+
];
|
|
20564
21283
|
/**
|
|
20565
21284
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
20566
21285
|
* channel). The home directory is resolved by the processor through
|
|
@@ -20596,8 +21315,16 @@ function warpSettingsDir() {
|
|
|
20596
21315
|
* patterns as regexes when targeting Warp (mirrors the Zed permissions
|
|
20597
21316
|
* adapter). Warp has no per-command "ask" list, so `ask` rules are dropped; and
|
|
20598
21317
|
* the command lists only model shell commands, so non-`bash` categories are
|
|
20599
|
-
* skipped (with a warning when they carry `deny` rules).
|
|
20600
|
-
*
|
|
21318
|
+
* skipped (with a warning when they carry `deny` rules).
|
|
21319
|
+
*
|
|
21320
|
+
* Warp's `[agents.profiles]` table also exposes file-read/read-only autonomy
|
|
21321
|
+
* knobs that do not fit the canonical `allow | ask | deny` per-command model:
|
|
21322
|
+
* `agent_mode_coding_permissions`, `agent_mode_coding_file_read_allowlist`, and
|
|
21323
|
+
* `agent_mode_execute_readonly_commands`. These are authored and round-tripped
|
|
21324
|
+
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
21325
|
+
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
21326
|
+
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
21327
|
+
* MCP allow/deny is a separate surface not modeled here.
|
|
20601
21328
|
*
|
|
20602
21329
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
20603
21330
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -20642,12 +21369,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20642
21369
|
} catch (error) {
|
|
20643
21370
|
throw new Error(`Failed to parse existing Warp settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
20644
21371
|
}
|
|
21372
|
+
const config = rulesyncPermissions.getJson();
|
|
20645
21373
|
const { allow, deny } = convertRulesyncToWarpPermissions({
|
|
20646
|
-
config
|
|
21374
|
+
config,
|
|
20647
21375
|
logger
|
|
20648
21376
|
});
|
|
20649
21377
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
20650
21378
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
21379
|
+
const override = config.warp;
|
|
21380
|
+
if (isRecord(override)) Object.assign(profiles, override);
|
|
20651
21381
|
const mergedAllow = uniq(allow.toSorted());
|
|
20652
21382
|
const mergedDeny = uniq(deny.toSorted());
|
|
20653
21383
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -20678,7 +21408,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20678
21408
|
allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
20679
21409
|
deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
20680
21410
|
});
|
|
20681
|
-
|
|
21411
|
+
const warpOverride = {};
|
|
21412
|
+
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
21413
|
+
const result = { ...config };
|
|
21414
|
+
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
21415
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
20682
21416
|
}
|
|
20683
21417
|
validate() {
|
|
20684
21418
|
return {
|
|
@@ -24044,8 +24778,9 @@ const DevinSkillFrontmatterSchema = z.looseObject({
|
|
|
24044
24778
|
* Represents a Devin (now Devin Desktop) skill directory.
|
|
24045
24779
|
* Devin supports skills in both project mode under .devin/skills/
|
|
24046
24780
|
* (preferred since the Devin Desktop rebrand; .devin/skills/ is the legacy
|
|
24047
|
-
* fallback the tool still reads) and global mode under
|
|
24048
|
-
* (
|
|
24781
|
+
* fallback the tool still reads) and global mode under the Devin-native
|
|
24782
|
+
* ~/.config/devin/skills/ (consistent with Devin's global agents/rules paths;
|
|
24783
|
+
* the legacy channel-dependent ~/.codeium/<channel>/skills/ is no longer emitted).
|
|
24049
24784
|
*/
|
|
24050
24785
|
var DevinSkill = class DevinSkill extends ToolSkill {
|
|
24051
24786
|
constructor({ outputRoot = process.cwd(), relativeDirPath = DevinSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -24067,7 +24802,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
|
|
|
24067
24802
|
}
|
|
24068
24803
|
}
|
|
24069
24804
|
static getSettablePaths({ global = false } = {}) {
|
|
24070
|
-
if (global) return { relativeDirPath:
|
|
24805
|
+
if (global) return { relativeDirPath: DEVIN_GLOBAL_SKILLS_DIR_PATH };
|
|
24071
24806
|
return { relativeDirPath: DEVIN_SKILLS_DIR_PATH };
|
|
24072
24807
|
}
|
|
24073
24808
|
getFrontmatter() {
|
|
@@ -27126,7 +27861,7 @@ const QwencodeSubagentFrontmatterSchema = z.looseObject({
|
|
|
27126
27861
|
disallowedTools: z.optional(z.array(z.string())),
|
|
27127
27862
|
maxTurns: z.optional(z.number()),
|
|
27128
27863
|
color: z.optional(z.string()),
|
|
27129
|
-
mcpServers: z.optional(z.array(z.string())),
|
|
27864
|
+
mcpServers: z.optional(z.union([z.array(z.string()), z.record(z.string(), z.looseObject({}))])),
|
|
27130
27865
|
hooks: z.optional(z.unknown())
|
|
27131
27866
|
});
|
|
27132
27867
|
var QwencodeSubagent = class QwencodeSubagent extends ToolSubagent {
|
|
@@ -37107,4 +37842,4 @@ async function importPermissionsCore(params) {
|
|
|
37107
37842
|
//#endregion
|
|
37108
37843
|
export { CLIError as $, IgnoreProcessor as A, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as At, toolCommandFactories as B, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Bt, PermissionsProcessorToolTargetSchema as C, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Ct, McpProcessorToolTargetSchema as D, RULESYNC_HOOKS_FILE_NAME as Dt, McpProcessor as E, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Et, HooksProcessorToolTargetSchema as F, RULESYNC_PERMISSIONS_FILE_NAME as Ft, CLAUDECODE_SKILLS_DIR_PATH as G, CLAUDECODE_LOCAL_RULE_FILE_NAME as H, formatError as Ht, toolHooksFactories as I, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as It, stringifyFrontmatter as J, RulesyncCommand as K, RulesyncHooks as L, RULESYNC_RELATIVE_DIR_PATH as Lt, toolIgnoreFactories as M, RULESYNC_MCP_RELATIVE_FILE_PATH as Mt, RulesyncIgnore as N, RULESYNC_MCP_SCHEMA_URL as Nt, toolMcpFactories as O, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Ot, HooksProcessor as P, RULESYNC_OVERVIEW_FILE_NAME as Pt, JsonLogger as Q, CommandsProcessor as R, RULESYNC_RULES_RELATIVE_DIR_PATH as Rt, PermissionsProcessor as S, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as St, RulesyncPermissions as T, RULESYNC_CONFIG_SCHEMA_URL as Tt, CLAUDECODE_MEMORIES_DIR_NAME as U, ALL_FEATURES as Ut, CLAUDECODE_DIR as V, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Vt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as W, ALL_FEATURES_WITH_WILDCARD as Wt, findControlCharacter as X, ConfigResolver as Y, ConsoleLogger as Z, toolSkillFactories as _, ALL_TOOL_TARGETS as _t, RulesProcessor as a, fileExists as at, RulesyncSkillFrontmatterSchema as b, MAX_FILE_SIZE as bt, RulesyncRule as c, getHomeDirectory as ct, SubagentsProcessorToolTargetSchema as d, readFileContent as dt, ErrorCodes as et, toolSubagentFactories as f, removeDirectory as ft, SkillsProcessorToolTargetSchema as g, writeFileContent as gt, SkillsProcessor as h, toPosixPath as ht, convertFromTool as i, ensureDir as it, IgnoreProcessorToolTargetSchema as j, RULESYNC_MCP_FILE_NAME as jt, RulesyncMcp as k, RULESYNC_IGNORE_RELATIVE_FILE_PATH as kt, RulesyncRuleFrontmatterSchema as l, isSymlink as lt, RulesyncSubagentFrontmatterSchema as m, removeTempDirectory as mt, checkRulesyncDirExists as n, createTempDirectory as nt, RulesProcessorToolTargetSchema as o, findFilesByGlobs as ot, RulesyncSubagent as p, removeFile as pt, RulesyncCommandFrontmatterSchema as q, generate as r, directoryExists as rt, toolRuleFactories as s, getFileSize as st, importFromTool as t, checkPathTraversal as tt, SubagentsProcessor as u, listDirectoryFiles as ut, getLocalSkillDirNames as v, ALL_TOOL_TARGETS_WITH_WILDCARD as vt, toolPermissionsFactories as w, RULESYNC_CONFIG_RELATIVE_FILE_PATH as wt, SKILL_FILE_NAME as x, RULESYNC_AIIGNORE_FILE_NAME as xt, RulesyncSkill as y, ToolTargetSchema as yt, CommandsProcessorToolTargetSchema as z, RULESYNC_SKILLS_RELATIVE_DIR_PATH as zt };
|
|
37109
37844
|
|
|
37110
|
-
//# sourceMappingURL=import-
|
|
37845
|
+
//# sourceMappingURL=import-B7VzSVUK.js.map
|