rulesync 37.1.0 → 38.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DYYG88Rs.cjs → import-B9NOtObR.cjs} +2563 -336
- package/dist/{import-BsEFYJsg.js → import-CoFTCNq7.js} +2548 -321
- package/dist/{import-BsEFYJsg.js.map → import-CoFTCNq7.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -630,6 +630,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
630
630
|
"musecode",
|
|
631
631
|
"mimocode",
|
|
632
632
|
"omp",
|
|
633
|
+
"openclaw",
|
|
633
634
|
"opencode",
|
|
634
635
|
"pi",
|
|
635
636
|
"pool",
|
|
@@ -682,6 +683,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
682
683
|
"kiro",
|
|
683
684
|
"kiro-cli",
|
|
684
685
|
"kiro-ide",
|
|
686
|
+
"lettacode",
|
|
685
687
|
"mimocode",
|
|
686
688
|
"omp",
|
|
687
689
|
"opencode",
|
|
@@ -909,6 +911,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
909
911
|
"rovodev",
|
|
910
912
|
"tabnine",
|
|
911
913
|
"takt",
|
|
914
|
+
"trae",
|
|
912
915
|
"vibe",
|
|
913
916
|
"warp",
|
|
914
917
|
"warpcli",
|
|
@@ -4742,10 +4745,13 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
4742
4745
|
* eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
4743
4746
|
* `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
|
|
4744
4747
|
* `PostCompaction` (fires after context compaction; added in the Devin CLI
|
|
4745
|
-
* stable changelog — https://docs.devin.ai/cli/changelog/stable).
|
|
4746
|
-
*
|
|
4747
|
-
*
|
|
4748
|
-
*
|
|
4748
|
+
* stable changelog — https://docs.devin.ai/cli/changelog/stable). CLI
|
|
4749
|
+
* v3000.11.92 added `PreCompact` and `PostCompact`, which run before and after
|
|
4750
|
+
* compaction with `manual` / `auto` trigger matchers; `PostCompaction` stays
|
|
4751
|
+
* supported. The tool/permission events (`PreToolUse`/`PostToolUse`/
|
|
4752
|
+
* `PermissionRequest`) carry a `matcher` (regex against `tool_name`) and
|
|
4753
|
+
* `PreCompact` a trigger matcher; the other session/turn/compaction events do
|
|
4754
|
+
* not.
|
|
4749
4755
|
*
|
|
4750
4756
|
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
4751
4757
|
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
@@ -4761,6 +4767,7 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
4761
4767
|
"beforeSubmitPrompt",
|
|
4762
4768
|
"stop",
|
|
4763
4769
|
"permissionRequest",
|
|
4770
|
+
"preCompact",
|
|
4764
4771
|
"postCompact"
|
|
4765
4772
|
];
|
|
4766
4773
|
/**
|
|
@@ -6144,12 +6151,17 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
|
6144
6151
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6145
6152
|
stop: "Stop",
|
|
6146
6153
|
permissionRequest: "PermissionRequest",
|
|
6154
|
+
preCompact: "PreCompact",
|
|
6147
6155
|
postCompact: "PostCompaction"
|
|
6148
6156
|
};
|
|
6149
6157
|
/**
|
|
6150
|
-
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
6158
|
+
* Map Devin Local PascalCase event names to canonical camelCase. `PostCompact`
|
|
6159
|
+
* (CLI v3000.11.92) is accepted on import as an alias of `PostCompaction`.
|
|
6151
6160
|
*/
|
|
6152
|
-
const DEVIN_TO_CANONICAL_EVENT_NAMES =
|
|
6161
|
+
const DEVIN_TO_CANONICAL_EVENT_NAMES = {
|
|
6162
|
+
...Object.fromEntries(Object.entries(CANONICAL_TO_DEVIN_EVENT_NAMES).map(([k, v]) => [v, k])),
|
|
6163
|
+
PostCompact: "postCompact"
|
|
6164
|
+
};
|
|
6153
6165
|
/**
|
|
6154
6166
|
* Map canonical camelCase event names to AugmentCode PascalCase.
|
|
6155
6167
|
* Auggie reuses the same PascalCase names as Claude for the events it supports.
|
|
@@ -7082,6 +7094,17 @@ const CodexcliMcpToolConfigSchema = z.looseObject({
|
|
|
7082
7094
|
function isCodexcliToolConfig(value) {
|
|
7083
7095
|
return CodexcliMcpToolConfigSchema.safeParse(value).success;
|
|
7084
7096
|
}
|
|
7097
|
+
const CrushChannelReplyRouteSchema = z.looseObject({
|
|
7098
|
+
tool: z.string(),
|
|
7099
|
+
target_param: z.optional(z.string()),
|
|
7100
|
+
target_meta: z.optional(z.string())
|
|
7101
|
+
});
|
|
7102
|
+
const CrushChannelReplySchema = z.looseObject({
|
|
7103
|
+
user: z.optional(CrushChannelReplyRouteSchema),
|
|
7104
|
+
group: z.optional(CrushChannelReplyRouteSchema),
|
|
7105
|
+
message_param: z.optional(z.string()),
|
|
7106
|
+
suppress_tools: z.optional(z.array(z.string()))
|
|
7107
|
+
});
|
|
7085
7108
|
const McpServerSchema = z.looseObject({
|
|
7086
7109
|
type: z.optional(z.enum([
|
|
7087
7110
|
"local",
|
|
@@ -7123,6 +7146,9 @@ const McpServerSchema = z.looseObject({
|
|
|
7123
7146
|
crushOauthClientSecret: z.optional(z.string()),
|
|
7124
7147
|
crushOauthCallbackPort: z.optional(z.int()),
|
|
7125
7148
|
crushSessionless: z.optional(z.boolean()),
|
|
7149
|
+
crushChannelEnabled: z.optional(z.boolean()),
|
|
7150
|
+
crushChannelReply: z.optional(CrushChannelReplySchema),
|
|
7151
|
+
zcodeOauth: z.optional(z.looseObject({ type: z.string() })),
|
|
7126
7152
|
poolAllow: z.optional(z.array(z.string())),
|
|
7127
7153
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
7128
7154
|
/**
|
|
@@ -7234,6 +7260,7 @@ const RulesyncMcpFileSchema = z.looseObject({
|
|
|
7234
7260
|
kiro: z.optional(toolScopedMcpSchema),
|
|
7235
7261
|
mimocode: z.optional(toolScopedMcpSchema),
|
|
7236
7262
|
omp: z.optional(toolScopedMcpSchema),
|
|
7263
|
+
openclaw: z.optional(toolScopedMcpSchema),
|
|
7237
7264
|
opencode: z.optional(toolScopedMcpSchema),
|
|
7238
7265
|
pi: z.optional(toolScopedMcpSchema),
|
|
7239
7266
|
qoder: z.optional(toolScopedMcpSchema),
|
|
@@ -7581,11 +7608,16 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
7581
7608
|
"crushOauthClientSecret",
|
|
7582
7609
|
"crushOauthCallbackPort",
|
|
7583
7610
|
"crushSessionless",
|
|
7611
|
+
"crushChannelEnabled",
|
|
7612
|
+
"crushChannelReply",
|
|
7584
7613
|
"poolAllow",
|
|
7614
|
+
"zcodeOauth",
|
|
7585
7615
|
"oauth_client_id",
|
|
7586
7616
|
"oauth_client_secret",
|
|
7587
7617
|
"oauth_callback_port",
|
|
7588
7618
|
"sessionless",
|
|
7619
|
+
"channel_enabled",
|
|
7620
|
+
"channel_reply",
|
|
7589
7621
|
"enabled"
|
|
7590
7622
|
])];
|
|
7591
7623
|
}));
|
|
@@ -8321,8 +8353,12 @@ const TabninePermissionsOverrideSchema = z.looseObject({
|
|
|
8321
8353
|
* are merged into the matching `reasonix.toml` table and emitted only for
|
|
8322
8354
|
* Reasonix, while the shared `permission` block continues to drive
|
|
8323
8355
|
* `[permissions].allow`/`ask`/`deny`. Kept `looseObject` (verbatim passthrough).
|
|
8324
|
-
*
|
|
8325
|
-
*
|
|
8356
|
+
* The user-only `[sandbox]` keys (`allow_devices`, `egress_unlisted`,
|
|
8357
|
+
* `host_authorities`) are written only by `--global` generate: Reasonix replaces
|
|
8358
|
+
* a project value with the user's (`narrowSandbox` in `project_scope.go`).
|
|
8359
|
+
* Note: the whole `[sandbox]` table round-trips (minus the user-only keys of a
|
|
8360
|
+
* project file), but only the plan-mode keys are re-extracted from `[agent]` on
|
|
8361
|
+
* import — other `agent` keys authored here reach
|
|
8326
8362
|
* `reasonix.toml` on generate but are not re-extracted back into the override.
|
|
8327
8363
|
*
|
|
8328
8364
|
* @example
|
|
@@ -8354,14 +8390,52 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8354
8390
|
* output-style` rule option) or a `plugin-name:style-name` reference. The
|
|
8355
8391
|
* shared `permission` block continues to drive `permissions.allow`/`ask`/`deny`.
|
|
8356
8392
|
*
|
|
8393
|
+
* The other documented settings with no canonical category are authored here
|
|
8394
|
+
* too and merged into the same file, key by key, over what the file already
|
|
8395
|
+
* holds: `general.defaultPermissionMode`, `tools.core` / `tools.exclude`, the
|
|
8396
|
+
* MCP approval and loading keys under `mcp` (`allowed`, `excluded`,
|
|
8397
|
+
* `enableAllProjectMcpServers`, `enabledProjectMcpServers`, `lazyLoad`),
|
|
8398
|
+
* `agents.overrides`, the top-level `agentsMdExcludes` and the top-level
|
|
8399
|
+
* `autoMode` classifier guidance (`allow` / `soft_deny` / `environment`).
|
|
8400
|
+
* Qoder reads `autoMode` from the user settings only, so it is written by
|
|
8401
|
+
* `--global` generate alone. Only the keys listed here are written; any other
|
|
8402
|
+
* key of these groups or of `autoMode` (`tools.shell.pager`,
|
|
8403
|
+
* `general.preferredEditor`, ...) is skipped with a warning. Each authored key
|
|
8404
|
+
* is laid over what the file holds; removing it from the override leaves the
|
|
8405
|
+
* previously written value in place.
|
|
8406
|
+
*
|
|
8357
8407
|
* @see https://docs.qoder.com/cli/output-styles
|
|
8358
8408
|
* @see https://docs.qoder.com/cli/settings-reference
|
|
8409
|
+
* @see https://docs.qoder.com/en/cli/permissions
|
|
8359
8410
|
* @example
|
|
8360
8411
|
* { "outputStyle": "concise-review" }
|
|
8412
|
+
* @example
|
|
8413
|
+
* { "mcp": { "enabledProjectMcpServers": ["playwright"], "lazyLoad": true },
|
|
8414
|
+
* "tools": { "exclude": ["WebSearch"] },
|
|
8415
|
+
* "autoMode": { "environment": ["This is a pnpm monorepo"] } }
|
|
8361
8416
|
*/
|
|
8362
8417
|
const QoderPermissionsOverrideSchema = z.looseObject({
|
|
8363
8418
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
8364
|
-
outputStyle: z.optional(z.string().check(z.trim(), z.minLength(1)))
|
|
8419
|
+
outputStyle: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
8420
|
+
general: z.optional(z.looseObject({ defaultPermissionMode: z.optional(z.string().check(z.trim(), z.minLength(1))) })),
|
|
8421
|
+
tools: z.optional(z.looseObject({
|
|
8422
|
+
core: z.optional(z.array(z.string())),
|
|
8423
|
+
exclude: z.optional(z.array(z.string()))
|
|
8424
|
+
})),
|
|
8425
|
+
mcp: z.optional(z.looseObject({
|
|
8426
|
+
allowed: z.optional(z.array(z.string())),
|
|
8427
|
+
excluded: z.optional(z.array(z.string())),
|
|
8428
|
+
enableAllProjectMcpServers: z.optional(z.boolean()),
|
|
8429
|
+
enabledProjectMcpServers: z.optional(z.array(z.string())),
|
|
8430
|
+
lazyLoad: z.optional(z.boolean())
|
|
8431
|
+
})),
|
|
8432
|
+
agents: z.optional(z.looseObject({ overrides: z.optional(z.record(z.string(), z.looseObject({}))) })),
|
|
8433
|
+
autoMode: z.optional(z.looseObject({
|
|
8434
|
+
allow: z.optional(z.array(z.string())),
|
|
8435
|
+
soft_deny: z.optional(z.array(z.string())),
|
|
8436
|
+
environment: z.optional(z.array(z.string()))
|
|
8437
|
+
})),
|
|
8438
|
+
agentsMdExcludes: z.optional(z.array(z.string()))
|
|
8365
8439
|
});
|
|
8366
8440
|
/**
|
|
8367
8441
|
* One native Factory Droid permission rule. `match.prefix` positions are either
|
|
@@ -9149,14 +9223,40 @@ const ZedPermissionsOverrideSchema = z.looseObject({
|
|
|
9149
9223
|
*
|
|
9150
9224
|
* @example
|
|
9151
9225
|
* { "sandbox": { "allowed_domains": ["github.com"], "network_mode": "limited" } }
|
|
9226
|
+
* The same override carries the repo-level plugin governance lists Devin reads
|
|
9227
|
+
* from a checkout's `.devin/config.json`: `requiredPlugins` (plugin sources
|
|
9228
|
+
* installed automatically), `optionalPlugins` (endorsed sources that carve an
|
|
9229
|
+
* exception out of a forbidden entry; never installed) and `forbiddenPlugins`
|
|
9230
|
+
* (plugin identities or `*` globs that may not load). A source is a string
|
|
9231
|
+
* (`"owner/repo"` or a git URL) or an object such as
|
|
9232
|
+
* `{ "source": "github", "repo": "owner/repo" }`. They are a repo governance
|
|
9233
|
+
* level, so they are emitted at project scope only.
|
|
9234
|
+
*
|
|
9152
9235
|
* @example
|
|
9153
9236
|
* { "sandbox": { "excluded": { "allow": ["Exec(git status *)"], "deny": ["Exec(git tag *)"] } } }
|
|
9237
|
+
* @example
|
|
9238
|
+
* { "forbiddenPlugins": ["*"], "optionalPlugins": ["acme/approved"] }
|
|
9154
9239
|
* @see https://docs.devin.ai/cli/sandbox
|
|
9155
9240
|
* @see https://docs.devin.ai/cli/reference/configuration/config-file
|
|
9241
|
+
* @see https://docs.devin.ai/cli/extensibility/plugins/overview
|
|
9242
|
+
*/
|
|
9243
|
+
const DevinPluginSourceListSchema = z.array(z.union([z.string(), z.looseObject({})]));
|
|
9244
|
+
/**
|
|
9245
|
+
* Shapes of the Devin repo-level plugin governance lists, keyed by their
|
|
9246
|
+
* `.devin/config.json` name. Shared by the override schema and the import,
|
|
9247
|
+
* which drops a list that would not pass it.
|
|
9156
9248
|
*/
|
|
9249
|
+
const DEVIN_PLUGIN_GOVERNANCE_SCHEMAS = {
|
|
9250
|
+
requiredPlugins: DevinPluginSourceListSchema,
|
|
9251
|
+
optionalPlugins: DevinPluginSourceListSchema,
|
|
9252
|
+
forbiddenPlugins: z.array(z.string())
|
|
9253
|
+
};
|
|
9157
9254
|
const DevinPermissionsOverrideSchema = z.looseObject({
|
|
9158
9255
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
9159
|
-
sandbox: z.optional(z.looseObject({}))
|
|
9256
|
+
sandbox: z.optional(z.looseObject({})),
|
|
9257
|
+
requiredPlugins: z.optional(DEVIN_PLUGIN_GOVERNANCE_SCHEMAS.requiredPlugins),
|
|
9258
|
+
optionalPlugins: z.optional(DEVIN_PLUGIN_GOVERNANCE_SCHEMAS.optionalPlugins),
|
|
9259
|
+
forbiddenPlugins: z.optional(DEVIN_PLUGIN_GOVERNANCE_SCHEMAS.forbiddenPlugins)
|
|
9160
9260
|
});
|
|
9161
9261
|
/**
|
|
9162
9262
|
* Permissions configuration.
|
|
@@ -9230,6 +9330,7 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
9230
9330
|
"kimi-code": z.optional(KimiCodePermissionsOverrideSchema),
|
|
9231
9331
|
roo: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9232
9332
|
rovodev: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9333
|
+
trae: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9233
9334
|
zoocode: z.optional(CanonicalPermissionsOverrideSchema)
|
|
9234
9335
|
});
|
|
9235
9336
|
/**
|
|
@@ -11088,6 +11189,11 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
11088
11189
|
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
11089
11190
|
metadata: z.optional(z.looseObject({}))
|
|
11090
11191
|
})),
|
|
11192
|
+
musecode: z.optional(z.looseObject({
|
|
11193
|
+
license: z.optional(z.string()),
|
|
11194
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
11195
|
+
metadata: z.optional(z.looseObject({}))
|
|
11196
|
+
})),
|
|
11091
11197
|
zcode: z.optional(z.looseObject({
|
|
11092
11198
|
when_to_use: z.optional(z.string()),
|
|
11093
11199
|
license: z.optional(z.string()),
|
|
@@ -11518,8 +11624,20 @@ const GITLABDUO_MR_REVIEW_INSTRUCTIONS_FILE_NAME = "mr-review-instructions.yaml"
|
|
|
11518
11624
|
const GITLABDUO_COMMANDS_DIR = join(".agents", "commands");
|
|
11519
11625
|
/** User custom slash commands. */
|
|
11520
11626
|
const GITLABDUO_GLOBAL_COMMANDS_DIR = join(GITLABDUO_DIR, "commands");
|
|
11521
|
-
/**
|
|
11522
|
-
|
|
11627
|
+
/**
|
|
11628
|
+
* Project skills live in the cross-tool `.agents/skills/` directory, which
|
|
11629
|
+
* GitLab Duo reads alongside a non-hidden `skills/` directory at the project
|
|
11630
|
+
* root; when both hold a skill of the same name, `.agents/skills/` wins.
|
|
11631
|
+
*
|
|
11632
|
+
* @see https://docs.gitlab.com/user/duo_agent_platform/customize/agent_skills/
|
|
11633
|
+
*/
|
|
11634
|
+
const GITLABDUO_SKILLS_DIR = join(".agents", "skills");
|
|
11635
|
+
/**
|
|
11636
|
+
* The project-root `skills/` directory GitLab Duo also reads. Earlier rulesync
|
|
11637
|
+
* versions wrote project skills there; it is now an import-only root, since it
|
|
11638
|
+
* routinely holds hand-authored skills rulesync must not manage.
|
|
11639
|
+
*/
|
|
11640
|
+
const GITLABDUO_LEGACY_SKILLS_DIR = "skills";
|
|
11523
11641
|
/** User skills (requires `--enable-global-skills` / `GITLAB_ENABLE_GLOBAL_SKILLS`). */
|
|
11524
11642
|
const GITLABDUO_GLOBAL_SKILLS_DIR = join(GITLABDUO_DIR, "skills");
|
|
11525
11643
|
//#endregion
|
|
@@ -11681,6 +11799,239 @@ const CODEXCLI_OVERRIDE_KEYS = [
|
|
|
11681
11799
|
"approvals_reviewer",
|
|
11682
11800
|
"tui"
|
|
11683
11801
|
];
|
|
11802
|
+
const SINGLE_CHAR_ESCAPES = {
|
|
11803
|
+
"'": "'",
|
|
11804
|
+
"\"": "\"",
|
|
11805
|
+
"\\": "\\",
|
|
11806
|
+
b: "\b",
|
|
11807
|
+
f: "\f",
|
|
11808
|
+
n: "\n",
|
|
11809
|
+
r: "\r",
|
|
11810
|
+
t: " ",
|
|
11811
|
+
v: "\v"
|
|
11812
|
+
};
|
|
11813
|
+
const LINE_TERMINATORS = /* @__PURE__ */ new Set([
|
|
11814
|
+
"\n",
|
|
11815
|
+
"\r",
|
|
11816
|
+
"\u2028",
|
|
11817
|
+
"\u2029"
|
|
11818
|
+
]);
|
|
11819
|
+
const ID_START = /[\p{L}\p{Nl}$_]/u;
|
|
11820
|
+
const ID_CONTINUE = /[\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}$_\u200C\u200D]/u;
|
|
11821
|
+
const WHITESPACE$1 = /[\t\n\v\f\r \u00A0\u2028\u2029\uFEFF\p{Zs}]/u;
|
|
11822
|
+
var Json5Reader = class {
|
|
11823
|
+
text;
|
|
11824
|
+
pos = 0;
|
|
11825
|
+
constructor(text) {
|
|
11826
|
+
this.text = text;
|
|
11827
|
+
}
|
|
11828
|
+
parse() {
|
|
11829
|
+
this.skipTrivia();
|
|
11830
|
+
const value = this.readValue(0);
|
|
11831
|
+
this.skipTrivia();
|
|
11832
|
+
if (this.pos < this.text.length) this.fail(`unexpected ${this.describe()} after the end of the document`);
|
|
11833
|
+
return value;
|
|
11834
|
+
}
|
|
11835
|
+
fail(message) {
|
|
11836
|
+
let line = 1;
|
|
11837
|
+
let column = 1;
|
|
11838
|
+
for (let i = 0; i < this.pos && i < this.text.length; i++) if (this.text[i] === "\n") {
|
|
11839
|
+
line++;
|
|
11840
|
+
column = 1;
|
|
11841
|
+
} else column++;
|
|
11842
|
+
throw new SyntaxError(`JSON5: ${message} at line ${line}, column ${column}`);
|
|
11843
|
+
}
|
|
11844
|
+
describe() {
|
|
11845
|
+
const char = this.text[this.pos];
|
|
11846
|
+
return char === void 0 ? "end of input" : `character ${JSON.stringify(char)}`;
|
|
11847
|
+
}
|
|
11848
|
+
peek(offset = 0) {
|
|
11849
|
+
return this.text[this.pos + offset];
|
|
11850
|
+
}
|
|
11851
|
+
skipTrivia() {
|
|
11852
|
+
while (this.pos < this.text.length) {
|
|
11853
|
+
const char = this.text[this.pos];
|
|
11854
|
+
if (WHITESPACE$1.test(char)) this.pos++;
|
|
11855
|
+
else if (char === "/" && this.peek(1) === "/") {
|
|
11856
|
+
this.pos += 2;
|
|
11857
|
+
while (this.pos < this.text.length && !LINE_TERMINATORS.has(this.text[this.pos])) this.pos++;
|
|
11858
|
+
} else if (char === "/" && this.peek(1) === "*") {
|
|
11859
|
+
const end = this.text.indexOf("*/", this.pos + 2);
|
|
11860
|
+
if (end === -1) this.fail("unterminated block comment");
|
|
11861
|
+
this.pos = end + 2;
|
|
11862
|
+
} else return;
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
11865
|
+
readValue(depth) {
|
|
11866
|
+
const char = this.peek();
|
|
11867
|
+
if (char === "{" || char === "[") {
|
|
11868
|
+
if (depth >= 256) this.fail(`document nests deeper than 256 levels`);
|
|
11869
|
+
return char === "{" ? this.readObject(depth + 1) : this.readArray(depth + 1);
|
|
11870
|
+
}
|
|
11871
|
+
if (char === "\"" || char === "'") return this.readString();
|
|
11872
|
+
if (char !== void 0 && /[0-9+\-.IN]/.test(char)) return this.readNumber();
|
|
11873
|
+
for (const [literal, value] of [
|
|
11874
|
+
["true", true],
|
|
11875
|
+
["false", false],
|
|
11876
|
+
["null", null]
|
|
11877
|
+
]) if (this.text.startsWith(literal, this.pos) && !this.isIdContinueAt(literal.length)) {
|
|
11878
|
+
this.pos += literal.length;
|
|
11879
|
+
return value;
|
|
11880
|
+
}
|
|
11881
|
+
return this.fail(`unexpected ${this.describe()}`);
|
|
11882
|
+
}
|
|
11883
|
+
isIdContinueAt(offset) {
|
|
11884
|
+
const char = this.peek(offset);
|
|
11885
|
+
return char !== void 0 && (ID_CONTINUE.test(char) || char === "\\");
|
|
11886
|
+
}
|
|
11887
|
+
readObject(depth) {
|
|
11888
|
+
const result = {};
|
|
11889
|
+
this.pos++;
|
|
11890
|
+
this.skipTrivia();
|
|
11891
|
+
while (this.peek() !== "}") {
|
|
11892
|
+
const key = this.readKey();
|
|
11893
|
+
this.skipTrivia();
|
|
11894
|
+
if (this.peek() !== ":") this.fail(`expected ':' after the key ${JSON.stringify(key)}`);
|
|
11895
|
+
this.pos++;
|
|
11896
|
+
this.skipTrivia();
|
|
11897
|
+
const value = this.readValue(depth);
|
|
11898
|
+
Object.defineProperty(result, key, {
|
|
11899
|
+
value,
|
|
11900
|
+
enumerable: true,
|
|
11901
|
+
writable: true,
|
|
11902
|
+
configurable: true
|
|
11903
|
+
});
|
|
11904
|
+
this.skipTrivia();
|
|
11905
|
+
if (this.peek() === ",") {
|
|
11906
|
+
this.pos++;
|
|
11907
|
+
this.skipTrivia();
|
|
11908
|
+
continue;
|
|
11909
|
+
}
|
|
11910
|
+
if (this.peek() !== "}") this.fail(`expected ',' or '}' but found ${this.describe()}`);
|
|
11911
|
+
}
|
|
11912
|
+
this.pos++;
|
|
11913
|
+
return result;
|
|
11914
|
+
}
|
|
11915
|
+
readArray(depth) {
|
|
11916
|
+
const result = [];
|
|
11917
|
+
this.pos++;
|
|
11918
|
+
this.skipTrivia();
|
|
11919
|
+
while (this.peek() !== "]") {
|
|
11920
|
+
result.push(this.readValue(depth));
|
|
11921
|
+
this.skipTrivia();
|
|
11922
|
+
if (this.peek() === ",") {
|
|
11923
|
+
this.pos++;
|
|
11924
|
+
this.skipTrivia();
|
|
11925
|
+
continue;
|
|
11926
|
+
}
|
|
11927
|
+
if (this.peek() !== "]") this.fail(`expected ',' or ']' but found ${this.describe()}`);
|
|
11928
|
+
}
|
|
11929
|
+
this.pos++;
|
|
11930
|
+
return result;
|
|
11931
|
+
}
|
|
11932
|
+
readKey() {
|
|
11933
|
+
const char = this.peek();
|
|
11934
|
+
if (char === "\"" || char === "'") return this.readString();
|
|
11935
|
+
let key = "";
|
|
11936
|
+
while (this.pos < this.text.length) {
|
|
11937
|
+
const start = this.pos;
|
|
11938
|
+
let next = this.peek();
|
|
11939
|
+
if (next === "\\") {
|
|
11940
|
+
if (this.peek(1) !== "u") this.fail("expected a \\u escape in an identifier key");
|
|
11941
|
+
this.pos += 2;
|
|
11942
|
+
next = this.readHex(4);
|
|
11943
|
+
} else this.pos++;
|
|
11944
|
+
if (!(key === "" ? ID_START.test(next) : ID_CONTINUE.test(next))) {
|
|
11945
|
+
this.pos = start;
|
|
11946
|
+
break;
|
|
11947
|
+
}
|
|
11948
|
+
key += next;
|
|
11949
|
+
}
|
|
11950
|
+
if (key === "") this.fail(`expected a key but found ${this.describe()}`);
|
|
11951
|
+
return key;
|
|
11952
|
+
}
|
|
11953
|
+
/** Reads `length` hex digits at the cursor and returns the character they encode. */
|
|
11954
|
+
readHex(length) {
|
|
11955
|
+
const digits = this.text.slice(this.pos, this.pos + length);
|
|
11956
|
+
if (digits.length !== length || !/^[0-9a-fA-F]+$/.test(digits)) this.fail(`expected ${length} hexadecimal digits`);
|
|
11957
|
+
this.pos += length;
|
|
11958
|
+
return String.fromCharCode(Number.parseInt(digits, 16));
|
|
11959
|
+
}
|
|
11960
|
+
readString() {
|
|
11961
|
+
const quote = this.peek();
|
|
11962
|
+
this.pos++;
|
|
11963
|
+
let result = "";
|
|
11964
|
+
while (true) {
|
|
11965
|
+
const char = this.peek();
|
|
11966
|
+
if (char === void 0) this.fail("unterminated string");
|
|
11967
|
+
if (char === quote) {
|
|
11968
|
+
this.pos++;
|
|
11969
|
+
return result;
|
|
11970
|
+
}
|
|
11971
|
+
if (char === "\n" || char === "\r") this.fail("unescaped line break in a string");
|
|
11972
|
+
if (char !== "\\") {
|
|
11973
|
+
result += char;
|
|
11974
|
+
this.pos++;
|
|
11975
|
+
continue;
|
|
11976
|
+
}
|
|
11977
|
+
this.pos++;
|
|
11978
|
+
const escape = this.peek();
|
|
11979
|
+
if (escape === void 0) this.fail("unterminated string");
|
|
11980
|
+
if (escape === "\r") this.pos += this.peek(1) === "\n" ? 2 : 1;
|
|
11981
|
+
else if (LINE_TERMINATORS.has(escape)) this.pos++;
|
|
11982
|
+
else if (escape === "x") {
|
|
11983
|
+
this.pos++;
|
|
11984
|
+
result += this.readHex(2);
|
|
11985
|
+
} else if (escape === "u") {
|
|
11986
|
+
this.pos++;
|
|
11987
|
+
result += this.readHex(4);
|
|
11988
|
+
} else if (escape === "0") {
|
|
11989
|
+
if (/[0-9]/.test(this.peek(1) ?? "")) this.fail("octal escapes are not allowed");
|
|
11990
|
+
result += "\0";
|
|
11991
|
+
this.pos++;
|
|
11992
|
+
} else if (/[1-9]/.test(escape)) this.fail("octal escapes are not allowed");
|
|
11993
|
+
else {
|
|
11994
|
+
result += SINGLE_CHAR_ESCAPES[escape] ?? escape;
|
|
11995
|
+
this.pos++;
|
|
11996
|
+
}
|
|
11997
|
+
}
|
|
11998
|
+
}
|
|
11999
|
+
readNumber() {
|
|
12000
|
+
const start = this.pos;
|
|
12001
|
+
let sign = 1;
|
|
12002
|
+
if (this.peek() === "+" || this.peek() === "-") {
|
|
12003
|
+
sign = this.peek() === "-" ? -1 : 1;
|
|
12004
|
+
this.pos++;
|
|
12005
|
+
}
|
|
12006
|
+
for (const [literal, value] of [["Infinity", Infinity], ["NaN", NaN]]) if (this.text.startsWith(literal, this.pos)) {
|
|
12007
|
+
if (this.isIdContinueAt(literal.length)) break;
|
|
12008
|
+
this.pos += literal.length;
|
|
12009
|
+
return sign * value;
|
|
12010
|
+
}
|
|
12011
|
+
if (this.peek() === "0" && (this.peek(1) === "x" || this.peek(1) === "X")) {
|
|
12012
|
+
this.pos += 2;
|
|
12013
|
+
const digits = /^[0-9a-fA-F]+/.exec(this.text.slice(this.pos))?.[0];
|
|
12014
|
+
if (digits === void 0) this.fail("expected hexadecimal digits");
|
|
12015
|
+
this.pos += digits.length;
|
|
12016
|
+
return sign * Number.parseInt(digits, 16);
|
|
12017
|
+
}
|
|
12018
|
+
const match = /^(?:(?:0|[1-9][0-9]*)(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?/.exec(this.text.slice(this.pos));
|
|
12019
|
+
if (match === null) {
|
|
12020
|
+
this.pos = start;
|
|
12021
|
+
this.fail(`invalid number`);
|
|
12022
|
+
}
|
|
12023
|
+
this.pos += match[0].length;
|
|
12024
|
+
if (this.isIdContinueAt(0)) this.fail(`invalid number`);
|
|
12025
|
+
return sign * Number(match[0]);
|
|
12026
|
+
}
|
|
12027
|
+
};
|
|
12028
|
+
/**
|
|
12029
|
+
* Parse a JSON5 document. Throws a `SyntaxError` naming the line and column of
|
|
12030
|
+
* the first problem.
|
|
12031
|
+
*/
|
|
12032
|
+
function parseJson5(text) {
|
|
12033
|
+
return new Json5Reader(text).parse();
|
|
12034
|
+
}
|
|
11684
12035
|
//#endregion
|
|
11685
12036
|
//#region src/utils/quote-value.ts
|
|
11686
12037
|
/**
|
|
@@ -11830,6 +12181,7 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
|
|
|
11830
12181
|
if (format === "yaml") parsed = loadYaml(fileContent);
|
|
11831
12182
|
else if (format === "toml") parsed = parse$2(fileContent);
|
|
11832
12183
|
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
12184
|
+
else if (format === "json5") parsed = parseJson5(fileContent);
|
|
11833
12185
|
else if (jsoncParseErrors === "error") {
|
|
11834
12186
|
const errors = [];
|
|
11835
12187
|
parsed = parse$1(fileContent, errors, { allowTrailingComma: true });
|
|
@@ -12427,8 +12779,12 @@ function serializeSharedConfig({ format, document, existingContent, leadingKeys
|
|
|
12427
12779
|
format,
|
|
12428
12780
|
document
|
|
12429
12781
|
});
|
|
12430
|
-
if (format !== "jsonc" || existingContent.trim() === "") return whole;
|
|
12782
|
+
if (format !== "jsonc" && format !== "json5" || existingContent.trim() === "") return whole;
|
|
12431
12783
|
const wholeBecause = (reason) => {
|
|
12784
|
+
if (format === "json5") throw new Json5EditRefusedError({
|
|
12785
|
+
filePath,
|
|
12786
|
+
reason
|
|
12787
|
+
});
|
|
12432
12788
|
warnAboutWholeJsoncRewrite({
|
|
12433
12789
|
filePath,
|
|
12434
12790
|
reason,
|
|
@@ -12439,7 +12795,7 @@ function serializeSharedConfig({ format, document, existingContent, leadingKeys
|
|
|
12439
12795
|
try {
|
|
12440
12796
|
const errors = [];
|
|
12441
12797
|
const root = parseTree(existingContent, errors, { allowTrailingComma: true });
|
|
12442
|
-
if (root === void 0 || errors.length > 0) return wholeBecause("it could not be parsed");
|
|
12798
|
+
if (root === void 0 || errors.length > 0) return wholeBecause(format === "json5" ? "it uses JSON5-only syntax (such as unquoted keys, single quotes, hex numbers, Infinity or NaN)" : "it could not be parsed");
|
|
12443
12799
|
if (root.type !== "object") return wholeBecause("its root is not an object");
|
|
12444
12800
|
const uneditable = uneditableKeyOf(root);
|
|
12445
12801
|
if (uneditable !== void 0) {
|
|
@@ -12468,10 +12824,22 @@ function serializeSharedConfig({ format, document, existingContent, leadingKeys
|
|
|
12468
12824
|
leadingKeys
|
|
12469
12825
|
});
|
|
12470
12826
|
} catch (error) {
|
|
12827
|
+
if (error instanceof Json5EditRefusedError) throw error;
|
|
12471
12828
|
return wholeBecause(`the editor refused it (${stripControlCharacters(formatError(error))})`);
|
|
12472
12829
|
}
|
|
12473
12830
|
}
|
|
12474
12831
|
/**
|
|
12832
|
+
* Thrown instead of rewriting a `json5` file whole (see
|
|
12833
|
+
* {@link serializeSharedConfig}); nothing is written.
|
|
12834
|
+
*/
|
|
12835
|
+
var Json5EditRefusedError = class extends Error {
|
|
12836
|
+
constructor({ filePath, reason }) {
|
|
12837
|
+
const subject = filePath === void 0 ? "a shared JSON5 config file" : quoteForLog(filePath);
|
|
12838
|
+
super(`Refusing to update ${subject}: rulesync cannot edit it in place because ${reason}, and rewriting it whole would drop its comments and could change its values. Convert it to JSONC-compatible syntax (double-quoted keys and strings, decimal numbers; comments and trailing commas are fine), or add the entries by hand.`);
|
|
12839
|
+
this.name = "Json5EditRefusedError";
|
|
12840
|
+
}
|
|
12841
|
+
};
|
|
12842
|
+
/**
|
|
12475
12843
|
* Report, once per run and file, that a JSONC file is being written whole
|
|
12476
12844
|
* instead of edited in place. Named by `filePath` when the caller knows it;
|
|
12477
12845
|
* a caller serializing text it never read from disk has no name to give.
|
|
@@ -12534,6 +12902,7 @@ const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
|
12534
12902
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
12535
12903
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
12536
12904
|
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
12905
|
+
const OPENCLAW_CONFIG_SHARED_FILE_KEY = ".openclaw/openclaw.json";
|
|
12537
12906
|
const POOL_PROJECT_SETTINGS_SHARED_FILE_KEY = ".poolside/settings.yaml";
|
|
12538
12907
|
const POOL_GLOBAL_SETTINGS_SHARED_FILE_KEY = ".config/poolside/settings.yaml";
|
|
12539
12908
|
/**
|
|
@@ -12989,7 +13358,12 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12989
13358
|
format: "json",
|
|
12990
13359
|
features: { permissions: {
|
|
12991
13360
|
kind: "replace-owned-keys",
|
|
12992
|
-
ownedKeys: [
|
|
13361
|
+
ownedKeys: [
|
|
13362
|
+
"permissions",
|
|
13363
|
+
"requiredPlugins",
|
|
13364
|
+
"optionalPlugins",
|
|
13365
|
+
"forbiddenPlugins"
|
|
13366
|
+
]
|
|
12993
13367
|
} }
|
|
12994
13368
|
},
|
|
12995
13369
|
".config/devin/config.json": {
|
|
@@ -13137,10 +13511,27 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13137
13511
|
},
|
|
13138
13512
|
permissions: {
|
|
13139
13513
|
kind: "replace-owned-keys",
|
|
13140
|
-
ownedKeys: [
|
|
13514
|
+
ownedKeys: [
|
|
13515
|
+
"permissions",
|
|
13516
|
+
"outputStyle",
|
|
13517
|
+
"general",
|
|
13518
|
+
"tools",
|
|
13519
|
+
"mcp",
|
|
13520
|
+
"agents",
|
|
13521
|
+
"autoMode",
|
|
13522
|
+
"agentsMdExcludes"
|
|
13523
|
+
]
|
|
13141
13524
|
}
|
|
13142
13525
|
}
|
|
13143
13526
|
},
|
|
13527
|
+
".trae/permission/global.json": {
|
|
13528
|
+
format: "json",
|
|
13529
|
+
invalidRootPolicy: "error",
|
|
13530
|
+
features: { permissions: {
|
|
13531
|
+
kind: "replace-owned-keys",
|
|
13532
|
+
ownedKeys: ["customProfiles", "resourceAuthorization"]
|
|
13533
|
+
} }
|
|
13534
|
+
},
|
|
13144
13535
|
".tabnine/agent/settings.json": {
|
|
13145
13536
|
format: "json",
|
|
13146
13537
|
invalidRootPolicy: "error",
|
|
@@ -13414,6 +13805,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13414
13805
|
ownedKeys: ["toolPermissions"]
|
|
13415
13806
|
}
|
|
13416
13807
|
}
|
|
13808
|
+
},
|
|
13809
|
+
[OPENCLAW_CONFIG_SHARED_FILE_KEY]: {
|
|
13810
|
+
format: "json5",
|
|
13811
|
+
invalidRootPolicy: "error",
|
|
13812
|
+
features: { mcp: {
|
|
13813
|
+
kind: "replace-owned-keys",
|
|
13814
|
+
ownedKeys: ["mcp"]
|
|
13815
|
+
} }
|
|
13417
13816
|
}
|
|
13418
13817
|
};
|
|
13419
13818
|
/**
|
|
@@ -21956,6 +22355,190 @@ var KiroIdeCommand = class extends KiroCommand {
|
|
|
21956
22355
|
}
|
|
21957
22356
|
};
|
|
21958
22357
|
//#endregion
|
|
22358
|
+
//#region src/constants/lettacode-paths.ts
|
|
22359
|
+
const LETTACODE_DIR = ".letta";
|
|
22360
|
+
const LETTACODE_SETTINGS_FILE_NAME = "settings.json";
|
|
22361
|
+
const LETTACODE_SETTINGS_LOCAL_FILE_NAME = "settings.local.json";
|
|
22362
|
+
const LETTACODE_IGNORE_FILE_NAME = ".lettaignore";
|
|
22363
|
+
const LETTACODE_AGENTS_DIR_PATH = join(LETTACODE_DIR, "agents");
|
|
22364
|
+
const LETTACODE_PROJECT_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
22365
|
+
const LETTACODE_GLOBAL_SKILLS_DIR_PATH = join(LETTACODE_DIR, "skills");
|
|
22366
|
+
const LETTACODE_PROJECT_COMMANDS_DIR_PATH = ".commands";
|
|
22367
|
+
const LETTACODE_GLOBAL_COMMANDS_DIR_PATH = join(LETTACODE_DIR, "commands");
|
|
22368
|
+
//#endregion
|
|
22369
|
+
//#region src/features/commands/lettacode-command.ts
|
|
22370
|
+
/**
|
|
22371
|
+
* The only frontmatter keys Letta Code reads from a custom command
|
|
22372
|
+
* (`parseCommandFile` in `src/cli/commands/custom.ts`). Anything else — e.g.
|
|
22373
|
+
* `allowed-tools`, `model` or `disable-model-invocation` copied over from a
|
|
22374
|
+
* Claude Code command — is parsed and ignored upstream.
|
|
22375
|
+
*/
|
|
22376
|
+
const LETTACODE_COMMAND_FRONTMATTER_KEYS = ["description", "argument-hint"];
|
|
22377
|
+
const LettacodeCommandFrontmatterSchema = z.looseObject({
|
|
22378
|
+
description: z.optional(z.string()),
|
|
22379
|
+
"argument-hint": z.optional(z.string())
|
|
22380
|
+
});
|
|
22381
|
+
function pickLettacodeFrontmatter({ section, relativeFilePath, logger }) {
|
|
22382
|
+
if (!isRecord$1(section)) return {};
|
|
22383
|
+
const picked = {};
|
|
22384
|
+
for (const [key, value] of Object.entries(section)) {
|
|
22385
|
+
if (LETTACODE_COMMAND_FRONTMATTER_KEYS.includes(key)) {
|
|
22386
|
+
picked[key] = value;
|
|
22387
|
+
continue;
|
|
22388
|
+
}
|
|
22389
|
+
warnWithFallback(logger, `Letta Code commands: dropping lettacode.${stripControlCharacters(key)} from ${quoteForLog(relativeFilePath)}: Letta Code only reads ${LETTACODE_COMMAND_FRONTMATTER_KEYS.join(" and ")}.`);
|
|
22390
|
+
}
|
|
22391
|
+
return picked;
|
|
22392
|
+
}
|
|
22393
|
+
/**
|
|
22394
|
+
* Letta Code custom slash commands: Markdown prompt files under `.commands/`
|
|
22395
|
+
* (project, at the working-directory root) and `~/.letta/commands/` (global),
|
|
22396
|
+
* discovered recursively. The file basename is the command id
|
|
22397
|
+
* (`git/commit.md` is invoked as `/commit`; `findCustomCommand` matches the
|
|
22398
|
+
* id only), so two files with the same basename collide and the first one
|
|
22399
|
+
* found wins; a project command wins over a global one with the same id. Frontmatter supports `description` (defaulting to the first body
|
|
22400
|
+
* line) and `argument-hint`; the body supports `$ARGUMENTS`, `$1`..`$9`,
|
|
22401
|
+
* `` !`command` `` and `@file`.
|
|
22402
|
+
*
|
|
22403
|
+
* `description` comes from the rulesync frontmatter; a `lettacode:` section
|
|
22404
|
+
* may set `argument-hint` (and override `description`). Other keys in that
|
|
22405
|
+
* section are dropped with a warning because Letta Code never reads them.
|
|
22406
|
+
*
|
|
22407
|
+
* @see https://docs.letta.com/platform/cli/slash-commands
|
|
22408
|
+
* @see https://github.com/letta-ai/letta-code/blob/main/src/cli/commands/custom.ts
|
|
22409
|
+
*/
|
|
22410
|
+
var LettacodeCommand = class LettacodeCommand extends ToolCommand {
|
|
22411
|
+
frontmatter;
|
|
22412
|
+
body;
|
|
22413
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
22414
|
+
if (rest.validate) {
|
|
22415
|
+
const result = LettacodeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
22416
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
22417
|
+
}
|
|
22418
|
+
super({
|
|
22419
|
+
...rest,
|
|
22420
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
22421
|
+
});
|
|
22422
|
+
this.frontmatter = frontmatter;
|
|
22423
|
+
this.body = body;
|
|
22424
|
+
}
|
|
22425
|
+
static getSettablePaths({ global = false } = {}) {
|
|
22426
|
+
return { relativeDirPath: global ? LETTACODE_GLOBAL_COMMANDS_DIR_PATH : LETTACODE_PROJECT_COMMANDS_DIR_PATH };
|
|
22427
|
+
}
|
|
22428
|
+
getBody() {
|
|
22429
|
+
return this.body;
|
|
22430
|
+
}
|
|
22431
|
+
getFrontmatter() {
|
|
22432
|
+
return this.frontmatter;
|
|
22433
|
+
}
|
|
22434
|
+
toRulesyncCommand() {
|
|
22435
|
+
const { description, ...restFields } = this.frontmatter;
|
|
22436
|
+
const rulesyncFrontmatter = {
|
|
22437
|
+
targets: ["*"],
|
|
22438
|
+
description,
|
|
22439
|
+
...Object.keys(restFields).length > 0 && { lettacode: restFields }
|
|
22440
|
+
};
|
|
22441
|
+
return new RulesyncCommand({
|
|
22442
|
+
outputRoot: ".",
|
|
22443
|
+
frontmatter: rulesyncFrontmatter,
|
|
22444
|
+
body: this.body,
|
|
22445
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
22446
|
+
relativeFilePath: this.relativeFilePath,
|
|
22447
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
22448
|
+
validate: true
|
|
22449
|
+
});
|
|
22450
|
+
}
|
|
22451
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false, logger }) {
|
|
22452
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
22453
|
+
const lettacodeFields = pickLettacodeFrontmatter({
|
|
22454
|
+
section: rulesyncFrontmatter.lettacode,
|
|
22455
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
22456
|
+
logger
|
|
22457
|
+
});
|
|
22458
|
+
const frontmatter = {
|
|
22459
|
+
...rulesyncFrontmatter.description !== void 0 && { description: rulesyncFrontmatter.description },
|
|
22460
|
+
...lettacodeFields
|
|
22461
|
+
};
|
|
22462
|
+
const paths = this.getSettablePaths({ global });
|
|
22463
|
+
return new LettacodeCommand({
|
|
22464
|
+
outputRoot,
|
|
22465
|
+
frontmatter,
|
|
22466
|
+
body: rulesyncCommand.getBody(),
|
|
22467
|
+
relativeDirPath: paths.relativeDirPath,
|
|
22468
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
22469
|
+
validate,
|
|
22470
|
+
global
|
|
22471
|
+
});
|
|
22472
|
+
}
|
|
22473
|
+
validate() {
|
|
22474
|
+
if (!this.frontmatter) return {
|
|
22475
|
+
success: true,
|
|
22476
|
+
error: null
|
|
22477
|
+
};
|
|
22478
|
+
const result = LettacodeCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
22479
|
+
if (result.success) return {
|
|
22480
|
+
success: true,
|
|
22481
|
+
error: null
|
|
22482
|
+
};
|
|
22483
|
+
return {
|
|
22484
|
+
success: false,
|
|
22485
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
22486
|
+
};
|
|
22487
|
+
}
|
|
22488
|
+
/**
|
|
22489
|
+
* Warn when two targeted commands share a basename. Letta Code invokes a
|
|
22490
|
+
* command by its basename only, so `git/commit.md` and `release/commit.md`
|
|
22491
|
+
* are both `/commit` and only the first one Letta Code finds is reachable.
|
|
22492
|
+
* Both files are still written, so this warns instead of failing the run.
|
|
22493
|
+
*/
|
|
22494
|
+
static validateRulesyncCommands({ rulesyncCommands, logger }) {
|
|
22495
|
+
const pathsById = /* @__PURE__ */ new Map();
|
|
22496
|
+
for (const command of rulesyncCommands) {
|
|
22497
|
+
if (!this.isTargetedByRulesyncCommand(command)) continue;
|
|
22498
|
+
const relativeFilePath = command.getRelativeFilePath();
|
|
22499
|
+
const id = basename(relativeFilePath, ".md");
|
|
22500
|
+
pathsById.set(id, [...pathsById.get(id) ?? [], relativeFilePath]);
|
|
22501
|
+
}
|
|
22502
|
+
for (const [id, paths] of pathsById) {
|
|
22503
|
+
if (paths.length < 2) continue;
|
|
22504
|
+
logger.warn(`Letta Code invokes commands by file basename, so ${paths.map((path) => quoteForLog(path)).join(", ")} are all /${stripControlCharacters(id)} and only one of them is reachable. Rename them to make each invocable.`);
|
|
22505
|
+
}
|
|
22506
|
+
}
|
|
22507
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
22508
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
22509
|
+
rulesyncCommand,
|
|
22510
|
+
toolTarget: "lettacode"
|
|
22511
|
+
});
|
|
22512
|
+
}
|
|
22513
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
22514
|
+
const paths = this.getSettablePaths({ global });
|
|
22515
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
22516
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
22517
|
+
const result = LettacodeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
22518
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
22519
|
+
return new LettacodeCommand({
|
|
22520
|
+
outputRoot,
|
|
22521
|
+
relativeDirPath: paths.relativeDirPath,
|
|
22522
|
+
relativeFilePath,
|
|
22523
|
+
frontmatter: result.data,
|
|
22524
|
+
body: body.trim(),
|
|
22525
|
+
validate,
|
|
22526
|
+
global
|
|
22527
|
+
});
|
|
22528
|
+
}
|
|
22529
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
22530
|
+
return new LettacodeCommand({
|
|
22531
|
+
outputRoot,
|
|
22532
|
+
relativeDirPath,
|
|
22533
|
+
relativeFilePath,
|
|
22534
|
+
frontmatter: {},
|
|
22535
|
+
body: "",
|
|
22536
|
+
validate: false,
|
|
22537
|
+
global
|
|
22538
|
+
});
|
|
22539
|
+
}
|
|
22540
|
+
};
|
|
22541
|
+
//#endregion
|
|
21959
22542
|
//#region src/constants/mimocode-paths.ts
|
|
21960
22543
|
/**
|
|
21961
22544
|
* MiMo Code (Xiaomi's OpenCode fork) reads `.mimocode/` in the project and
|
|
@@ -21973,7 +22556,8 @@ const MIMOCODE_LAYOUT = {
|
|
|
21973
22556
|
configDir: MIMOCODE_DIR,
|
|
21974
22557
|
jsonFileName: "mimocode.json",
|
|
21975
22558
|
jsoncFileName: "mimocode.jsonc",
|
|
21976
|
-
readsV2ConfigSpellings: false
|
|
22559
|
+
readsV2ConfigSpellings: false,
|
|
22560
|
+
readsDotDirConfig: false
|
|
21977
22561
|
};
|
|
21978
22562
|
//#endregion
|
|
21979
22563
|
//#region src/constants/opencode-paths.ts
|
|
@@ -22000,7 +22584,8 @@ const OPENCODE_LAYOUT = {
|
|
|
22000
22584
|
configDir: ".",
|
|
22001
22585
|
jsonFileName: OPENCODE_JSON_FILE_NAME,
|
|
22002
22586
|
jsoncFileName: OPENCODE_JSONC_FILE_NAME,
|
|
22003
|
-
readsV2ConfigSpellings: true
|
|
22587
|
+
readsV2ConfigSpellings: true,
|
|
22588
|
+
readsDotDirConfig: true
|
|
22004
22589
|
};
|
|
22005
22590
|
//#endregion
|
|
22006
22591
|
//#region src/features/opencode-config.ts
|
|
@@ -22034,6 +22619,107 @@ async function readOpencodeConfig({ outputRoot, global = false, layout = OPENCOD
|
|
|
22034
22619
|
});
|
|
22035
22620
|
}
|
|
22036
22621
|
/**
|
|
22622
|
+
* Reads the project-scope config files OpenCode merges over the root
|
|
22623
|
+
* `opencode.json(c)`: `.opencode/opencode.json`, then `.opencode/opencode.jsonc`,
|
|
22624
|
+
* in the order OpenCode loads them (a later file wins). Returns an empty list
|
|
22625
|
+
* in global mode and for layouts without this location (MiMo Code).
|
|
22626
|
+
*
|
|
22627
|
+
* @see https://opencode.ai/docs/config/#precedence-order
|
|
22628
|
+
* @see https://github.com/anomalyco/opencode/blob/v1.19.3/packages/opencode/src/config/config.ts
|
|
22629
|
+
*/
|
|
22630
|
+
async function readOpencodeDotDirConfigLayers({ outputRoot, global = false, layout = OPENCODE_LAYOUT }) {
|
|
22631
|
+
if (global || !layout.readsDotDirConfig) return [];
|
|
22632
|
+
const configDir = join(outputRoot, layout.dir);
|
|
22633
|
+
const layers = [];
|
|
22634
|
+
for (const fileName of [layout.jsonFileName, layout.jsoncFileName]) {
|
|
22635
|
+
const fileContent = await readFileContentOrNull(join(configDir, fileName));
|
|
22636
|
+
if (fileContent === null) continue;
|
|
22637
|
+
layers.push({
|
|
22638
|
+
relativeFilePath: join(layout.dir, fileName),
|
|
22639
|
+
configDir,
|
|
22640
|
+
config: parseSharedConfig({
|
|
22641
|
+
format: "jsonc",
|
|
22642
|
+
fileContent
|
|
22643
|
+
})
|
|
22644
|
+
});
|
|
22645
|
+
}
|
|
22646
|
+
return layers;
|
|
22647
|
+
}
|
|
22648
|
+
/**
|
|
22649
|
+
* Reads every OpenCode config file that applies at this scope, lowest
|
|
22650
|
+
* precedence first: the root config (see {@link readOpencodeConfig}), then the
|
|
22651
|
+
* `.opencode/` files (see {@link readOpencodeDotDirConfigLayers}).
|
|
22652
|
+
*/
|
|
22653
|
+
async function readOpencodeConfigLayers({ outputRoot, global = false, layout = OPENCODE_LAYOUT }) {
|
|
22654
|
+
return [{
|
|
22655
|
+
configDir: getOpencodeConfigDir({
|
|
22656
|
+
outputRoot,
|
|
22657
|
+
global,
|
|
22658
|
+
layout
|
|
22659
|
+
}),
|
|
22660
|
+
config: await readOpencodeConfig({
|
|
22661
|
+
outputRoot,
|
|
22662
|
+
global,
|
|
22663
|
+
layout
|
|
22664
|
+
})
|
|
22665
|
+
}, ...await readOpencodeDotDirConfigLayers({
|
|
22666
|
+
outputRoot,
|
|
22667
|
+
global,
|
|
22668
|
+
layout
|
|
22669
|
+
})];
|
|
22670
|
+
}
|
|
22671
|
+
/**
|
|
22672
|
+
* Deep-merges `source` over `target` the way OpenCode merges its config files
|
|
22673
|
+
* (remeda's `mergeDeep`): plain objects merge key by key, and any other value —
|
|
22674
|
+
* arrays included — replaces the target's.
|
|
22675
|
+
*/
|
|
22676
|
+
function mergeOpencodeConfigValues(target, source) {
|
|
22677
|
+
if (!isRecord$1(target) || !isRecord$1(source)) return source;
|
|
22678
|
+
const merged = {};
|
|
22679
|
+
for (const [key, value] of Object.entries(target)) setOwnEntry(merged, key, value);
|
|
22680
|
+
for (const [key, value] of Object.entries(source)) setOwnEntry(merged, key, Object.hasOwn(target, key) ? mergeOpencodeConfigValues(target[key], value) : value);
|
|
22681
|
+
return merged;
|
|
22682
|
+
}
|
|
22683
|
+
/**
|
|
22684
|
+
* Merges per-file entry maps (the `command` or `agent` entries of each config
|
|
22685
|
+
* layer, lowest precedence first) into one, as OpenCode does. Returns `null`
|
|
22686
|
+
* when no layer has entries.
|
|
22687
|
+
*/
|
|
22688
|
+
function mergeOpencodeEntryLayers(layers) {
|
|
22689
|
+
let merged = null;
|
|
22690
|
+
for (const entries of layers) {
|
|
22691
|
+
if (entries === null) continue;
|
|
22692
|
+
const next = mergeOpencodeConfigValues(merged ?? {}, entries);
|
|
22693
|
+
merged = isRecord$1(next) ? next : merged;
|
|
22694
|
+
}
|
|
22695
|
+
return merged;
|
|
22696
|
+
}
|
|
22697
|
+
/**
|
|
22698
|
+
* Warns when a `.opencode/opencode.json(c)` file sets entries that the
|
|
22699
|
+
* generated root config also sets. OpenCode merges those files over the root
|
|
22700
|
+
* config, so their entries win and the generated ones never take effect.
|
|
22701
|
+
*
|
|
22702
|
+
* `generated` maps each top-level key rulesync writes (`mcp`, `permission`) to
|
|
22703
|
+
* its generated value. `overlays` are the `.opencode/` layers, already lowered
|
|
22704
|
+
* by the caller where needed.
|
|
22705
|
+
*/
|
|
22706
|
+
function warnOpencodeDotDirOverrides({ generated, overlays, rootFileName, logger }) {
|
|
22707
|
+
if (!logger) return;
|
|
22708
|
+
for (const overlay of overlays) {
|
|
22709
|
+
const overridden = [];
|
|
22710
|
+
for (const [key, value] of Object.entries(generated)) {
|
|
22711
|
+
if (value === void 0 || !Object.hasOwn(overlay.config, key)) continue;
|
|
22712
|
+
const overlayValue = overlay.config[key];
|
|
22713
|
+
if (!isRecord$1(value) || !isRecord$1(overlayValue)) {
|
|
22714
|
+
overridden.push(`"${key}"`);
|
|
22715
|
+
continue;
|
|
22716
|
+
}
|
|
22717
|
+
for (const name of Object.keys(value)) if (Object.hasOwn(overlayValue, name)) overridden.push(`"${key}" > ${quoteValueForWarning(name)}`);
|
|
22718
|
+
}
|
|
22719
|
+
if (overridden.length > 0) logger.warn(`OpenCode merges "${toPosixPath(overlay.relativeFilePath)}" over the generated "${rootFileName}", so its values for these generated entries take precedence: ${overridden.join(", ")}.`);
|
|
22720
|
+
}
|
|
22721
|
+
}
|
|
22722
|
+
/**
|
|
22037
22723
|
* Narrows an unknown value to a plain record of entries keyed by name, as used
|
|
22038
22724
|
* for OpenCode's `command` / `agent` config sections. Returns `null` when the
|
|
22039
22725
|
* value is not a usable object.
|
|
@@ -22476,8 +23162,9 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
22476
23162
|
});
|
|
22477
23163
|
}
|
|
22478
23164
|
/**
|
|
22479
|
-
* Imports commands defined inline in `opencode.json` / `opencode.jsonc`
|
|
22480
|
-
*
|
|
23165
|
+
* Imports commands defined inline in `opencode.json` / `opencode.jsonc` (and,
|
|
23166
|
+
* at project scope, `.opencode/opencode.json(c)`, which OpenCode merges over
|
|
23167
|
+
* them) under the top-level `command` key — or its V2 spelling `commands`, see
|
|
22481
23168
|
* `getOpencodeCommandEntries` — (in addition to the Markdown files under
|
|
22482
23169
|
* `.opencode/commands/`). Each entry's `template` becomes the command body,
|
|
22483
23170
|
* while `description` / `agent` / `model` / `variant` / `subtask` map to the
|
|
@@ -22490,14 +23177,14 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
22490
23177
|
* @see https://opencode.ai/docs/commands/#json
|
|
22491
23178
|
*/
|
|
22492
23179
|
static async loadAdditionalImportFiles({ outputRoot = process.cwd(), global = false } = {}) {
|
|
22493
|
-
const commandEntries =
|
|
22494
|
-
|
|
22495
|
-
|
|
22496
|
-
global,
|
|
22497
|
-
layout: this.layout
|
|
22498
|
-
}),
|
|
23180
|
+
const commandEntries = mergeOpencodeEntryLayers((await readOpencodeConfigLayers({
|
|
23181
|
+
outputRoot,
|
|
23182
|
+
global,
|
|
22499
23183
|
layout: this.layout
|
|
22500
|
-
})
|
|
23184
|
+
})).map(({ config }) => getOpencodeCommandEntries({
|
|
23185
|
+
config,
|
|
23186
|
+
layout: this.layout
|
|
23187
|
+
})));
|
|
22501
23188
|
if (!commandEntries) return [];
|
|
22502
23189
|
const paths = this.getSettablePaths({ global });
|
|
22503
23190
|
const commands = [];
|
|
@@ -23883,6 +24570,7 @@ var TaktCommand = class TaktCommand extends ToolCommand {
|
|
|
23883
24570
|
* @see https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
23884
24571
|
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23885
24572
|
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
24573
|
+
* @see https://docs.trae.ai/ide/permission-and-approval?_lang=en
|
|
23886
24574
|
*/
|
|
23887
24575
|
const TRAE_DIR = ".trae";
|
|
23888
24576
|
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
@@ -23896,6 +24584,13 @@ const TRAE_SKILLS_DIR_PATH = join(TRAE_DIR, "skills");
|
|
|
23896
24584
|
const TRAE_COMMANDS_DIR_PATH = join(TRAE_DIR, "commands");
|
|
23897
24585
|
/** Indexing ignore list (gitignore syntax), `.trae/.ignore`, project scope only. */
|
|
23898
24586
|
const TRAE_IGNORE_FILE_NAME = ".ignore";
|
|
24587
|
+
/**
|
|
24588
|
+
* Global permission policy: `~/.trae/permission/global.json`, user scope only. It
|
|
24589
|
+
* also holds the custom permission mode (`customProfiles`), so it is edited in
|
|
24590
|
+
* place rather than owned outright.
|
|
24591
|
+
*/
|
|
24592
|
+
const TRAE_PERMISSION_DIR_PATH = join(TRAE_DIR, "permission");
|
|
24593
|
+
const TRAE_PERMISSION_FILE_NAME = "global.json";
|
|
23899
24594
|
//#endregion
|
|
23900
24595
|
//#region src/features/commands/trae-command.ts
|
|
23901
24596
|
const TraeCommandFrontmatterSchema = z.looseObject({
|
|
@@ -24661,6 +25356,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24661
25356
|
supportsSubdirectory: false
|
|
24662
25357
|
}
|
|
24663
25358
|
}],
|
|
25359
|
+
["lettacode", {
|
|
25360
|
+
class: LettacodeCommand,
|
|
25361
|
+
meta: {
|
|
25362
|
+
extension: "md",
|
|
25363
|
+
supportsProject: true,
|
|
25364
|
+
supportsGlobal: true,
|
|
25365
|
+
isSimulated: false,
|
|
25366
|
+
supportsSubdirectory: true,
|
|
25367
|
+
committedOutput: true
|
|
25368
|
+
}
|
|
25369
|
+
}],
|
|
24664
25370
|
["mimocode", {
|
|
24665
25371
|
class: MimocodeCommand,
|
|
24666
25372
|
meta: {
|
|
@@ -24970,12 +25676,25 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
24970
25676
|
return rulesyncCommands;
|
|
24971
25677
|
}
|
|
24972
25678
|
/**
|
|
25679
|
+
* Whether {@link loadToolFiles} must not scan the tool's output tree at all.
|
|
25680
|
+
* `skipToolFileScan` targets never scan it; a `committedOutput` target is not
|
|
25681
|
+
* swept at project scope, because nothing on disk tells a hand-written
|
|
25682
|
+
* command in such a committed root apart from one rulesync generated.
|
|
25683
|
+
*/
|
|
25684
|
+
skipsToolFileScan({ factory, forDeletion }) {
|
|
25685
|
+
if (factory.meta.skipToolFileScan) return true;
|
|
25686
|
+
return forDeletion && factory.meta.committedOutput === true && !this.global;
|
|
25687
|
+
}
|
|
25688
|
+
/**
|
|
24973
25689
|
* Implementation of abstract method from FeatureProcessor
|
|
24974
25690
|
* Load tool-specific command configurations and parse them into ToolCommand instances
|
|
24975
25691
|
*/
|
|
24976
25692
|
async loadToolFiles({ forDeletion = false } = {}) {
|
|
24977
25693
|
const factory = this.getFactory(this.toolTarget);
|
|
24978
|
-
if (
|
|
25694
|
+
if (this.skipsToolFileScan({
|
|
25695
|
+
factory,
|
|
25696
|
+
forDeletion
|
|
25697
|
+
})) return [];
|
|
24979
25698
|
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
24980
25699
|
const outputRootFull = join(this.outputRoot, paths.relativeDirPath);
|
|
24981
25700
|
const commandFilePaths = await findFilesByGlobs(factory.meta.supportsSubdirectory ? `**/*.${factory.meta.extension}` : `*.${factory.meta.extension}`, {
|
|
@@ -26692,13 +27411,48 @@ function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, w
|
|
|
26692
27411
|
})
|
|
26693
27412
|
};
|
|
26694
27413
|
}
|
|
27414
|
+
/**
|
|
27415
|
+
* Why a definition must not be emitted for this tool, or `undefined` when it
|
|
27416
|
+
* can be: it carries a restricting field the tool refuses to honour (see
|
|
27417
|
+
* `skippedWhenFields`).
|
|
27418
|
+
*/
|
|
27419
|
+
function describeExportSkipReason({ def, hookType, eventName, converterConfig }) {
|
|
27420
|
+
const field = converterConfig.skippedWhenFields?.find((name) => isNonEmptyString(def[name]));
|
|
27421
|
+
if (field === void 0) return;
|
|
27422
|
+
return `Skipping a "${eventName}" ${hookType} hook (${quoteValueForWarning(def.command ?? def.url)}): this tool does not support its "${field}" condition (${quoteValueForWarning(def[field])}), and without it the hook would run on every call its matcher selects.`;
|
|
27423
|
+
}
|
|
27424
|
+
/**
|
|
27425
|
+
* Whether a definition can be emitted for this tool: its hook type is one the
|
|
27426
|
+
* tool supports, and it carries no restricting field the tool refuses to
|
|
27427
|
+
* honour (warned about here).
|
|
27428
|
+
*/
|
|
27429
|
+
function isEmittableDefinition({ def, hookType, eventName, converterConfig, warn }) {
|
|
27430
|
+
if (!isSupportedHookType({
|
|
27431
|
+
type: hookType,
|
|
27432
|
+
converterConfig
|
|
27433
|
+
})) return false;
|
|
27434
|
+
const skipReason = describeExportSkipReason({
|
|
27435
|
+
def,
|
|
27436
|
+
hookType,
|
|
27437
|
+
eventName,
|
|
27438
|
+
converterConfig
|
|
27439
|
+
});
|
|
27440
|
+
if (skipReason !== void 0) {
|
|
27441
|
+
warn?.(skipReason);
|
|
27442
|
+
return false;
|
|
27443
|
+
}
|
|
27444
|
+
return true;
|
|
27445
|
+
}
|
|
26695
27446
|
function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
26696
27447
|
const hooks = [];
|
|
26697
27448
|
for (const def of defs) {
|
|
26698
27449
|
const hookType = def.type ?? "command";
|
|
26699
|
-
if (!
|
|
26700
|
-
|
|
26701
|
-
|
|
27450
|
+
if (!isEmittableDefinition({
|
|
27451
|
+
def,
|
|
27452
|
+
hookType,
|
|
27453
|
+
eventName,
|
|
27454
|
+
converterConfig,
|
|
27455
|
+
warn
|
|
26702
27456
|
})) continue;
|
|
26703
27457
|
const command = applyCommandPrefix({
|
|
26704
27458
|
def,
|
|
@@ -27270,7 +28024,10 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
27270
28024
|
warn
|
|
27271
28025
|
}));
|
|
27272
28026
|
}
|
|
27273
|
-
if (defs.length > 0)
|
|
28027
|
+
if (defs.length > 0) {
|
|
28028
|
+
const existing = Object.hasOwn(canonical, eventName) ? canonical[eventName] : void 0;
|
|
28029
|
+
canonical[eventName] = existing ? [...existing, ...defs] : defs;
|
|
28030
|
+
}
|
|
27274
28031
|
}
|
|
27275
28032
|
return canonical;
|
|
27276
28033
|
}
|
|
@@ -31980,6 +32737,16 @@ const GROKCLI_CONVERTER_CONFIG = {
|
|
|
31980
32737
|
tool: "env",
|
|
31981
32738
|
commandOnly: true
|
|
31982
32739
|
}],
|
|
32740
|
+
arrayPassthroughFields: [{
|
|
32741
|
+
canonical: "args",
|
|
32742
|
+
tool: "args",
|
|
32743
|
+
commandOnly: true
|
|
32744
|
+
}],
|
|
32745
|
+
skippedWhenFields: ["if"],
|
|
32746
|
+
stringPassthroughFields: [{
|
|
32747
|
+
canonical: "if",
|
|
32748
|
+
tool: "if"
|
|
32749
|
+
}],
|
|
31983
32750
|
noMatcherEvents: /* @__PURE__ */ new Set(["stop", "beforeSubmitPrompt"])
|
|
31984
32751
|
};
|
|
31985
32752
|
/**
|
|
@@ -33476,15 +34243,6 @@ var KiroHooks = class KiroHooks extends ToolHooks {
|
|
|
33476
34243
|
}
|
|
33477
34244
|
};
|
|
33478
34245
|
//#endregion
|
|
33479
|
-
//#region src/constants/lettacode-paths.ts
|
|
33480
|
-
const LETTACODE_DIR = ".letta";
|
|
33481
|
-
const LETTACODE_SETTINGS_FILE_NAME = "settings.json";
|
|
33482
|
-
const LETTACODE_SETTINGS_LOCAL_FILE_NAME = "settings.local.json";
|
|
33483
|
-
const LETTACODE_IGNORE_FILE_NAME = ".lettaignore";
|
|
33484
|
-
const LETTACODE_AGENTS_DIR_PATH = join(LETTACODE_DIR, "agents");
|
|
33485
|
-
const LETTACODE_PROJECT_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
33486
|
-
const LETTACODE_GLOBAL_SKILLS_DIR_PATH = join(LETTACODE_DIR, "skills");
|
|
33487
|
-
//#endregion
|
|
33488
34246
|
//#region src/features/hooks/lettacode-hooks.ts
|
|
33489
34247
|
const LETTACODE_CONVERTER_CONFIG = {
|
|
33490
34248
|
supportedEvents: LETTACODE_HOOK_EVENTS,
|
|
@@ -43173,8 +43931,10 @@ var CortexcodeMcp = class CortexcodeMcp extends ToolMcp {
|
|
|
43173
43931
|
/**
|
|
43174
43932
|
* Crush-only MCP fields with no canonical counterpart, authored under a
|
|
43175
43933
|
* `crush`-prefixed key in `.rulesync/mcp.jsonc` and written under Crush's own
|
|
43176
|
-
* name: `oauth*` drive Crush's OAuth 2.1 flow for HTTP servers
|
|
43177
|
-
* `sessionless` marks a server that issues no `Mcp-Session-Id
|
|
43934
|
+
* name: `oauth*` drive Crush's OAuth 2.1 flow for HTTP servers,
|
|
43935
|
+
* `sessionless` marks a server that issues no `Mcp-Session-Id`; and
|
|
43936
|
+
* `channel_enabled` / `channel_reply` (v0.97.0) turn a server that implements
|
|
43937
|
+
* Claude Channels into a channel and route replies back through its tools.
|
|
43178
43938
|
* `RulesyncMcp.getMcpServers()` strips both spellings so the client secret
|
|
43179
43939
|
* never reaches another tool's config; `fromRulesyncMcp` re-merges them from
|
|
43180
43940
|
* the raw source JSON, honouring the raw Crush spelling as a fallback for an
|
|
@@ -43213,6 +43973,18 @@ const CRUSH_ONLY_KEYS = [
|
|
|
43213
43973
|
crush: "sessionless",
|
|
43214
43974
|
accepts: (v) => typeof v === "boolean",
|
|
43215
43975
|
rawFallback: true
|
|
43976
|
+
},
|
|
43977
|
+
{
|
|
43978
|
+
canonical: "crushChannelEnabled",
|
|
43979
|
+
crush: "channel_enabled",
|
|
43980
|
+
accepts: (v) => typeof v === "boolean",
|
|
43981
|
+
rawFallback: true
|
|
43982
|
+
},
|
|
43983
|
+
{
|
|
43984
|
+
canonical: "crushChannelReply",
|
|
43985
|
+
crush: "channel_reply",
|
|
43986
|
+
accepts: (v) => CrushChannelReplySchema.safeParse(v).success,
|
|
43987
|
+
rawFallback: true
|
|
43216
43988
|
}
|
|
43217
43989
|
];
|
|
43218
43990
|
/**
|
|
@@ -43220,12 +43992,16 @@ const CRUSH_ONLY_KEYS = [
|
|
|
43220
43992
|
* canonical name, so `convertToCrushFormat` sees them after the shared map
|
|
43221
43993
|
* stripped them.
|
|
43222
43994
|
*/
|
|
43223
|
-
function readCrushOnlyKeys(rawServer) {
|
|
43995
|
+
function readCrushOnlyKeys({ serverName, rawServer, logger }) {
|
|
43224
43996
|
const result = {};
|
|
43225
43997
|
if (!isRecord$1(rawServer)) return result;
|
|
43226
43998
|
for (const { canonical, crush, accepts, rawFallback } of CRUSH_ONLY_KEYS) {
|
|
43227
43999
|
const value = accepts(rawServer[canonical]) ? rawServer[canonical] : rawFallback && accepts(rawServer[crush]) ? rawServer[crush] : void 0;
|
|
43228
44000
|
if (value !== void 0) result[canonical] = value;
|
|
44001
|
+
else if (canonical === "crushChannelReply") {
|
|
44002
|
+
const rejected = [canonical, crush].find((key) => rawServer[key] !== void 0);
|
|
44003
|
+
if (rejected !== void 0) logger?.warn(`Crush MCP server "${serverName}": ignoring "${rejected}" because it does not match Crush's channel_reply shape ({ user?, group?: { tool, target_param?, target_meta? }, message_param?, suppress_tools? }).`);
|
|
44004
|
+
}
|
|
43229
44005
|
}
|
|
43230
44006
|
return result;
|
|
43231
44007
|
}
|
|
@@ -43350,8 +44126,8 @@ function convertToCrushFormat(mcpServers, logger) {
|
|
|
43350
44126
|
* servers. `stdio` is Crush's default `type`, so it is dropped (a canonical
|
|
43351
44127
|
* server with a `command` is stdio already); `http` and `sse` are kept as the
|
|
43352
44128
|
* canonical transport names; `disabled_tools`/`enabled_tools` become the
|
|
43353
|
-
* canonical `disabledTools`/`enabledTools`; the Crush-only OAuth
|
|
43354
|
-
* `sessionless` fields are lifted into their `crush*` authoring keys (a value
|
|
44129
|
+
* canonical `disabledTools`/`enabledTools`; the Crush-only OAuth,
|
|
44130
|
+
* `sessionless` and channel fields are lifted into their `crush*` authoring keys (a value
|
|
43355
44131
|
* of the wrong type is dropped, as Crush itself would refuse it), and any
|
|
43356
44132
|
* other key is kept verbatim so an import preserves what a hand-authored
|
|
43357
44133
|
* entry declared.
|
|
@@ -43442,7 +44218,11 @@ var CrushMcp = class CrushMcp extends ToolMcp {
|
|
|
43442
44218
|
});
|
|
43443
44219
|
const converted = convertToCrushFormat(Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([serverName, serverConfig]) => [serverName, {
|
|
43444
44220
|
...serverConfig,
|
|
43445
|
-
...readCrushOnlyKeys(
|
|
44221
|
+
...readCrushOnlyKeys({
|
|
44222
|
+
serverName,
|
|
44223
|
+
rawServer: rulesyncMcp.getRawMcpServer(serverName),
|
|
44224
|
+
logger
|
|
44225
|
+
})
|
|
43446
44226
|
}])), logger);
|
|
43447
44227
|
return new CrushMcp({
|
|
43448
44228
|
outputRoot,
|
|
@@ -44035,12 +44815,19 @@ var DevinPluginMcp = class DevinPluginMcp extends DevinMcp {
|
|
|
44035
44815
|
* only the default location. Project-scoped assets live under a `.dsh/`
|
|
44036
44816
|
* directory at the project root.
|
|
44037
44817
|
*
|
|
44038
|
-
* Rules: `dsh-agent-instructions` loads the user-global
|
|
44039
|
-
*
|
|
44040
|
-
*
|
|
44041
|
-
*
|
|
44042
|
-
*
|
|
44043
|
-
*
|
|
44818
|
+
* Rules: `dsh-agent-instructions` loads the user-global chain —
|
|
44819
|
+
* `$DSH_HOME/AGENTS.md`, then `<agentsHome>/AGENTS.md` (`$DSH_AGENTS_HOME`,
|
|
44820
|
+
* default `~/.agents`, since dsh-v0.2.1-alpha.2) — then the project chain of
|
|
44821
|
+
* `AGENTS.md` / `CLAUDE.md` files plus their `AGENTS.local.md` /
|
|
44822
|
+
* `CLAUDE.local.md` overlays from the project root (the nearest ancestor
|
|
44823
|
+
* holding `.git`) down to the session cwd, broad to specific. Neither
|
|
44824
|
+
* user-global root has a `.local.md` overlay. The two user-global roots share
|
|
44825
|
+
* one dedupe group, so the shared-root file collapses behind the harness-home
|
|
44826
|
+
* file only when both match after trimming; otherwise both load. Rulesync
|
|
44827
|
+
* writes only `$DSH_HOME/AGENTS.md` globally (never the shared
|
|
44828
|
+
* `~/.agents/AGENTS.md`), and `AGENTS.md` / `AGENTS.local.md` in the project;
|
|
44829
|
+
* `CLAUDE.md` is the `claudecode` target's file, and a `CLAUDE.md` duplicating
|
|
44830
|
+
* its sibling `AGENTS.md` renders once.
|
|
44044
44831
|
*
|
|
44045
44832
|
* Skills: `dsh-skill-filesystem` scans `<projectRoot>/.dsh/skills` (rank 100),
|
|
44046
44833
|
* `<projectRoot>/.agents/skills` (200), `<dshHome>/skills` (400) and
|
|
@@ -46384,6 +47171,20 @@ function normalizeKimiCodeTransport({ transport, hasCommand }) {
|
|
|
46384
47171
|
if (transport === "http" || transport === "streamable-http") return "http";
|
|
46385
47172
|
return hasCommand ? "stdio" : "http";
|
|
46386
47173
|
}
|
|
47174
|
+
/**
|
|
47175
|
+
* The only `executor` value Kimi Code accepts on a stdio server. 2.2.0 removed
|
|
47176
|
+
* the never-implemented `kaos` option and narrowed the schema to
|
|
47177
|
+
* `z.enum(['local'])`, so any other value makes Kimi fail validation when it
|
|
47178
|
+
* loads `mcp.json`.
|
|
47179
|
+
* @see https://github.com/MoonshotAI/kimi-code/pull/4184
|
|
47180
|
+
*/
|
|
47181
|
+
const KIMI_CODE_MCP_EXECUTOR = "local";
|
|
47182
|
+
/** Remove, with a warning, an `executor` value Kimi Code would reject. Mutates `server`. */
|
|
47183
|
+
function dropUnsupportedKimiCodeExecutor({ name, server, logger }) {
|
|
47184
|
+
if (server.executor === void 0 || server.executor === KIMI_CODE_MCP_EXECUTOR) return;
|
|
47185
|
+
warnWithFallback(logger, `Kimi Code MCP: dropping executor ${JSON.stringify(server.executor)} from "${name}" because Kimi Code only accepts "${KIMI_CODE_MCP_EXECUTOR}".`);
|
|
47186
|
+
delete server.executor;
|
|
47187
|
+
}
|
|
46387
47188
|
function toKimiCodeServer({ name, server, logger }) {
|
|
46388
47189
|
const transport = server.transport ?? server.type;
|
|
46389
47190
|
if (transport === "ws") {
|
|
@@ -46422,6 +47223,11 @@ function toKimiCodeServer({ name, server, logger }) {
|
|
|
46422
47223
|
"enabledTools",
|
|
46423
47224
|
"disabledTools"
|
|
46424
47225
|
]) if (server[field] !== void 0) converted[field] = server[field];
|
|
47226
|
+
dropUnsupportedKimiCodeExecutor({
|
|
47227
|
+
name,
|
|
47228
|
+
server: converted,
|
|
47229
|
+
logger
|
|
47230
|
+
});
|
|
46425
47231
|
if (server.disabled === true) converted.enabled = false;
|
|
46426
47232
|
return converted;
|
|
46427
47233
|
}
|
|
@@ -46437,9 +47243,14 @@ function toKimiCodeServers({ servers, logger }) {
|
|
|
46437
47243
|
}
|
|
46438
47244
|
return result;
|
|
46439
47245
|
}
|
|
46440
|
-
function fromKimiCodeServers(servers) {
|
|
47246
|
+
function fromKimiCodeServers({ servers, logger }) {
|
|
46441
47247
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
46442
47248
|
const { transport, enabled, ...rest } = server;
|
|
47249
|
+
dropUnsupportedKimiCodeExecutor({
|
|
47250
|
+
name,
|
|
47251
|
+
server: rest,
|
|
47252
|
+
logger
|
|
47253
|
+
});
|
|
46443
47254
|
const type = transport === "stdio" || transport === "sse" || transport === "http" ? transport : void 0;
|
|
46444
47255
|
return [name, {
|
|
46445
47256
|
...rest,
|
|
@@ -46554,9 +47365,11 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
46554
47365
|
* a different file that only the async `fromFile` can read.
|
|
46555
47366
|
*/
|
|
46556
47367
|
configDefaults;
|
|
46557
|
-
|
|
47368
|
+
logger;
|
|
47369
|
+
constructor({ configDefaults, logger, ...params }) {
|
|
46558
47370
|
super(params);
|
|
46559
|
-
this.configDefaults =
|
|
47371
|
+
this.configDefaults = configDefaults ?? {};
|
|
47372
|
+
this.logger = logger;
|
|
46560
47373
|
try {
|
|
46561
47374
|
this.json = this.fileContent ? JSON.parse(this.fileContent) : {};
|
|
46562
47375
|
} catch (error) {
|
|
@@ -46572,7 +47385,7 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
46572
47385
|
relativeFilePath: KIMI_CODE_MCP_FILE_NAME
|
|
46573
47386
|
};
|
|
46574
47387
|
}
|
|
46575
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
47388
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
|
|
46576
47389
|
const paths = this.getSettablePaths({ global });
|
|
46577
47390
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
|
|
46578
47391
|
return new KimiCodeMcp({
|
|
@@ -46582,7 +47395,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
46582
47395
|
fileContent,
|
|
46583
47396
|
validate,
|
|
46584
47397
|
global,
|
|
46585
|
-
configDefaults: global ? await readKimiCodeMcpDefaults({ outputRoot }) : {}
|
|
47398
|
+
configDefaults: global ? await readKimiCodeMcpDefaults({ outputRoot }) : {},
|
|
47399
|
+
logger
|
|
46586
47400
|
});
|
|
46587
47401
|
}
|
|
46588
47402
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
@@ -46656,7 +47470,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
46656
47470
|
relativeFilePath: RULESYNC_MCP_FILE_NAME,
|
|
46657
47471
|
fileContent: JSON.stringify({
|
|
46658
47472
|
...this.json,
|
|
46659
|
-
mcpServers: fromKimiCodeServers(
|
|
47473
|
+
mcpServers: fromKimiCodeServers({
|
|
47474
|
+
servers: isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {},
|
|
47475
|
+
logger: this.logger
|
|
47476
|
+
}),
|
|
46660
47477
|
...Object.keys(this.configDefaults).length > 0 && { "kimi-code": this.configDefaults }
|
|
46661
47478
|
}, null, 2)
|
|
46662
47479
|
});
|
|
@@ -47097,7 +47914,13 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
47097
47914
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
47098
47915
|
if (fileContent) relativeFilePath = this.layout.jsonFileName;
|
|
47099
47916
|
}
|
|
47100
|
-
const
|
|
47917
|
+
const fileContentToUse = fileContent ?? "{\"mcp\":{}}";
|
|
47918
|
+
const overlays = await readOpencodeDotDirConfigLayers({
|
|
47919
|
+
outputRoot,
|
|
47920
|
+
global,
|
|
47921
|
+
layout: this.layout
|
|
47922
|
+
});
|
|
47923
|
+
const json = overlays.length === 0 ? parse$1(fileContentToUse) : [parse$1(fileContentToUse), ...overlays.map(({ config }) => config)].map((layer) => this.lowerConfig(layer, logger)).reduce(mergeOpencodeConfigValues);
|
|
47101
47924
|
const newJson = {
|
|
47102
47925
|
...json,
|
|
47103
47926
|
mcp: json.mcp ?? {}
|
|
@@ -47134,6 +47957,27 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
47134
47957
|
if (containers.length > 0) logger?.warn(`OpenCode MCP: the whole "mcp" key is rewritten in the flat V1 shape, so ${containers.map(quoteValueForWarning).join(" and ")} ${containers.length === 1 ? "is" : "are"} not kept.`);
|
|
47135
47958
|
}
|
|
47136
47959
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
47960
|
+
const generatedMcp = undefinedWhenEmpty(convertedMcp);
|
|
47961
|
+
const generatedTools = Object.keys(mcpTools).length > 0 ? mcpTools : void 0;
|
|
47962
|
+
warnOpencodeDotDirOverrides({
|
|
47963
|
+
generated: {
|
|
47964
|
+
mcp: generatedMcp,
|
|
47965
|
+
tools: generatedTools
|
|
47966
|
+
},
|
|
47967
|
+
overlays: (await readOpencodeDotDirConfigLayers({
|
|
47968
|
+
outputRoot,
|
|
47969
|
+
global,
|
|
47970
|
+
layout: this.layout
|
|
47971
|
+
})).map((overlay) => {
|
|
47972
|
+
const lowered = this.lowerConfig(overlay.config);
|
|
47973
|
+
return {
|
|
47974
|
+
...overlay,
|
|
47975
|
+
config: isRecord$1(lowered) ? lowered : {}
|
|
47976
|
+
};
|
|
47977
|
+
}),
|
|
47978
|
+
rootFileName: relativeFilePath,
|
|
47979
|
+
logger
|
|
47980
|
+
});
|
|
47137
47981
|
return new this({
|
|
47138
47982
|
outputRoot,
|
|
47139
47983
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -47143,8 +47987,8 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
47143
47987
|
feature: "mcp",
|
|
47144
47988
|
existingContent: fileContent ?? "",
|
|
47145
47989
|
patch: {
|
|
47146
|
-
mcp:
|
|
47147
|
-
tools:
|
|
47990
|
+
mcp: generatedMcp,
|
|
47991
|
+
tools: generatedTools
|
|
47148
47992
|
},
|
|
47149
47993
|
filePath: join(jsonDir, relativeFilePath),
|
|
47150
47994
|
logger
|
|
@@ -47790,6 +48634,358 @@ var OmpMcp = class OmpMcp extends ToolMcp {
|
|
|
47790
48634
|
}
|
|
47791
48635
|
};
|
|
47792
48636
|
//#endregion
|
|
48637
|
+
//#region src/constants/openclaw-paths.ts
|
|
48638
|
+
const OPENCLAW_WORKSPACE_DIR_PATH = join(".openclaw", "workspace");
|
|
48639
|
+
const OPENCLAW_RULE_FILE_NAME = "AGENTS.md";
|
|
48640
|
+
const OPENCLAW_CONFIG_DIR_PATH = ".openclaw";
|
|
48641
|
+
const OPENCLAW_CONFIG_FILE_NAME = "openclaw.json";
|
|
48642
|
+
const OPENCLAW_MCP_SERVERS_KEY = "servers";
|
|
48643
|
+
//#endregion
|
|
48644
|
+
//#region src/features/mcp/openclaw-mcp.ts
|
|
48645
|
+
const OPENCLAW_GLOBAL_ONLY_MESSAGE = "OpenClaw MCP is global-only; use --global to sync ~/.openclaw/openclaw.json";
|
|
48646
|
+
/**
|
|
48647
|
+
* Single spelling of the openclaw.json codec/policy, matching its
|
|
48648
|
+
* `SHARED_CONFIG_OWNERSHIP` declaration: the file is JSON5, and a file whose
|
|
48649
|
+
* root is not an object is refused rather than replaced, since it also holds
|
|
48650
|
+
* every other Gateway setting.
|
|
48651
|
+
*/
|
|
48652
|
+
function parseOpenclawConfig(fileContent, filePath) {
|
|
48653
|
+
return parseSharedConfig({
|
|
48654
|
+
format: "json5",
|
|
48655
|
+
fileContent,
|
|
48656
|
+
filePath,
|
|
48657
|
+
invalidRootPolicy: "error"
|
|
48658
|
+
});
|
|
48659
|
+
}
|
|
48660
|
+
const OPENCLAW_OAUTH_STRING_KEYS = [
|
|
48661
|
+
"authProfileId",
|
|
48662
|
+
"scope",
|
|
48663
|
+
"redirectUrl",
|
|
48664
|
+
"clientMetadataUrl"
|
|
48665
|
+
];
|
|
48666
|
+
const OPENCLAW_OAUTH_IDENTITIES = /* @__PURE__ */ new Set(["shared", "per-requester"]);
|
|
48667
|
+
/**
|
|
48668
|
+
* Copies the `oauth` keys OpenClaw accepts. A known key with a value its
|
|
48669
|
+
* schema rejects (an `identity` other than `shared` / `per-requester`, a blank
|
|
48670
|
+
* string) is dropped with a warning, since the Gateway would refuse to start
|
|
48671
|
+
* on it; keys OpenClaw does not know are left behind silently, as they belong
|
|
48672
|
+
* to other tools' `oauth` blocks.
|
|
48673
|
+
*/
|
|
48674
|
+
function copyOpenclawOauth({ source, serverName, logger }) {
|
|
48675
|
+
const oauth = {};
|
|
48676
|
+
const dropped = [];
|
|
48677
|
+
if (source.identity !== void 0) {
|
|
48678
|
+
if (typeof source.identity === "string" && OPENCLAW_OAUTH_IDENTITIES.has(source.identity)) oauth.identity = source.identity;
|
|
48679
|
+
else dropped.push("identity");
|
|
48680
|
+
}
|
|
48681
|
+
for (const key of OPENCLAW_OAUTH_STRING_KEYS) {
|
|
48682
|
+
const value = source[key];
|
|
48683
|
+
if (value === void 0) continue;
|
|
48684
|
+
if (typeof value === "string" && value.trim() !== "") oauth[key] = value;
|
|
48685
|
+
else dropped.push(key);
|
|
48686
|
+
}
|
|
48687
|
+
if (dropped.length > 0 && serverName !== void 0) logger?.warn(`OpenClaw MCP: dropping invalid oauth ${dropped.map((key) => `"${key}"`).join(", ")} of ${quoteValueForWarning(serverName)}; OpenClaw's config validation would reject it.`);
|
|
48688
|
+
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
48689
|
+
}
|
|
48690
|
+
const OPENCLAW_CODEX_APPROVAL_MODES = /* @__PURE__ */ new Set([
|
|
48691
|
+
"auto",
|
|
48692
|
+
"prompt",
|
|
48693
|
+
"approve"
|
|
48694
|
+
]);
|
|
48695
|
+
/**
|
|
48696
|
+
* Copies the OpenClaw-only per-server fields that have no canonical
|
|
48697
|
+
* counterpart — `auth`, `oauth`, the TLS controls, the timeouts in
|
|
48698
|
+
* milliseconds, `supportsParallelToolCalls` and the `codex` projection — from
|
|
48699
|
+
* `source` to `target`, in both directions. OpenClaw validates its config
|
|
48700
|
+
* strictly (`oauth`, `toolFilter` and `codex` are strict objects) and refuses
|
|
48701
|
+
* to start the Gateway on a value it rejects, so only the documented keys with
|
|
48702
|
+
* the documented value types are copied; a same-named key another tool reads
|
|
48703
|
+
* differently (Hermes' snake_case `oauth` keys, say) is left behind.
|
|
48704
|
+
*
|
|
48705
|
+
* @see https://github.com/openclaw/openclaw/blob/v2026.10.4/src/config/zod-schema.mcp-server.ts
|
|
48706
|
+
*/
|
|
48707
|
+
function copyOpenclawOnlyFields(source, target, { serverName, logger } = {}) {
|
|
48708
|
+
let copied = false;
|
|
48709
|
+
if (source.auth === "oauth") {
|
|
48710
|
+
target.auth = "oauth";
|
|
48711
|
+
copied = true;
|
|
48712
|
+
}
|
|
48713
|
+
if (isRecord$1(source.oauth)) {
|
|
48714
|
+
const oauth = copyOpenclawOauth({
|
|
48715
|
+
source: source.oauth,
|
|
48716
|
+
serverName,
|
|
48717
|
+
logger
|
|
48718
|
+
});
|
|
48719
|
+
if (oauth) {
|
|
48720
|
+
target.oauth = oauth;
|
|
48721
|
+
copied = true;
|
|
48722
|
+
}
|
|
48723
|
+
}
|
|
48724
|
+
for (const key of ["clientCert", "clientKey"]) if (typeof source[key] === "string") {
|
|
48725
|
+
target[key] = source[key];
|
|
48726
|
+
copied = true;
|
|
48727
|
+
}
|
|
48728
|
+
for (const key of ["sslVerify", "supportsParallelToolCalls"]) if (typeof source[key] === "boolean") {
|
|
48729
|
+
target[key] = source[key];
|
|
48730
|
+
copied = true;
|
|
48731
|
+
}
|
|
48732
|
+
for (const key of ["requestTimeoutMs", "connectionTimeoutMs"]) {
|
|
48733
|
+
const value = source[key];
|
|
48734
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
48735
|
+
target[key] = value;
|
|
48736
|
+
copied = true;
|
|
48737
|
+
}
|
|
48738
|
+
}
|
|
48739
|
+
if (isRecord$1(source.codex)) {
|
|
48740
|
+
const codex = {};
|
|
48741
|
+
if (isStringArray$1(source.codex.agents) && source.codex.agents.length > 0) codex.agents = [...source.codex.agents];
|
|
48742
|
+
const mode = source.codex.defaultToolsApprovalMode;
|
|
48743
|
+
if (typeof mode === "string" && OPENCLAW_CODEX_APPROVAL_MODES.has(mode)) codex.defaultToolsApprovalMode = mode;
|
|
48744
|
+
if (Object.keys(codex).length > 0) {
|
|
48745
|
+
target.codex = codex;
|
|
48746
|
+
copied = true;
|
|
48747
|
+
}
|
|
48748
|
+
}
|
|
48749
|
+
return copied;
|
|
48750
|
+
}
|
|
48751
|
+
/** A string map without pollution keys, or `undefined` when nothing is left. */
|
|
48752
|
+
function copyStringMap(value) {
|
|
48753
|
+
if (!isPlainObject$1(value)) return void 0;
|
|
48754
|
+
const result = {};
|
|
48755
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
48756
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
48757
|
+
if (typeof entry === "string") result[key] = entry;
|
|
48758
|
+
else if (typeof entry === "number" || typeof entry === "boolean") result[key] = String(entry);
|
|
48759
|
+
}
|
|
48760
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
48761
|
+
}
|
|
48762
|
+
/** A non-empty `toolFilter` list, trimmed of blank entries OpenClaw rejects. */
|
|
48763
|
+
function toolFilterList(value) {
|
|
48764
|
+
if (!isStringArray$1(value)) return void 0;
|
|
48765
|
+
const list = value.map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
48766
|
+
return list.length > 0 ? list : void 0;
|
|
48767
|
+
}
|
|
48768
|
+
function convertServerToOpenclaw({ name, config, logger }) {
|
|
48769
|
+
if (declaresNoTransport(config)) return warnAndSkipMcpServer({
|
|
48770
|
+
toolName: "OpenClaw",
|
|
48771
|
+
serverName: name,
|
|
48772
|
+
reason: "no transport",
|
|
48773
|
+
logger
|
|
48774
|
+
});
|
|
48775
|
+
const out = {};
|
|
48776
|
+
if (isRemoteMcpServer(config)) {
|
|
48777
|
+
const url = resolveRemoteMcpUrl(config);
|
|
48778
|
+
if (!url) return warnAndSkipMcpServer({
|
|
48779
|
+
toolName: "OpenClaw",
|
|
48780
|
+
serverName: name,
|
|
48781
|
+
reason: "a remote transport without a url",
|
|
48782
|
+
logger
|
|
48783
|
+
});
|
|
48784
|
+
const stated = config.type ?? config.transport;
|
|
48785
|
+
if (stated === "ws" || stated === void 0 && /^wss?:\/\//i.test(url)) return warnAndSkipMcpServer({
|
|
48786
|
+
toolName: "OpenClaw",
|
|
48787
|
+
serverName: name,
|
|
48788
|
+
reason: "a WebSocket transport, which OpenClaw does not offer (only streamable-http and sse)",
|
|
48789
|
+
logger
|
|
48790
|
+
});
|
|
48791
|
+
out.url = url;
|
|
48792
|
+
out.transport = stated === "sse" ? "sse" : "streamable-http";
|
|
48793
|
+
const headers = copyStringMap(config.headers);
|
|
48794
|
+
if (headers) out.headers = headers;
|
|
48795
|
+
} else {
|
|
48796
|
+
const [command, ...args] = resolveLocalMcpCommand(config);
|
|
48797
|
+
if (!command) return warnAndSkipMcpServer({
|
|
48798
|
+
toolName: "OpenClaw",
|
|
48799
|
+
serverName: name,
|
|
48800
|
+
reason: "a stdio transport without a command",
|
|
48801
|
+
logger
|
|
48802
|
+
});
|
|
48803
|
+
out.command = command;
|
|
48804
|
+
if (args.length > 0) out.args = args;
|
|
48805
|
+
const env = copyStringMap(config.env);
|
|
48806
|
+
if (env) out.env = env;
|
|
48807
|
+
if (typeof config.cwd === "string") out.cwd = config.cwd;
|
|
48808
|
+
}
|
|
48809
|
+
if (config.disabled === true) out.enabled = false;
|
|
48810
|
+
const include = toolFilterList(config.enabledTools);
|
|
48811
|
+
const exclude = toolFilterList(config.disabledTools);
|
|
48812
|
+
if (include || exclude) out.toolFilter = {
|
|
48813
|
+
...include && { include },
|
|
48814
|
+
...exclude && { exclude }
|
|
48815
|
+
};
|
|
48816
|
+
copyOpenclawOnlyFields(config, out, {
|
|
48817
|
+
serverName: name,
|
|
48818
|
+
logger
|
|
48819
|
+
});
|
|
48820
|
+
return out;
|
|
48821
|
+
}
|
|
48822
|
+
/**
|
|
48823
|
+
* Converts canonical rulesync servers into OpenClaw `mcp.servers` entries.
|
|
48824
|
+
* Only keys OpenClaw accepts are written — never canonical-only ones such as
|
|
48825
|
+
* `type` or `disabled`, which its strict config validation would answer by
|
|
48826
|
+
* refusing to start the Gateway.
|
|
48827
|
+
*/
|
|
48828
|
+
function convertToOpenclawFormat({ mcpServers, logger }) {
|
|
48829
|
+
const result = {};
|
|
48830
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
48831
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
48832
|
+
const converted = convertServerToOpenclaw({
|
|
48833
|
+
name,
|
|
48834
|
+
config,
|
|
48835
|
+
logger
|
|
48836
|
+
});
|
|
48837
|
+
if (converted) result[name] = converted;
|
|
48838
|
+
}
|
|
48839
|
+
return result;
|
|
48840
|
+
}
|
|
48841
|
+
/**
|
|
48842
|
+
* Converts OpenClaw `mcp.servers` entries back into canonical rulesync
|
|
48843
|
+
* servers. OpenClaw-only fields go into the `openclaw.mcpServers` override
|
|
48844
|
+
* block (the whole server, since an override entry replaces the shared one for
|
|
48845
|
+
* that tool), so they survive an import/generate round trip without leaking
|
|
48846
|
+
* into every other tool's config.
|
|
48847
|
+
*/
|
|
48848
|
+
function convertFromOpenclawFormat(servers) {
|
|
48849
|
+
const mcpServers = {};
|
|
48850
|
+
const openclawOverrides = {};
|
|
48851
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
48852
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
48853
|
+
const server = {};
|
|
48854
|
+
if (typeof config.command === "string") {
|
|
48855
|
+
server.command = config.command;
|
|
48856
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
48857
|
+
const env = copyStringMap(config.env);
|
|
48858
|
+
if (env) server.env = env;
|
|
48859
|
+
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
48860
|
+
} else if (typeof config.url === "string") {
|
|
48861
|
+
server.type = config.transport === "sse" ? "sse" : "http";
|
|
48862
|
+
server.url = config.url;
|
|
48863
|
+
const headers = copyStringMap(config.headers);
|
|
48864
|
+
if (headers) server.headers = headers;
|
|
48865
|
+
}
|
|
48866
|
+
if (config.enabled === false) server.disabled = true;
|
|
48867
|
+
if (isRecord$1(config.toolFilter)) {
|
|
48868
|
+
const include = toolFilterList(config.toolFilter.include);
|
|
48869
|
+
const exclude = toolFilterList(config.toolFilter.exclude);
|
|
48870
|
+
if (include) server.enabledTools = include;
|
|
48871
|
+
if (exclude) server.disabledTools = exclude;
|
|
48872
|
+
}
|
|
48873
|
+
mcpServers[name] = server;
|
|
48874
|
+
const openclawServer = { ...server };
|
|
48875
|
+
if (copyOpenclawOnlyFields(config, openclawServer)) openclawOverrides[name] = openclawServer;
|
|
48876
|
+
}
|
|
48877
|
+
return {
|
|
48878
|
+
mcpServers,
|
|
48879
|
+
openclawOverrides
|
|
48880
|
+
};
|
|
48881
|
+
}
|
|
48882
|
+
/**
|
|
48883
|
+
* OpenClaw MCP servers.
|
|
48884
|
+
*
|
|
48885
|
+
* OpenClaw keeps its MCP registry under `mcp.servers` of the Gateway config
|
|
48886
|
+
* `~/.openclaw/openclaw.json` (JSON5; global only — there is no project-scoped
|
|
48887
|
+
* MCP location). That file also holds credentials and every other Gateway
|
|
48888
|
+
* setting, so generation merges rulesync's servers into `mcp.servers` by name,
|
|
48889
|
+
* keeps servers rulesync does not define and every other key, and never deletes
|
|
48890
|
+
* the file.
|
|
48891
|
+
*
|
|
48892
|
+
* @see https://docs.openclaw.ai/gateway/config-extensions
|
|
48893
|
+
* @see https://docs.openclaw.ai/tools/mcp
|
|
48894
|
+
*/
|
|
48895
|
+
var OpenclawMcp = class OpenclawMcp extends ToolMcp {
|
|
48896
|
+
config;
|
|
48897
|
+
constructor(params) {
|
|
48898
|
+
super(params);
|
|
48899
|
+
this.config = parseOpenclawConfig(this.fileContent ?? "", join(this.relativeDirPath, this.relativeFilePath));
|
|
48900
|
+
}
|
|
48901
|
+
getConfig() {
|
|
48902
|
+
return this.config;
|
|
48903
|
+
}
|
|
48904
|
+
isDeletable() {
|
|
48905
|
+
return false;
|
|
48906
|
+
}
|
|
48907
|
+
static getSettablePaths(_options = {}) {
|
|
48908
|
+
return {
|
|
48909
|
+
relativeDirPath: OPENCLAW_CONFIG_DIR_PATH,
|
|
48910
|
+
relativeFilePath: OPENCLAW_CONFIG_FILE_NAME
|
|
48911
|
+
};
|
|
48912
|
+
}
|
|
48913
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
48914
|
+
if (!global) throw new Error(OPENCLAW_GLOBAL_ONLY_MESSAGE);
|
|
48915
|
+
const paths = this.getSettablePaths({ global });
|
|
48916
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
48917
|
+
return new OpenclawMcp({
|
|
48918
|
+
outputRoot,
|
|
48919
|
+
relativeDirPath: paths.relativeDirPath,
|
|
48920
|
+
relativeFilePath: paths.relativeFilePath,
|
|
48921
|
+
fileContent,
|
|
48922
|
+
validate,
|
|
48923
|
+
global
|
|
48924
|
+
});
|
|
48925
|
+
}
|
|
48926
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
48927
|
+
if (!global) throw new Error(OPENCLAW_GLOBAL_ONLY_MESSAGE);
|
|
48928
|
+
const paths = this.getSettablePaths({ global });
|
|
48929
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
48930
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
48931
|
+
const existing = parseOpenclawConfig(existingContent, filePath);
|
|
48932
|
+
const existingMcp = isRecord$1(existing["mcp"]) ? existing["mcp"] : {};
|
|
48933
|
+
const existingServers = isRecord$1(existingMcp["servers"]) ? existingMcp[OPENCLAW_MCP_SERVERS_KEY] : {};
|
|
48934
|
+
const converted = convertToOpenclawFormat({
|
|
48935
|
+
mcpServers: rulesyncMcp.getMcpServers(),
|
|
48936
|
+
logger
|
|
48937
|
+
});
|
|
48938
|
+
const servers = {
|
|
48939
|
+
...existingServers,
|
|
48940
|
+
...converted
|
|
48941
|
+
};
|
|
48942
|
+
const kept = Object.keys(existingServers).filter((name) => !PROTOTYPE_POLLUTION_KEYS.has(name) && !Object.hasOwn(converted, name));
|
|
48943
|
+
if (kept.length > 0) logger?.warn(`OpenClaw MCP servers not defined in rulesync are kept in ${join(paths.relativeDirPath, paths.relativeFilePath)}: ${kept.map((name) => quoteValueForWarning(name)).join(", ")}. Remove them with 'openclaw mcp unset <name>' if they are no longer wanted.`);
|
|
48944
|
+
return new OpenclawMcp({
|
|
48945
|
+
outputRoot,
|
|
48946
|
+
relativeDirPath: paths.relativeDirPath,
|
|
48947
|
+
relativeFilePath: paths.relativeFilePath,
|
|
48948
|
+
fileContent: applySharedConfigPatch({
|
|
48949
|
+
fileKey: sharedConfigFileKey(paths),
|
|
48950
|
+
feature: "mcp",
|
|
48951
|
+
existingContent,
|
|
48952
|
+
patch: { ["mcp"]: {
|
|
48953
|
+
...existingMcp,
|
|
48954
|
+
[OPENCLAW_MCP_SERVERS_KEY]: servers
|
|
48955
|
+
} },
|
|
48956
|
+
filePath,
|
|
48957
|
+
logger
|
|
48958
|
+
}),
|
|
48959
|
+
validate,
|
|
48960
|
+
global
|
|
48961
|
+
});
|
|
48962
|
+
}
|
|
48963
|
+
toRulesyncMcp() {
|
|
48964
|
+
const mcp = isRecord$1(this.config["mcp"]) ? this.config["mcp"] : {};
|
|
48965
|
+
const { mcpServers, openclawOverrides } = convertFromOpenclawFormat(isRecord$1(mcp["servers"]) ? mcp[OPENCLAW_MCP_SERVERS_KEY] : {});
|
|
48966
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
48967
|
+
mcpServers,
|
|
48968
|
+
...Object.keys(openclawOverrides).length > 0 && { openclaw: { mcpServers: openclawOverrides } }
|
|
48969
|
+
}, null, 2) });
|
|
48970
|
+
}
|
|
48971
|
+
validate() {
|
|
48972
|
+
return {
|
|
48973
|
+
success: true,
|
|
48974
|
+
error: null
|
|
48975
|
+
};
|
|
48976
|
+
}
|
|
48977
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
48978
|
+
return new OpenclawMcp({
|
|
48979
|
+
outputRoot,
|
|
48980
|
+
relativeDirPath,
|
|
48981
|
+
relativeFilePath,
|
|
48982
|
+
fileContent: "",
|
|
48983
|
+
validate: false,
|
|
48984
|
+
global
|
|
48985
|
+
});
|
|
48986
|
+
}
|
|
48987
|
+
};
|
|
48988
|
+
//#endregion
|
|
47793
48989
|
//#region src/features/mcp/pi-mcp.ts
|
|
47794
48990
|
/**
|
|
47795
48991
|
* Canonical keys consumed by the conversion below (or with no Pi counterpart),
|
|
@@ -50844,6 +52040,128 @@ function asZcodeRemoteType(stated, url) {
|
|
|
50844
52040
|
if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
|
|
50845
52041
|
}
|
|
50846
52042
|
/**
|
|
52043
|
+
* The keys each OAuth flow of ZCode's per-server `oauth` union accepts on a
|
|
52044
|
+
* remote server, and which of them are required. Read off the 3.14.3/3.15.3
|
|
52045
|
+
* runtime config schema, since ZCode's MCP page only says to "enable OAuth for
|
|
52046
|
+
* the service in its configuration". Both variants are strict, so any other
|
|
52047
|
+
* key would make ZCode skip the server.
|
|
52048
|
+
*/
|
|
52049
|
+
const ZCODE_OAUTH_FLOWS = {
|
|
52050
|
+
client_credentials: {
|
|
52051
|
+
required: ["clientId", "clientSecret"],
|
|
52052
|
+
optional: ["clientName", "scope"]
|
|
52053
|
+
},
|
|
52054
|
+
authorization_code: {
|
|
52055
|
+
required: [],
|
|
52056
|
+
optional: [
|
|
52057
|
+
"clientId",
|
|
52058
|
+
"clientSecret",
|
|
52059
|
+
"clientName",
|
|
52060
|
+
"redirectPath",
|
|
52061
|
+
"scope"
|
|
52062
|
+
]
|
|
52063
|
+
}
|
|
52064
|
+
};
|
|
52065
|
+
/**
|
|
52066
|
+
* The `protocolVersion` values ZCode's server schema accepts (an enum on a
|
|
52067
|
+
* strict object, so any other value makes ZCode skip the server).
|
|
52068
|
+
*/
|
|
52069
|
+
const ZCODE_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([
|
|
52070
|
+
"auto",
|
|
52071
|
+
"legacy",
|
|
52072
|
+
"2026-07-28"
|
|
52073
|
+
]);
|
|
52074
|
+
/**
|
|
52075
|
+
* The namespaced canonical key that carries ZCode's `oauth`. The canonical
|
|
52076
|
+
* `oauth` already holds Claude Code's `{ clientId }` object, and ZCode's shape
|
|
52077
|
+
* may carry a client secret, so it is kept apart (the `crushOauth*`
|
|
52078
|
+
* precedent) and stripped by `RulesyncMcp.getMcpServers()`.
|
|
52079
|
+
*/
|
|
52080
|
+
const ZCODE_OAUTH_CANONICAL_KEY = "zcodeOauth";
|
|
52081
|
+
/**
|
|
52082
|
+
* Whether an `oauth` value names one of ZCode's flows.
|
|
52083
|
+
*/
|
|
52084
|
+
function isZcodeOauth(value) {
|
|
52085
|
+
return isRecord$1(value) && typeof value.type === "string" && value.type in ZCODE_OAUTH_FLOWS;
|
|
52086
|
+
}
|
|
52087
|
+
/**
|
|
52088
|
+
* Build ZCode's `oauth` from a `zcodeOauth` value: only the keys of the named
|
|
52089
|
+
* flow are kept, as strings. A value naming no ZCode flow, or missing a key
|
|
52090
|
+
* the flow requires, is dropped with a warning instead of making ZCode skip
|
|
52091
|
+
* the server.
|
|
52092
|
+
*/
|
|
52093
|
+
function toZcodeOauth({ serverName, value, logger }) {
|
|
52094
|
+
if (value === void 0) return void 0;
|
|
52095
|
+
if (!isZcodeOauth(value)) {
|
|
52096
|
+
logger?.warn(`ZCode MCP server "${serverName}": dropping zcodeOauth, as its type must be "client_credentials" or "authorization_code".`);
|
|
52097
|
+
return;
|
|
52098
|
+
}
|
|
52099
|
+
const type = String(value.type);
|
|
52100
|
+
const flow = ZCODE_OAUTH_FLOWS[type];
|
|
52101
|
+
if (!flow) return void 0;
|
|
52102
|
+
const missing = flow.required.filter((key) => typeof value[key] !== "string");
|
|
52103
|
+
if (missing.length > 0) {
|
|
52104
|
+
logger?.warn(`ZCode MCP server "${serverName}": dropping zcodeOauth, as the ${type} flow requires ${missing.join(", ")}.`);
|
|
52105
|
+
return;
|
|
52106
|
+
}
|
|
52107
|
+
const allowed = /* @__PURE__ */ new Set([...flow.required, ...flow.optional]);
|
|
52108
|
+
const oauth = { type };
|
|
52109
|
+
const ignored = [];
|
|
52110
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
52111
|
+
if (key === "type" || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
52112
|
+
if (allowed.has(key) && typeof entry === "string") oauth[key] = entry;
|
|
52113
|
+
else ignored.push(key);
|
|
52114
|
+
}
|
|
52115
|
+
if (ignored.length > 0) logger?.warn(`ZCode MCP server "${serverName}": ignoring zcodeOauth keys the ${type} flow does not accept: ${ignored.join(", ")}.`);
|
|
52116
|
+
return oauth;
|
|
52117
|
+
}
|
|
52118
|
+
/**
|
|
52119
|
+
* The `zcodeOauth` a server was authored with. `getMcpServers()` strips it, so
|
|
52120
|
+
* the authored server (`rawServerOf`) is consulted first.
|
|
52121
|
+
*/
|
|
52122
|
+
function authoredZcodeOauth({ name, config, rawServerOf }) {
|
|
52123
|
+
const raw = rawServerOf?.(name);
|
|
52124
|
+
return isRecord$1(raw) ? raw[ZCODE_OAUTH_CANONICAL_KEY] : config[ZCODE_OAUTH_CANONICAL_KEY];
|
|
52125
|
+
}
|
|
52126
|
+
function isPositiveMs(value) {
|
|
52127
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
52128
|
+
}
|
|
52129
|
+
/**
|
|
52130
|
+
* Resolve ZCode's per-server `timeoutMs` (a positive number of milliseconds):
|
|
52131
|
+
* a valid explicit `timeoutMs` wins, otherwise the canonical `timeout`
|
|
52132
|
+
* (milliseconds, the `gitlabduo-mcp.ts` precedent) is used. A value ZCode
|
|
52133
|
+
* would reject is dropped with a warning instead of making ZCode skip the
|
|
52134
|
+
* server.
|
|
52135
|
+
*/
|
|
52136
|
+
function resolveZcodeTimeoutMs({ serverName, config, logger }) {
|
|
52137
|
+
const { timeoutMs, timeout } = config;
|
|
52138
|
+
if (timeoutMs !== void 0) {
|
|
52139
|
+
if (isPositiveMs(timeoutMs)) return timeoutMs;
|
|
52140
|
+
logger?.warn(`ZCode MCP server "${serverName}": ignoring timeoutMs ${JSON.stringify(timeoutMs)}, as it must be a positive number of milliseconds.`);
|
|
52141
|
+
}
|
|
52142
|
+
if (timeout === void 0) return void 0;
|
|
52143
|
+
if (isPositiveMs(timeout)) return timeout;
|
|
52144
|
+
logger?.warn(`ZCode MCP server "${serverName}": dropping timeout ${JSON.stringify(timeout)}, as timeoutMs must be a positive number of milliseconds.`);
|
|
52145
|
+
}
|
|
52146
|
+
/**
|
|
52147
|
+
* The per-server keys ZCode reads on every transport: `timeoutMs`,
|
|
52148
|
+
* `protocolVersion` and the enable toggle (`enableKey`).
|
|
52149
|
+
*/
|
|
52150
|
+
function toZcodeCommonKeys({ serverName, config, enableKey, logger }) {
|
|
52151
|
+
const common = {};
|
|
52152
|
+
const timeoutMs = resolveZcodeTimeoutMs({
|
|
52153
|
+
serverName,
|
|
52154
|
+
config,
|
|
52155
|
+
logger
|
|
52156
|
+
});
|
|
52157
|
+
if (timeoutMs !== void 0) common.timeoutMs = timeoutMs;
|
|
52158
|
+
const { protocolVersion } = config;
|
|
52159
|
+
if (ZCODE_PROTOCOL_VERSIONS.has(protocolVersion)) common.protocolVersion = protocolVersion;
|
|
52160
|
+
else if (protocolVersion !== void 0) logger?.warn(`ZCode MCP server "${serverName}": dropping protocolVersion ${JSON.stringify(protocolVersion)}, as ZCode accepts only ${[...ZCODE_PROTOCOL_VERSIONS].map((v) => JSON.stringify(v)).join(", ")}.`);
|
|
52161
|
+
if (config.disabled === true) common[enableKey] = false;
|
|
52162
|
+
return common;
|
|
52163
|
+
}
|
|
52164
|
+
/**
|
|
50847
52165
|
* Convert canonical rulesync servers to ZCode's native `mcp.servers` shape:
|
|
50848
52166
|
* stdio servers carry `command`/`args`/`env`, remote servers carry
|
|
50849
52167
|
* `type` (`http` or `sse`), `url` and optional `headers`. A canonical
|
|
@@ -50851,9 +52169,18 @@ function asZcodeRemoteType(stated, url) {
|
|
|
50851
52169
|
* plugin's `.mcp.json`, via `enableKey`), which it defaults to `true` when
|
|
50852
52170
|
* absent.
|
|
50853
52171
|
*
|
|
52172
|
+
* Every server also carries `timeoutMs` (from the canonical `timeout`, or an
|
|
52173
|
+
* explicit `timeoutMs`) and an allow-listed `protocolVersion`; stdio servers
|
|
52174
|
+
* carry `cwd`, and remote servers an `oauth` built from the namespaced
|
|
52175
|
+
* `zcodeOauth`. That key is stripped by `RulesyncMcp.getMcpServers()`, so it
|
|
52176
|
+
* is read from the authored server through `rawServerOf`. ZCode's docs do not
|
|
52177
|
+
* name these keys, so they follow the 3.14.3/3.15.3 runtime's per-server
|
|
52178
|
+
* schema, which is strict: an unknown key makes ZCode skip the server, which
|
|
52179
|
+
* is why the output stays a whitelist.
|
|
52180
|
+
*
|
|
50854
52181
|
* @see https://zcode.z.ai/en/docs/mcp-services
|
|
50855
52182
|
*/
|
|
50856
|
-
function convertToZcodeFormat({ mcpServers, logger, enableKey = "enable" }) {
|
|
52183
|
+
function convertToZcodeFormat({ mcpServers, logger, enableKey = "enable", rawServerOf }) {
|
|
50857
52184
|
const result = {};
|
|
50858
52185
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
50859
52186
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
@@ -50893,6 +52220,16 @@ function convertToZcodeFormat({ mcpServers, logger, enableKey = "enable" }) {
|
|
|
50893
52220
|
converted.type = type;
|
|
50894
52221
|
converted.url = url;
|
|
50895
52222
|
if (config.headers && Object.keys(config.headers).length > 0) converted.headers = config.headers;
|
|
52223
|
+
const oauth = toZcodeOauth({
|
|
52224
|
+
serverName: name,
|
|
52225
|
+
value: authoredZcodeOauth({
|
|
52226
|
+
name,
|
|
52227
|
+
config,
|
|
52228
|
+
rawServerOf
|
|
52229
|
+
}),
|
|
52230
|
+
logger
|
|
52231
|
+
});
|
|
52232
|
+
if (oauth !== void 0) converted.oauth = oauth;
|
|
50896
52233
|
} else {
|
|
50897
52234
|
const [command, ...args] = resolveLocalMcpCommand(config);
|
|
50898
52235
|
if (!command) {
|
|
@@ -50907,35 +52244,62 @@ function convertToZcodeFormat({ mcpServers, logger, enableKey = "enable" }) {
|
|
|
50907
52244
|
converted.command = command;
|
|
50908
52245
|
converted.args = args;
|
|
50909
52246
|
if (config.env && Object.keys(config.env).length > 0) converted.env = config.env;
|
|
52247
|
+
if (typeof config.cwd === "string" && config.cwd !== "") converted.cwd = config.cwd;
|
|
50910
52248
|
}
|
|
50911
|
-
|
|
50912
|
-
|
|
52249
|
+
result[name] = {
|
|
52250
|
+
...converted,
|
|
52251
|
+
...toZcodeCommonKeys({
|
|
52252
|
+
serverName: name,
|
|
52253
|
+
config,
|
|
52254
|
+
enableKey,
|
|
52255
|
+
logger
|
|
52256
|
+
})
|
|
52257
|
+
};
|
|
50913
52258
|
}
|
|
50914
52259
|
return result;
|
|
50915
52260
|
}
|
|
50916
52261
|
/**
|
|
52262
|
+
* ZCode's per-server enable toggle, in both spellings: the runtime normalizes
|
|
52263
|
+
* the legacy `enable` to `enabled`, and a plugin's `.mcp.json` uses `enabled`.
|
|
52264
|
+
*/
|
|
52265
|
+
const ZCODE_ENABLE_KEYS = /* @__PURE__ */ new Set(["enable", "enabled"]);
|
|
52266
|
+
/**
|
|
50917
52267
|
* Convert ZCode's native `mcp.servers` shape back to canonical rulesync
|
|
50918
|
-
* servers: `enable: false`
|
|
50919
|
-
* through untouched so an import keeps whatever a
|
|
50920
|
-
*
|
|
50921
|
-
*
|
|
52268
|
+
* servers: `enable: false` / `enabled: false` map back to `disabled: true`,
|
|
52269
|
+
* and unknown keys pass through untouched so an import keeps whatever a
|
|
52270
|
+
* hand-authored entry declared. `timeoutMs` is lifted back into the canonical
|
|
52271
|
+
* `timeout` (milliseconds); when the server also states a `timeout`, ZCode
|
|
52272
|
+
* has already dropped that one, so `timeoutMs` replaces it. A ZCode-shaped
|
|
52273
|
+
* `oauth` is imported as the namespaced `zcodeOauth`, so its client secret is
|
|
52274
|
+
* never emitted to another tool. Generation back out is a whitelist, so only
|
|
52275
|
+
* the keys `convertToZcodeFormat` writes are re-emitted.
|
|
50922
52276
|
*
|
|
50923
52277
|
* An explicit `enable: true` is dropped rather than mapped to a canonical
|
|
50924
52278
|
* `disabled: false`: absent already means enabled on both sides, so the flag
|
|
50925
52279
|
* carries no information, and emitting it would push a redundant `disabled`
|
|
50926
|
-
* key into every other tool's generated config.
|
|
52280
|
+
* key into every other tool's generated config. (A canonical `enabled` is
|
|
52281
|
+
* rulesync's own generation filter, so it must never be passed through.)
|
|
50927
52282
|
*/
|
|
50928
|
-
function convertFromZcodeFormat({ zcodeServers
|
|
52283
|
+
function convertFromZcodeFormat({ zcodeServers }) {
|
|
50929
52284
|
const result = {};
|
|
50930
52285
|
for (const [name, config] of Object.entries(zcodeServers)) {
|
|
50931
52286
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
50932
52287
|
const converted = {};
|
|
50933
52288
|
for (const [key, value] of Object.entries(config)) {
|
|
50934
52289
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
50935
|
-
if (key
|
|
52290
|
+
if (ZCODE_ENABLE_KEYS.has(key)) {
|
|
50936
52291
|
if (value === false) converted.disabled = true;
|
|
50937
52292
|
continue;
|
|
50938
52293
|
}
|
|
52294
|
+
if (key === "timeoutMs" && typeof value === "number") {
|
|
52295
|
+
converted.timeout = value;
|
|
52296
|
+
continue;
|
|
52297
|
+
}
|
|
52298
|
+
if (key === "timeout" && typeof config.timeoutMs === "number") continue;
|
|
52299
|
+
if (key === "oauth" && isZcodeOauth(value)) {
|
|
52300
|
+
converted[ZCODE_OAUTH_CANONICAL_KEY] = value;
|
|
52301
|
+
continue;
|
|
52302
|
+
}
|
|
50939
52303
|
converted[key] = value;
|
|
50940
52304
|
}
|
|
50941
52305
|
result[name] = converted;
|
|
@@ -50992,7 +52356,8 @@ var ZcodeMcp = class ZcodeMcp extends ToolMcp {
|
|
|
50992
52356
|
const existing = parseZcodeConfig(existingContent, filePath);
|
|
50993
52357
|
const converted = convertToZcodeFormat({
|
|
50994
52358
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
50995
|
-
logger
|
|
52359
|
+
logger,
|
|
52360
|
+
rawServerOf: (name) => rulesyncMcp.getRawMcpServer(name)
|
|
50996
52361
|
});
|
|
50997
52362
|
const existingMcp = isRecord$1(existing["mcp"]) ? existing["mcp"] : {};
|
|
50998
52363
|
return new ZcodeMcp({
|
|
@@ -51096,7 +52461,8 @@ var ZcodePluginMcp = class ZcodePluginMcp extends ToolMcp {
|
|
|
51096
52461
|
const mcpServers = convertToZcodeFormat({
|
|
51097
52462
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
51098
52463
|
logger,
|
|
51099
|
-
enableKey: ZCODE_PLUGIN_ENABLE_KEY
|
|
52464
|
+
enableKey: ZCODE_PLUGIN_ENABLE_KEY,
|
|
52465
|
+
rawServerOf: (name) => rulesyncMcp.getRawMcpServer(name)
|
|
51100
52466
|
});
|
|
51101
52467
|
return new ZcodePluginMcp({
|
|
51102
52468
|
outputRoot,
|
|
@@ -51107,10 +52473,7 @@ var ZcodePluginMcp = class ZcodePluginMcp extends ToolMcp {
|
|
|
51107
52473
|
});
|
|
51108
52474
|
}
|
|
51109
52475
|
toRulesyncMcp() {
|
|
51110
|
-
const mcpServers = convertFromZcodeFormat({
|
|
51111
|
-
zcodeServers: serversOf(this.json),
|
|
51112
|
-
enableKey: ZCODE_PLUGIN_ENABLE_KEY
|
|
51113
|
-
});
|
|
52476
|
+
const mcpServers = convertFromZcodeFormat({ zcodeServers: serversOf(this.json) });
|
|
51114
52477
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
51115
52478
|
}
|
|
51116
52479
|
validate() {
|
|
@@ -51615,6 +52978,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
51615
52978
|
supportsDisabledTools: true
|
|
51616
52979
|
}
|
|
51617
52980
|
}],
|
|
52981
|
+
["openclaw", {
|
|
52982
|
+
class: OpenclawMcp,
|
|
52983
|
+
meta: {
|
|
52984
|
+
supportsProject: false,
|
|
52985
|
+
supportsGlobal: true,
|
|
52986
|
+
supportsEnabledTools: true,
|
|
52987
|
+
supportsDisabledTools: true
|
|
52988
|
+
}
|
|
52989
|
+
}],
|
|
51618
52990
|
["kimi-code", {
|
|
51619
52991
|
class: KimiCodeMcp,
|
|
51620
52992
|
meta: {
|
|
@@ -52768,7 +54140,7 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
52768
54140
|
*
|
|
52769
54141
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
52770
54142
|
*/
|
|
52771
|
-
const REGEX_PREFIX = "regex:";
|
|
54143
|
+
const REGEX_PREFIX$1 = "regex:";
|
|
52772
54144
|
/**
|
|
52773
54145
|
* The Antigravity actions whose target is a command: `command`, and
|
|
52774
54146
|
* `unsandboxed`, which takes the same word-by-word prefix or `regex:` target.
|
|
@@ -53106,7 +54478,7 @@ function wordToRegexSource(word) {
|
|
|
53106
54478
|
* pattern does.
|
|
53107
54479
|
*/
|
|
53108
54480
|
function toAntigravityCommandTarget(pattern) {
|
|
53109
|
-
if (pattern.startsWith(REGEX_PREFIX)) {
|
|
54481
|
+
if (pattern.startsWith(REGEX_PREFIX$1)) {
|
|
53110
54482
|
const regexWords = splitWords(pattern.slice(6));
|
|
53111
54483
|
if (regexWords.length === 0) return { skipReason: "The regex is empty" };
|
|
53112
54484
|
const invalid = regexWords.find(isInvalidRegex);
|
|
@@ -53126,7 +54498,7 @@ function toAntigravityCommandTarget(pattern) {
|
|
|
53126
54498
|
const head = words.slice(0, -1);
|
|
53127
54499
|
if (head.length > 0 && isStarWord(words.at(-1) ?? []) && head.every(isLiteralWord)) return { target: head.map(wordToLiteral).join(" ") };
|
|
53128
54500
|
return {
|
|
53129
|
-
target: `${REGEX_PREFIX}${words.map(wordToRegexSource).join(" ")}`,
|
|
54501
|
+
target: `${REGEX_PREFIX$1}${words.map(wordToRegexSource).join(" ")}`,
|
|
53130
54502
|
note
|
|
53131
54503
|
};
|
|
53132
54504
|
}
|
|
@@ -53138,7 +54510,7 @@ function toAntigravityCommandTarget(pattern) {
|
|
|
53138
54510
|
* word with a brace may be part of `{a,b}` alternatives, so it ends the prefix.
|
|
53139
54511
|
*/
|
|
53140
54512
|
function toAntigravityCommandLiteralPrefix(pattern) {
|
|
53141
|
-
if (pattern.startsWith(REGEX_PREFIX)) return;
|
|
54513
|
+
if (pattern.startsWith(REGEX_PREFIX$1)) return;
|
|
53142
54514
|
const words = splitIntoWords(parseGlobPattern(pattern).steps);
|
|
53143
54515
|
const prefix = [];
|
|
53144
54516
|
for (const word of words) {
|
|
@@ -53146,7 +54518,7 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
53146
54518
|
if (literal === void 0 || literal.includes("{") || literal.includes("}")) break;
|
|
53147
54519
|
prefix.push(literal);
|
|
53148
54520
|
}
|
|
53149
|
-
if (prefix.length === 0 || prefix[0]?.startsWith(REGEX_PREFIX)) return;
|
|
54521
|
+
if (prefix.length === 0 || prefix[0]?.startsWith(REGEX_PREFIX$1)) return;
|
|
53150
54522
|
return prefix.join(" ");
|
|
53151
54523
|
}
|
|
53152
54524
|
/**
|
|
@@ -53197,11 +54569,11 @@ function regexToGlob(source) {
|
|
|
53197
54569
|
* `regex:` target is kept as is.
|
|
53198
54570
|
*/
|
|
53199
54571
|
function fromAntigravityCommandTarget(target) {
|
|
53200
|
-
if (target.startsWith(REGEX_PREFIX)) return regexToGlob(target.slice(6)) ?? target;
|
|
54572
|
+
if (target.startsWith(REGEX_PREFIX$1)) return regexToGlob(target.slice(6)) ?? target;
|
|
53201
54573
|
if (target === "*") return target;
|
|
53202
54574
|
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) {
|
|
53203
54575
|
const words = splitWords(target);
|
|
53204
|
-
return `${REGEX_PREFIX}${words.map((word) => `^${[...word].map(escapeRegexCharacter).join("")}$`).join(" ")}`;
|
|
54576
|
+
return `${REGEX_PREFIX$1}${words.map((word) => `^${[...word].map(escapeRegexCharacter).join("")}$`).join(" ")}`;
|
|
53205
54577
|
}
|
|
53206
54578
|
return `${target} *`;
|
|
53207
54579
|
}
|
|
@@ -53221,7 +54593,7 @@ function fromAntigravityCommandTarget(target) {
|
|
|
53221
54593
|
* @see https://antigravity.google/docs/permissions
|
|
53222
54594
|
*/
|
|
53223
54595
|
function describeAntigravityCommandImportLoss(target) {
|
|
53224
|
-
if (target.startsWith(REGEX_PREFIX) || target === "*") return;
|
|
54596
|
+
if (target.startsWith(REGEX_PREFIX$1) || target === "*") return;
|
|
53225
54597
|
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
53226
54598
|
reason: "it holds characters that are special in a glob (`*`, `?`, `[`, `{` or `\\`), which Antigravity matches literally (likely output of an older rulesync), so it is imported as a literal-match regex that no other tool enforces",
|
|
53227
54599
|
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
@@ -55123,7 +56495,7 @@ function buildVscodeCommandLists({ rules, toolLabel, logger, prefixSemantics = "
|
|
|
55123
56495
|
//#region src/features/permissions/bob-permissions.ts
|
|
55124
56496
|
const TOOL_LABEL = "IBM Bob";
|
|
55125
56497
|
/** The top-level settings key holding Bob's auto-approval configuration. */
|
|
55126
|
-
const APPROVAL_KEY = "approval";
|
|
56498
|
+
const APPROVAL_KEY$1 = "approval";
|
|
55127
56499
|
/** The top-level settings key holding Bob's per-feature auto-approve toggles. */
|
|
55128
56500
|
const AUTO_APPROVE_KEY = "autoApprove";
|
|
55129
56501
|
/** The tool permission groups Bob documents for `approval.allowed_permissions`. */
|
|
@@ -55144,7 +56516,7 @@ const BOB_AUTO_APPROVE_KEYS = /* @__PURE__ */ new Set(["skills"]);
|
|
|
55144
56516
|
const OUTSIDE_WORKSPACE_GROUP_ID = "read";
|
|
55145
56517
|
/** The only executor tool Bob's `allowedExecutors` list documents. */
|
|
55146
56518
|
const EXECUTE_COMMAND_TOOL_ID = "execute_command";
|
|
55147
|
-
const SURFACE_LABEL = `${APPROVAL_KEY}.allowedExecutors[${EXECUTE_COMMAND_TOOL_ID}].approvedCommands/deniedCommands`;
|
|
56519
|
+
const SURFACE_LABEL = `${APPROVAL_KEY$1}.allowedExecutors[${EXECUTE_COMMAND_TOOL_ID}].approvedCommands/deniedCommands`;
|
|
55148
56520
|
function asStringArray$2(value) {
|
|
55149
56521
|
if (!Array.isArray(value)) return [];
|
|
55150
56522
|
return value.filter((entry) => typeof entry === "string");
|
|
@@ -55236,7 +56608,7 @@ function warnAboutUndocumentedSettings({ override, logger }) {
|
|
|
55236
56608
|
* fetched permissions file. Switches that are already on are not repeated.
|
|
55237
56609
|
*/
|
|
55238
56610
|
function warnAboutNewAutoApprovals({ settings, override, filePath, logger }) {
|
|
55239
|
-
const existingApproval = isPlainObject$1(settings[APPROVAL_KEY]) ? settings[APPROVAL_KEY] : {};
|
|
56611
|
+
const existingApproval = isPlainObject$1(settings[APPROVAL_KEY$1]) ? settings[APPROVAL_KEY$1] : {};
|
|
55240
56612
|
const existingGroups = new Set(asStringArray$2(existingApproval.allowed_permissions));
|
|
55241
56613
|
const existingOutside = new Set((Array.isArray(existingApproval.permissionOptions) ? existingApproval.permissionOptions : []).filter(isPlainObject$1).filter((option) => option.enableOutsideWorkspace === true).map((option) => option.groupId));
|
|
55242
56614
|
const existingAutoApprove = isPlainObject$1(settings[AUTO_APPROVE_KEY]) ? settings[AUTO_APPROVE_KEY] : {};
|
|
@@ -55302,7 +56674,7 @@ function buildBobCommandLists({ permission, logger }) {
|
|
|
55302
56674
|
*/
|
|
55303
56675
|
function readBobOverride(settings) {
|
|
55304
56676
|
const override = {};
|
|
55305
|
-
const approval = settings[APPROVAL_KEY];
|
|
56677
|
+
const approval = settings[APPROVAL_KEY$1];
|
|
55306
56678
|
if (isPlainObject$1(approval)) {
|
|
55307
56679
|
const overrideApproval = {};
|
|
55308
56680
|
const groups = approval.allowed_permissions;
|
|
@@ -55431,8 +56803,8 @@ var BobPermissions = class BobPermissions extends ToolPermissions {
|
|
|
55431
56803
|
deniedCommands: denied
|
|
55432
56804
|
};
|
|
55433
56805
|
}
|
|
55434
|
-
patch[APPROVAL_KEY] = buildApproval({
|
|
55435
|
-
existingApproval: settings[APPROVAL_KEY],
|
|
56806
|
+
patch[APPROVAL_KEY$1] = buildApproval({
|
|
56807
|
+
existingApproval: settings[APPROVAL_KEY$1],
|
|
55436
56808
|
overrideApproval,
|
|
55437
56809
|
commandLists
|
|
55438
56810
|
});
|
|
@@ -55461,7 +56833,7 @@ var BobPermissions = class BobPermissions extends ToolPermissions {
|
|
|
55461
56833
|
fileContent: this.getFileContent(),
|
|
55462
56834
|
filePath: join(this.getRelativeDirPath(), this.getRelativeFilePath())
|
|
55463
56835
|
});
|
|
55464
|
-
const approval = settings[APPROVAL_KEY];
|
|
56836
|
+
const approval = settings[APPROVAL_KEY$1];
|
|
55465
56837
|
const entry = (isPlainObject$1(approval) && Array.isArray(approval.allowedExecutors) ? approval.allowedExecutors : []).find(isExecuteCommandEntry);
|
|
55466
56838
|
const rules = {};
|
|
55467
56839
|
for (const pattern of asStringArray$2(entry?.approvedCommands)) rules[pattern] = "allow";
|
|
@@ -57177,7 +58549,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
57177
58549
|
"ask",
|
|
57178
58550
|
"deny"
|
|
57179
58551
|
];
|
|
57180
|
-
const CATCH_ALL_PATTERN$
|
|
58552
|
+
const CATCH_ALL_PATTERN$13 = "*";
|
|
57181
58553
|
const MCP_CATEGORY = "mcp";
|
|
57182
58554
|
const MCP_RULE_PREFIX = "mcp__";
|
|
57183
58555
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -57204,7 +58576,7 @@ const CATEGORY_TO_CODEBUDDY_TOOL = {
|
|
|
57204
58576
|
skill: "Skill"
|
|
57205
58577
|
};
|
|
57206
58578
|
const CODEBUDDY_TOOL_TO_CATEGORY = Object.fromEntries(Object.entries(CATEGORY_TO_CODEBUDDY_TOOL).map(([category, tool]) => [tool, category]));
|
|
57207
|
-
function toStringList$
|
|
58579
|
+
function toStringList$3(value) {
|
|
57208
58580
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
57209
58581
|
}
|
|
57210
58582
|
/**
|
|
@@ -57250,7 +58622,7 @@ function categoryOfRule$2(rule) {
|
|
|
57250
58622
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
57251
58623
|
*/
|
|
57252
58624
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
57253
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
58625
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$13;
|
|
57254
58626
|
if (category === MCP_CATEGORY) {
|
|
57255
58627
|
if (catchAll && action === "allow") {
|
|
57256
58628
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -57259,7 +58631,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
57259
58631
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
57260
58632
|
}
|
|
57261
58633
|
if (category === "*") {
|
|
57262
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
58634
|
+
if (catchAll) return CATCH_ALL_PATTERN$13;
|
|
57263
58635
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
57264
58636
|
return null;
|
|
57265
58637
|
}
|
|
@@ -57277,7 +58649,7 @@ function parseCodebuddyRule(rule) {
|
|
|
57277
58649
|
const { specifier } = splitCodebuddyRule(rule);
|
|
57278
58650
|
if (specifier === void 0) return {
|
|
57279
58651
|
category,
|
|
57280
|
-
pattern: CATCH_ALL_PATTERN$
|
|
58652
|
+
pattern: CATCH_ALL_PATTERN$13
|
|
57281
58653
|
};
|
|
57282
58654
|
const trimmed = specifier.trim();
|
|
57283
58655
|
return trimmed === "" ? null : {
|
|
@@ -57318,7 +58690,7 @@ function buildCodebuddyRuleLists({ config, existingPermissions, logger }) {
|
|
|
57318
58690
|
ask: [],
|
|
57319
58691
|
deny: []
|
|
57320
58692
|
};
|
|
57321
|
-
for (const list of CODEBUDDY_RULE_LISTS) lists[list] = toStringList$
|
|
58693
|
+
for (const list of CODEBUDDY_RULE_LISTS) lists[list] = toStringList$3(existingPermissions[list]).filter((rule) => {
|
|
57322
58694
|
const category = categoryOfRule$2(rule);
|
|
57323
58695
|
return category === void 0 || !managedCategories.has(category);
|
|
57324
58696
|
});
|
|
@@ -57409,7 +58781,7 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
57409
58781
|
});
|
|
57410
58782
|
const permissions = isRecord$1(settings[CODEBUDDY_PERMISSIONS_KEY]) ? settings[CODEBUDDY_PERMISSIONS_KEY] : {};
|
|
57411
58783
|
const permission = {};
|
|
57412
|
-
for (const action of CODEBUDDY_RULE_LISTS) for (const rule of toStringList$
|
|
58784
|
+
for (const action of CODEBUDDY_RULE_LISTS) for (const rule of toStringList$3(permissions[action])) {
|
|
57413
58785
|
const parsed = parseCodebuddyRule(rule);
|
|
57414
58786
|
if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
57415
58787
|
const rules = permission[parsed.category] ??= {};
|
|
@@ -59528,7 +60900,7 @@ function mapBashActionToDecision(action) {
|
|
|
59528
60900
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
59529
60901
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
59530
60902
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
59531
|
-
const CATCH_ALL_PATTERN$
|
|
60903
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
59532
60904
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
59533
60905
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
59534
60906
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -59553,14 +60925,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
|
59553
60925
|
web_fetch: "webfetch",
|
|
59554
60926
|
web_search: "websearch"
|
|
59555
60927
|
};
|
|
59556
|
-
const ACTION_RANK$
|
|
60928
|
+
const ACTION_RANK$6 = {
|
|
59557
60929
|
allow: 0,
|
|
59558
60930
|
ask: 1,
|
|
59559
60931
|
deny: 2
|
|
59560
60932
|
};
|
|
59561
60933
|
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
59562
60934
|
function isStricterAction({ action, existing }) {
|
|
59563
|
-
return existing === void 0 || ACTION_RANK$
|
|
60935
|
+
return existing === void 0 || ACTION_RANK$6[action] > ACTION_RANK$6[existing];
|
|
59564
60936
|
}
|
|
59565
60937
|
/**
|
|
59566
60938
|
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
@@ -59583,10 +60955,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
59583
60955
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
59584
60956
|
*/
|
|
59585
60957
|
function buildCommandcodeRule(category, pattern) {
|
|
59586
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
60958
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$12 || pattern === "";
|
|
59587
60959
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
59588
60960
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
59589
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
60961
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$12 : null;
|
|
59590
60962
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
59591
60963
|
if (tool === void 0) return null;
|
|
59592
60964
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -59622,7 +60994,7 @@ function splitCommandcodeRule(rule) {
|
|
|
59622
60994
|
}
|
|
59623
60995
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
59624
60996
|
function isCatchAllSpecifier(inner) {
|
|
59625
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
60997
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$12;
|
|
59626
60998
|
}
|
|
59627
60999
|
/**
|
|
59628
61000
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -59656,34 +61028,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
59656
61028
|
if (server.length === 0) return null;
|
|
59657
61029
|
const everyServer = {
|
|
59658
61030
|
category: "mcp",
|
|
59659
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61031
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59660
61032
|
notInAllow: true
|
|
59661
61033
|
};
|
|
59662
61034
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
59663
|
-
if (server === CATCH_ALL_PATTERN$
|
|
59664
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
61035
|
+
if (server === CATCH_ALL_PATTERN$12) return everyServer;
|
|
61036
|
+
if (server.includes(CATCH_ALL_PATTERN$12)) return null;
|
|
59665
61037
|
return {
|
|
59666
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
59667
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61038
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$12 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
|
|
61039
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59668
61040
|
notInAllow: false
|
|
59669
61041
|
};
|
|
59670
61042
|
}
|
|
59671
61043
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
59672
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
61044
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$12);
|
|
59673
61045
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
59674
61046
|
category,
|
|
59675
61047
|
pattern: inner,
|
|
59676
61048
|
notInAllow: true
|
|
59677
61049
|
} : null;
|
|
59678
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
59679
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
61050
|
+
if (remainder === CATCH_ALL_PATTERN$12) return everyServer;
|
|
61051
|
+
if (rest === CATCH_ALL_PATTERN$12 && !server.includes(CATCH_ALL_PATTERN$12)) return {
|
|
59680
61052
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
59681
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61053
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59682
61054
|
notInAllow: true
|
|
59683
61055
|
};
|
|
59684
61056
|
return namesTool ? {
|
|
59685
61057
|
category,
|
|
59686
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61058
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59687
61059
|
notInAllow: false
|
|
59688
61060
|
} : null;
|
|
59689
61061
|
}
|
|
@@ -59707,14 +61079,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
59707
61079
|
if (category === "bash") {
|
|
59708
61080
|
if (isCatchAllSpecifier(inner)) return {
|
|
59709
61081
|
category,
|
|
59710
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61082
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59711
61083
|
notInAllow: false
|
|
59712
61084
|
};
|
|
59713
61085
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
59714
61086
|
if (normalized === "" || normalized === ":*") return null;
|
|
59715
61087
|
return isCatchAllSpecifier(normalized) ? {
|
|
59716
61088
|
category,
|
|
59717
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61089
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59718
61090
|
notInAllow: true
|
|
59719
61091
|
} : {
|
|
59720
61092
|
category,
|
|
@@ -59724,7 +61096,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
59724
61096
|
}
|
|
59725
61097
|
if (isCatchAllSpecifier(inner)) return {
|
|
59726
61098
|
category,
|
|
59727
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61099
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59728
61100
|
notInAllow: false
|
|
59729
61101
|
};
|
|
59730
61102
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -59743,9 +61115,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
59743
61115
|
*/
|
|
59744
61116
|
function parseCommandcodeRule(rule) {
|
|
59745
61117
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
59746
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
61118
|
+
if (tool === CATCH_ALL_PATTERN$12) return isCatchAllSpecifier(inner) ? {
|
|
59747
61119
|
category: "*",
|
|
59748
|
-
pattern: CATCH_ALL_PATTERN$
|
|
61120
|
+
pattern: CATCH_ALL_PATTERN$12,
|
|
59749
61121
|
notInAllow: true
|
|
59750
61122
|
} : null;
|
|
59751
61123
|
const lowered = tool.toLowerCase();
|
|
@@ -59815,7 +61187,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
59815
61187
|
}).filter((rule) => {
|
|
59816
61188
|
const parsed = parseCommandcodeRule(rule);
|
|
59817
61189
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
59818
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
61190
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$12})`);
|
|
59819
61191
|
if (key !== "allow" && isStricterAction({
|
|
59820
61192
|
action: key,
|
|
59821
61193
|
existing: replacement
|
|
@@ -59872,7 +61244,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
59872
61244
|
const ranked = /* @__PURE__ */ new Map();
|
|
59873
61245
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
59874
61246
|
for (const [category, rules] of Object.entries(permission)) {
|
|
59875
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
61247
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$12);
|
|
59876
61248
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
59877
61249
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
59878
61250
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -60143,7 +61515,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
60143
61515
|
//#endregion
|
|
60144
61516
|
//#region src/features/permissions/continue-permissions.ts
|
|
60145
61517
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
60146
|
-
const CATCH_ALL_PATTERN$
|
|
61518
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
60147
61519
|
/**
|
|
60148
61520
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
60149
61521
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -60151,7 +61523,7 @@ const CATCH_ALL_PATTERN$10 = "*";
|
|
|
60151
61523
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
60152
61524
|
*/
|
|
60153
61525
|
function normalizeCatchAllPattern$1(pattern) {
|
|
60154
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
61526
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$11 : pattern;
|
|
60155
61527
|
}
|
|
60156
61528
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
60157
61529
|
bash: "Bash",
|
|
@@ -60188,7 +61560,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
60188
61560
|
* `Tool(pattern)`.
|
|
60189
61561
|
*/
|
|
60190
61562
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
60191
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
61563
|
+
return pattern === CATCH_ALL_PATTERN$11 ? toolName : `${toolName}(${pattern})`;
|
|
60192
61564
|
}
|
|
60193
61565
|
/**
|
|
60194
61566
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -60202,7 +61574,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
60202
61574
|
if (toolName === "") return;
|
|
60203
61575
|
return {
|
|
60204
61576
|
toolName,
|
|
60205
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
61577
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$11 : pattern
|
|
60206
61578
|
};
|
|
60207
61579
|
}
|
|
60208
61580
|
/**
|
|
@@ -60376,7 +61748,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
60376
61748
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
60377
61749
|
continue;
|
|
60378
61750
|
}
|
|
60379
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
61751
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$11) {
|
|
60380
61752
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
60381
61753
|
continue;
|
|
60382
61754
|
}
|
|
@@ -60808,7 +62180,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
60808
62180
|
* arrays are accepted only in MDM/enterprise managed settings, and session
|
|
60809
62181
|
* tool approvals are machine-written to `permissions-config.json`.
|
|
60810
62182
|
*/
|
|
60811
|
-
const WEBFETCH_CATEGORY = "webfetch";
|
|
62183
|
+
const WEBFETCH_CATEGORY$1 = "webfetch";
|
|
60812
62184
|
/** `~/.copilot/settings.json` and `.github/copilot/settings.json` keys. */
|
|
60813
62185
|
const ALLOWED_URLS_KEY = "allowedUrls";
|
|
60814
62186
|
const DENIED_URLS_KEY = "deniedUrls";
|
|
@@ -60895,7 +62267,7 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
60895
62267
|
const paths = CopilotcliPermissions.getSettablePaths({ global });
|
|
60896
62268
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
60897
62269
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
60898
|
-
const rules = rulesyncPermissions.getJson().permission[WEBFETCH_CATEGORY];
|
|
62270
|
+
const rules = rulesyncPermissions.getJson().permission[WEBFETCH_CATEGORY$1];
|
|
60899
62271
|
const patch = {};
|
|
60900
62272
|
if (rules !== void 0) {
|
|
60901
62273
|
const { allowedUrls, deniedUrls } = splitUrlRules(rules);
|
|
@@ -60923,7 +62295,7 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
60923
62295
|
const rules = {};
|
|
60924
62296
|
for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
|
|
60925
62297
|
if (this.global) for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) rules[pattern] ??= "allow";
|
|
60926
|
-
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY]: rules } : {};
|
|
62298
|
+
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY$1]: rules } : {};
|
|
60927
62299
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
60928
62300
|
}
|
|
60929
62301
|
parseSettings() {
|
|
@@ -60958,14 +62330,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
60958
62330
|
};
|
|
60959
62331
|
//#endregion
|
|
60960
62332
|
//#region src/features/permissions/crush-permissions.ts
|
|
60961
|
-
const CATCH_ALL_PATTERN$
|
|
62333
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
60962
62334
|
/**
|
|
60963
62335
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
60964
62336
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
60965
62337
|
* still be written.
|
|
60966
62338
|
*/
|
|
60967
62339
|
function normalizeCatchAllPattern(pattern) {
|
|
60968
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
62340
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
|
|
60969
62341
|
}
|
|
60970
62342
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
60971
62343
|
bash: "bash",
|
|
@@ -61164,7 +62536,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
61164
62536
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
61165
62537
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
61166
62538
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
61167
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
62539
|
+
if (pattern === CATCH_ALL_PATTERN$10) {
|
|
61168
62540
|
catchAllAction = action;
|
|
61169
62541
|
continue;
|
|
61170
62542
|
}
|
|
@@ -61237,11 +62609,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
61237
62609
|
};
|
|
61238
62610
|
for (const entry of disabled) {
|
|
61239
62611
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
61240
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
62612
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$10] = "deny";
|
|
61241
62613
|
}
|
|
61242
62614
|
for (const entry of allowed) {
|
|
61243
62615
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
61244
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
62616
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$10] ??= "allow";
|
|
61245
62617
|
}
|
|
61246
62618
|
return { permission };
|
|
61247
62619
|
}
|
|
@@ -62683,7 +64055,8 @@ const DEVIN_RESTRICTION_LOSING_SANDBOX_PATHS = [
|
|
|
62683
64055
|
* (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
|
|
62684
64056
|
* merge into the existing JSON and the file is never deleted; only the keys
|
|
62685
64057
|
* this feature manages are rewritten — `permissions`, plus `sandbox` in global
|
|
62686
|
-
* mode
|
|
64058
|
+
* mode and the plugin governance lists (`requiredPlugins` / `optionalPlugins` /
|
|
64059
|
+
* `forbiddenPlugins`) in project mode when the `devin` override authors them.
|
|
62687
64060
|
*
|
|
62688
64061
|
* The sibling `sandbox` block — which decides what a permitted command may
|
|
62689
64062
|
* reach rather than which commands are permitted — has no canonical category
|
|
@@ -62704,7 +64077,8 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
62704
64077
|
/**
|
|
62705
64078
|
* config.json may carry the MCP/hooks features' keys, so it is never deleted;
|
|
62706
64079
|
* only the keys this feature manages are rewritten — `permissions`, plus
|
|
62707
|
-
* `sandbox`
|
|
64080
|
+
* `sandbox` (global) and the plugin governance lists (project) when the
|
|
64081
|
+
* `devin` override authors them.
|
|
62708
64082
|
*/
|
|
62709
64083
|
isDeletable() {
|
|
62710
64084
|
return false;
|
|
@@ -62799,6 +64173,13 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
62799
64173
|
});
|
|
62800
64174
|
} else if (Object.keys(authoredSandboxRecord).length > 0) logger?.warn("Devin reads 'sandbox' from the user config only, so the 'devin.sandbox' override was dropped from the project config. Generate with --global to author it.");
|
|
62801
64175
|
}
|
|
64176
|
+
applyPluginGovernanceOverride({
|
|
64177
|
+
override: config.devin,
|
|
64178
|
+
settings,
|
|
64179
|
+
patch,
|
|
64180
|
+
global,
|
|
64181
|
+
logger
|
|
64182
|
+
});
|
|
62802
64183
|
return new DevinPermissions({
|
|
62803
64184
|
outputRoot,
|
|
62804
64185
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -62813,7 +64194,7 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
62813
64194
|
validate
|
|
62814
64195
|
});
|
|
62815
64196
|
}
|
|
62816
|
-
toRulesyncPermissions() {
|
|
64197
|
+
toRulesyncPermissions({ logger } = {}) {
|
|
62817
64198
|
let settings;
|
|
62818
64199
|
try {
|
|
62819
64200
|
const parsed = JSON.parse(this.getFileContent());
|
|
@@ -62828,8 +64209,16 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
62828
64209
|
deny: Array.isArray(permissions.deny) ? permissions.deny : []
|
|
62829
64210
|
});
|
|
62830
64211
|
const sandbox = asDevinRecord(settings.sandbox);
|
|
64212
|
+
const devinOverride = {};
|
|
64213
|
+
if (Object.keys(sandbox).length > 0) devinOverride.sandbox = sandbox;
|
|
64214
|
+
for (const key of DEVIN_PLUGIN_GOVERNANCE_KEYS) {
|
|
64215
|
+
const value = settings[key];
|
|
64216
|
+
if (value === void 0) continue;
|
|
64217
|
+
if (DEVIN_PLUGIN_GOVERNANCE_SCHEMAS[key].safeParse(value).success) devinOverride[key] = value;
|
|
64218
|
+
else logger?.warn(`Skipped Devin \`${key}\` on import: ${key === "forbiddenPlugins" ? "expected an array of strings" : "expected an array of plugin sources (strings or objects)"}.`);
|
|
64219
|
+
}
|
|
62831
64220
|
const result = { ...config };
|
|
62832
|
-
if (Object.keys(
|
|
64221
|
+
if (Object.keys(devinOverride).length > 0) result.devin = devinOverride;
|
|
62833
64222
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
62834
64223
|
}
|
|
62835
64224
|
validate() {
|
|
@@ -62849,6 +64238,42 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
62849
64238
|
}
|
|
62850
64239
|
};
|
|
62851
64240
|
/**
|
|
64241
|
+
* The repo-level plugin governance lists of a checkout's `.devin/config.json`.
|
|
64242
|
+
*
|
|
64243
|
+
* @see https://docs.devin.ai/cli/extensibility/plugins/overview
|
|
64244
|
+
*/
|
|
64245
|
+
const DEVIN_PLUGIN_GOVERNANCE_KEYS = [
|
|
64246
|
+
"requiredPlugins",
|
|
64247
|
+
"optionalPlugins",
|
|
64248
|
+
"forbiddenPlugins"
|
|
64249
|
+
];
|
|
64250
|
+
/**
|
|
64251
|
+
* Patch the plugin governance lists the `devin` override authors into the
|
|
64252
|
+
* project `.devin/config.json`. Each authored list replaces the file's list
|
|
64253
|
+
* whole; a list it does not author is left as the file holds it. Devin reads these
|
|
64254
|
+
* lists only from a checkout's `.devin/config.json`, so in global mode they are
|
|
64255
|
+
* dropped with a warning. Losing a `forbiddenPlugins` entry the file held is
|
|
64256
|
+
* announced, since that lifts a restriction. A non-empty `requiredPlugins` that changes the file
|
|
64257
|
+
* is announced, since Devin installs required plugins automatically and a
|
|
64258
|
+
* plugin can ship its own hooks and MCP servers.
|
|
64259
|
+
*/
|
|
64260
|
+
function applyPluginGovernanceOverride({ override, settings, patch, global, logger }) {
|
|
64261
|
+
const authoredKeys = DEVIN_PLUGIN_GOVERNANCE_KEYS.filter((key) => override?.[key] !== void 0);
|
|
64262
|
+
if (authoredKeys.length === 0) return;
|
|
64263
|
+
if (global) {
|
|
64264
|
+
logger?.warn(`Devin reads the plugin governance lists (${authoredKeys.join(", ")}) from a checkout's '.devin/config.json' only, so they were dropped from the user config. Generate without --global to author them.`);
|
|
64265
|
+
return;
|
|
64266
|
+
}
|
|
64267
|
+
for (const key of authoredKeys) patch[key] = override?.[key];
|
|
64268
|
+
const forbidden = override?.forbiddenPlugins;
|
|
64269
|
+
if (Array.isArray(forbidden) && Array.isArray(settings.forbiddenPlugins)) {
|
|
64270
|
+
const lost = settings.forbiddenPlugins.filter((entry) => !forbidden.includes(entry));
|
|
64271
|
+
if (lost.length > 0) logger?.warn(`Replacing Devin \`forbiddenPlugins\` drops ${lost.map(quoteValueForWarning).join(", ")} from '.devin/config.json': those plugins are no longer forbidden at the repo level.`);
|
|
64272
|
+
}
|
|
64273
|
+
const required = override?.requiredPlugins;
|
|
64274
|
+
if (Array.isArray(required) && required.length > 0 && !isDeepStrictEqual(required, settings.requiredPlugins)) logger?.warn(`Writing Devin \`requiredPlugins\` (${required.map(quoteValueForWarning).join(", ")}): Devin installs required plugins automatically, and a plugin can ship its own hooks and MCP servers.`);
|
|
64275
|
+
}
|
|
64276
|
+
/**
|
|
62852
64277
|
* Collapse the pattern rules of a bare-only scope to the single action Devin
|
|
62853
64278
|
* can hold for it, using deny > ask > allow precedence. A map without a
|
|
62854
64279
|
* catch-all grants nothing to unmatched inputs, so an implicit `ask` joins the
|
|
@@ -63106,7 +64531,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
63106
64531
|
//#region src/features/permissions/goose-permissions.ts
|
|
63107
64532
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
63108
64533
|
const GOOSE_USER_KEY = "user";
|
|
63109
|
-
const CATCH_ALL_PATTERN$
|
|
64534
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
63110
64535
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
63111
64536
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
63112
64537
|
shell: ["bash"],
|
|
@@ -63118,7 +64543,7 @@ const ACTION_TO_GOOSE_LIST = {
|
|
|
63118
64543
|
ask: "ask_before",
|
|
63119
64544
|
deny: "never_allow"
|
|
63120
64545
|
};
|
|
63121
|
-
const ACTION_RANK$
|
|
64546
|
+
const ACTION_RANK$5 = {
|
|
63122
64547
|
allow: 0,
|
|
63123
64548
|
ask: 1,
|
|
63124
64549
|
deny: 2
|
|
@@ -63270,14 +64695,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
63270
64695
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
63271
64696
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
63272
64697
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
63273
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
64698
|
+
if (pattern !== CATCH_ALL_PATTERN$9) {
|
|
63274
64699
|
logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
63275
64700
|
continue;
|
|
63276
64701
|
}
|
|
63277
64702
|
const previous = assigned.get(toolName);
|
|
63278
64703
|
if (previous === action) continue;
|
|
63279
64704
|
if (previous !== void 0) {
|
|
63280
|
-
const stricter = ACTION_RANK$
|
|
64705
|
+
const stricter = ACTION_RANK$5[action] > ACTION_RANK$5[previous] ? action : previous;
|
|
63281
64706
|
logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
|
|
63282
64707
|
if (stricter === previous) continue;
|
|
63283
64708
|
const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
|
|
@@ -63301,7 +64726,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
63301
64726
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
63302
64727
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
63303
64728
|
permission[category] ??= {};
|
|
63304
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
64729
|
+
permission[category][CATCH_ALL_PATTERN$9] = action;
|
|
63305
64730
|
}
|
|
63306
64731
|
}
|
|
63307
64732
|
return { permission };
|
|
@@ -63312,7 +64737,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
63312
64737
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
63313
64738
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
63314
64739
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
63315
|
-
const CATCH_ALL_PATTERN$
|
|
64740
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
63316
64741
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
63317
64742
|
const CATEGORY_TO_GROK_TOOL = {
|
|
63318
64743
|
bash: "Bash",
|
|
@@ -63358,15 +64783,15 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
63358
64783
|
* under it has no Grok spelling and returns `null`.
|
|
63359
64784
|
*/
|
|
63360
64785
|
function buildGrokEntry(category, pattern) {
|
|
63361
|
-
if (category === "*") return pattern === CATCH_ALL_PATTERN$
|
|
64786
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$8 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
63362
64787
|
if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
|
|
63363
64788
|
const remainder = category.slice(5);
|
|
63364
64789
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
63365
64790
|
}
|
|
63366
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
64791
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$8 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
63367
64792
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
63368
64793
|
if (tool === void 0) return null;
|
|
63369
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
64794
|
+
return pattern === CATCH_ALL_PATTERN$8 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
63370
64795
|
}
|
|
63371
64796
|
/**
|
|
63372
64797
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -63388,21 +64813,21 @@ function parseGrokEntry(entry) {
|
|
|
63388
64813
|
}
|
|
63389
64814
|
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
63390
64815
|
category: "*",
|
|
63391
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64816
|
+
pattern: CATCH_ALL_PATTERN$8
|
|
63392
64817
|
};
|
|
63393
64818
|
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
|
|
63394
64819
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
63395
64820
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
63396
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64821
|
+
pattern: CATCH_ALL_PATTERN$8
|
|
63397
64822
|
} : {
|
|
63398
64823
|
category: "mcp",
|
|
63399
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64824
|
+
pattern: CATCH_ALL_PATTERN$8
|
|
63400
64825
|
};
|
|
63401
64826
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
63402
64827
|
if (category === void 0) return null;
|
|
63403
64828
|
return {
|
|
63404
64829
|
category,
|
|
63405
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
64830
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$8
|
|
63406
64831
|
};
|
|
63407
64832
|
}
|
|
63408
64833
|
/**
|
|
@@ -63419,14 +64844,14 @@ function parseGrokEntry(entry) {
|
|
|
63419
64844
|
function parseClaudeStyleMcpEntry(entry) {
|
|
63420
64845
|
const rest = entry.slice(5);
|
|
63421
64846
|
if (rest.length === 0) return null;
|
|
63422
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
64847
|
+
if (rest === CATCH_ALL_PATTERN$8) return {
|
|
63423
64848
|
category: "mcp",
|
|
63424
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64849
|
+
pattern: CATCH_ALL_PATTERN$8
|
|
63425
64850
|
};
|
|
63426
64851
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
63427
64852
|
return {
|
|
63428
64853
|
category: `${MCP_CANONICAL_PREFIX$3}${address}`,
|
|
63429
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64854
|
+
pattern: CATCH_ALL_PATTERN$8
|
|
63430
64855
|
};
|
|
63431
64856
|
}
|
|
63432
64857
|
/**
|
|
@@ -63447,20 +64872,20 @@ function parseGrokRule(rule) {
|
|
|
63447
64872
|
if (typeof tool !== "string") return null;
|
|
63448
64873
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
63449
64874
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
63450
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
64875
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$8;
|
|
63451
64876
|
const lowerTool = tool.trim().toLowerCase();
|
|
63452
|
-
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
64877
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$8 ? {
|
|
63453
64878
|
category: "*",
|
|
63454
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64879
|
+
pattern: CATCH_ALL_PATTERN$8,
|
|
63455
64880
|
action
|
|
63456
64881
|
} : null;
|
|
63457
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
64882
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$8 ? {
|
|
63458
64883
|
category: "mcp",
|
|
63459
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64884
|
+
pattern: CATCH_ALL_PATTERN$8,
|
|
63460
64885
|
action
|
|
63461
64886
|
} : {
|
|
63462
64887
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
63463
|
-
pattern: CATCH_ALL_PATTERN$
|
|
64888
|
+
pattern: CATCH_ALL_PATTERN$8,
|
|
63464
64889
|
action
|
|
63465
64890
|
};
|
|
63466
64891
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -63613,7 +65038,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
63613
65038
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
63614
65039
|
}
|
|
63615
65040
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
63616
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
65041
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$8]: legacyModeAction(parsed) } } };
|
|
63617
65042
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
63618
65043
|
}
|
|
63619
65044
|
validate() {
|
|
@@ -63633,7 +65058,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
63633
65058
|
});
|
|
63634
65059
|
}
|
|
63635
65060
|
};
|
|
63636
|
-
const ACTION_RANK$
|
|
65061
|
+
const ACTION_RANK$4 = {
|
|
63637
65062
|
allow: 0,
|
|
63638
65063
|
ask: 1,
|
|
63639
65064
|
deny: 2
|
|
@@ -63668,7 +65093,7 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
63668
65093
|
}
|
|
63669
65094
|
const existing = ranked.get(entry);
|
|
63670
65095
|
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).`);
|
|
63671
|
-
if (existing === void 0 || ACTION_RANK$
|
|
65096
|
+
if (existing === void 0 || ACTION_RANK$4[action] > ACTION_RANK$4[existing]) ranked.set(entry, action);
|
|
63672
65097
|
}
|
|
63673
65098
|
warnRemovedAllToolsRestrictions({
|
|
63674
65099
|
existingPermission,
|
|
@@ -63704,7 +65129,7 @@ function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger })
|
|
|
63704
65129
|
const entries = existingPermission[action];
|
|
63705
65130
|
if (!isStringArray$1(entries)) continue;
|
|
63706
65131
|
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63707
|
-
if (generated !== void 0 && ACTION_RANK$
|
|
65132
|
+
if (generated !== void 0 && ACTION_RANK$4[generated] >= ACTION_RANK$4[action]) continue;
|
|
63708
65133
|
logger.warn(`Grok CLI permissions: removing the existing bare '*' entry from the '${action}' array — Rulesync now manages it through the all-tools '*' category, and the canonical config has no '*' '${action}' rule. Add "*": { "*": "${action}" } to keep restricting every tool.`);
|
|
63709
65134
|
}
|
|
63710
65135
|
}
|
|
@@ -63719,7 +65144,7 @@ function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
|
63719
65144
|
if (!logger) return;
|
|
63720
65145
|
const allTools = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63721
65146
|
if (allTools === void 0 || allTools === "allow") return;
|
|
63722
|
-
const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$
|
|
65147
|
+
const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$4[action] < ACTION_RANK$4[allTools]).map(([entry]) => entry).toSorted();
|
|
63723
65148
|
if (shadowed.length === 0) return;
|
|
63724
65149
|
logger.warn(`Grok CLI permissions: the bare '*' '${allTools}' rule applies to every tool, and Grok evaluates deny > ask > allow, so these less strict entries never take effect: ${shadowed.join(", ")}.`);
|
|
63725
65150
|
}
|
|
@@ -63742,7 +65167,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
63742
65167
|
const record = ({ category, pattern, action }) => {
|
|
63743
65168
|
const bucket = result[category] ??= {};
|
|
63744
65169
|
const existing = bucket[pattern];
|
|
63745
|
-
if (existing === void 0 || ACTION_RANK$
|
|
65170
|
+
if (existing === void 0 || ACTION_RANK$4[action] > ACTION_RANK$4[existing]) bucket[pattern] = action;
|
|
63746
65171
|
};
|
|
63747
65172
|
const applyEntries = (entries, action) => {
|
|
63748
65173
|
for (const entry of entries ?? []) {
|
|
@@ -64146,7 +65571,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
64146
65571
|
mcpTools: "mcp",
|
|
64147
65572
|
readOutsideProject: "read"
|
|
64148
65573
|
};
|
|
64149
|
-
function isPermissionAction$
|
|
65574
|
+
function isPermissionAction$3(value) {
|
|
64150
65575
|
return PermissionActionSchema.safeParse(value).success;
|
|
64151
65576
|
}
|
|
64152
65577
|
/**
|
|
@@ -64332,7 +65757,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
|
64332
65757
|
for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
|
|
64333
65758
|
if (!rule || typeof rule !== "object") continue;
|
|
64334
65759
|
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
64335
|
-
if (pattern === void 0 || !isPermissionAction$
|
|
65760
|
+
if (pattern === void 0 || !isPermissionAction$3(rule.action)) continue;
|
|
64336
65761
|
(permission[category] ??= {})[pattern] = rule.action;
|
|
64337
65762
|
}
|
|
64338
65763
|
}
|
|
@@ -65292,7 +66717,7 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
65292
66717
|
}
|
|
65293
66718
|
//#endregion
|
|
65294
66719
|
//#region src/features/permissions/kiro-capability-permissions.ts
|
|
65295
|
-
const CATCH_ALL_PATTERN$
|
|
66720
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
65296
66721
|
const CANONICAL_TO_KIRO_CAPABILITY = {
|
|
65297
66722
|
bash: "shell",
|
|
65298
66723
|
read: "fs_read",
|
|
@@ -65309,12 +66734,12 @@ const KIRO_CAPABILITY_TO_CANONICAL = {
|
|
|
65309
66734
|
web_search: "websearch"
|
|
65310
66735
|
};
|
|
65311
66736
|
const MANAGED_CAPABILITIES = new Set(Object.keys(KIRO_CAPABILITY_TO_CANONICAL));
|
|
65312
|
-
const ACTION_RANK$
|
|
66737
|
+
const ACTION_RANK$3 = {
|
|
65313
66738
|
allow: 0,
|
|
65314
66739
|
ask: 1,
|
|
65315
66740
|
deny: 2
|
|
65316
66741
|
};
|
|
65317
|
-
function isPermissionAction$
|
|
66742
|
+
function isPermissionAction$2(value) {
|
|
65318
66743
|
return value === "allow" || value === "ask" || value === "deny";
|
|
65319
66744
|
}
|
|
65320
66745
|
/**
|
|
@@ -65341,7 +66766,7 @@ function rulePatternKey(capability, pattern) {
|
|
|
65341
66766
|
function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
65342
66767
|
const effects = emitted.get(rulePatternKey(capability, pattern));
|
|
65343
66768
|
if (effects === void 0) return "unrelated";
|
|
65344
|
-
return isPermissionAction$
|
|
66769
|
+
return isPermissionAction$2(effect) && effects.has(effect) ? "duplicate" : "conflict";
|
|
65345
66770
|
}
|
|
65346
66771
|
/**
|
|
65347
66772
|
* Merge one existing rule against the generated entries (see
|
|
@@ -65356,7 +66781,7 @@ function mergeExistingRule({ rule, emitted, logger }) {
|
|
|
65356
66781
|
};
|
|
65357
66782
|
const capability = rule.capability;
|
|
65358
66783
|
const isCatchAll = rule.match === void 0;
|
|
65359
|
-
const patterns = isStringArray$1(rule.match) ? rule.match : [CATCH_ALL_PATTERN$
|
|
66784
|
+
const patterns = isStringArray$1(rule.match) ? rule.match : [CATCH_ALL_PATTERN$7];
|
|
65360
66785
|
if (patterns.length === 0) return {
|
|
65361
66786
|
rule,
|
|
65362
66787
|
unrelatedEntries: 0
|
|
@@ -65410,7 +66835,7 @@ function mergeExistingRule({ rule, emitted, logger }) {
|
|
|
65410
66835
|
*/
|
|
65411
66836
|
function mergeKiroCapabilityRules({ existingRules, generatedRules, logger }) {
|
|
65412
66837
|
const emitted = /* @__PURE__ */ new Map();
|
|
65413
|
-
for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$
|
|
66838
|
+
for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$7]) {
|
|
65414
66839
|
const key = rulePatternKey(rule.capability, pattern);
|
|
65415
66840
|
const effects = emitted.get(key) ?? /* @__PURE__ */ new Set();
|
|
65416
66841
|
effects.add(rule.effect);
|
|
@@ -65538,13 +66963,13 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
|
|
|
65538
66963
|
const rules = isRecord$1(parsed) && Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
65539
66964
|
const permission = {};
|
|
65540
66965
|
for (const rule of rules) {
|
|
65541
|
-
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$
|
|
66966
|
+
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$2(rule.effect)) continue;
|
|
65542
66967
|
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
65543
|
-
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$
|
|
66968
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$7] : isStringArray$1(rule.match) ? rule.match : [];
|
|
65544
66969
|
const categoryRules = permission[category] ??= {};
|
|
65545
66970
|
for (const pattern of patterns) {
|
|
65546
66971
|
const previous = categoryRules[pattern];
|
|
65547
|
-
if (previous === void 0 || ACTION_RANK$
|
|
66972
|
+
if (previous === void 0 || ACTION_RANK$3[rule.effect] > ACTION_RANK$3[previous]) categoryRules[pattern] = rule.effect;
|
|
65548
66973
|
}
|
|
65549
66974
|
}
|
|
65550
66975
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
@@ -65598,11 +67023,11 @@ function buildKiroCapabilityRules({ config, logger }) {
|
|
|
65598
67023
|
}
|
|
65599
67024
|
const result = [];
|
|
65600
67025
|
for (const { capability, effect, patterns } of buckets.values()) {
|
|
65601
|
-
if (patterns.has(CATCH_ALL_PATTERN$
|
|
67026
|
+
if (patterns.has(CATCH_ALL_PATTERN$7)) result.push({
|
|
65602
67027
|
capability,
|
|
65603
67028
|
effect
|
|
65604
67029
|
});
|
|
65605
|
-
const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$
|
|
67030
|
+
const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$7);
|
|
65606
67031
|
if (match.length > 0) result.push({
|
|
65607
67032
|
capability,
|
|
65608
67033
|
match,
|
|
@@ -65627,7 +67052,7 @@ function serializeKiroPermissions({ next, parsed, existingContent }) {
|
|
|
65627
67052
|
*/
|
|
65628
67053
|
function warnOnWorkspaceRelativeFsPattern({ capability, category, pattern, logger }) {
|
|
65629
67054
|
if (capability !== "fs_read" && capability !== "fs_write") return;
|
|
65630
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
67055
|
+
if (pattern === CATCH_ALL_PATTERN$7 || LOCATION_INDEPENDENT_FS_PATTERN.test(pattern)) return;
|
|
65631
67056
|
logger?.warn(`Kiro permissions.yaml is user-level and applies to every workspace, but the ${category} pattern "${pattern}" is relative. Use an absolute, ~-relative or **-prefixed pattern to target a specific location.`);
|
|
65632
67057
|
}
|
|
65633
67058
|
/**
|
|
@@ -65655,7 +67080,7 @@ const KiroScopedPermissions = {
|
|
|
65655
67080
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
65656
67081
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
65657
67082
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
65658
|
-
const CATCH_ALL_PATTERN$
|
|
67083
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
65659
67084
|
/** Letta Code's `Bash(prefix:*)` prefix-match suffix. */
|
|
65660
67085
|
const PREFIX_SUFFIX = ":*";
|
|
65661
67086
|
const CATEGORY_TO_LETTACODE_TOOL = {
|
|
@@ -65700,12 +67125,12 @@ const LIST_TO_ACTION = {
|
|
|
65700
67125
|
alwaysAsk: "ask",
|
|
65701
67126
|
deny: "deny"
|
|
65702
67127
|
};
|
|
65703
|
-
const ACTION_RANK$
|
|
67128
|
+
const ACTION_RANK$2 = {
|
|
65704
67129
|
allow: 0,
|
|
65705
67130
|
ask: 1,
|
|
65706
67131
|
deny: 2
|
|
65707
67132
|
};
|
|
65708
|
-
function toStringList$
|
|
67133
|
+
function toStringList$2(value) {
|
|
65709
67134
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
65710
67135
|
}
|
|
65711
67136
|
/**
|
|
@@ -65748,7 +67173,7 @@ function categoryOfRule$1(rule) {
|
|
|
65748
67173
|
* widening it would grant commands the source never allowed.
|
|
65749
67174
|
*/
|
|
65750
67175
|
function toLettacodeBashPayload({ pattern, action, logger }) {
|
|
65751
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
67176
|
+
if (pattern === CATCH_ALL_PATTERN$6) return "";
|
|
65752
67177
|
const firstStar = pattern.indexOf("*");
|
|
65753
67178
|
if (firstStar === -1) return pattern;
|
|
65754
67179
|
if (pattern.endsWith(PREFIX_SUFFIX) && firstStar === pattern.length - 1) return pattern;
|
|
@@ -65769,7 +67194,7 @@ function toLettacodeRule({ category, pattern, action, logger }) {
|
|
|
65769
67194
|
pattern,
|
|
65770
67195
|
action,
|
|
65771
67196
|
logger
|
|
65772
|
-
}) : pattern === CATCH_ALL_PATTERN$
|
|
67197
|
+
}) : pattern === CATCH_ALL_PATTERN$6 ? "" : pattern;
|
|
65773
67198
|
if (payload === null) return null;
|
|
65774
67199
|
return payload === "" ? tool : `${tool}(${payload})`;
|
|
65775
67200
|
}
|
|
@@ -65780,7 +67205,7 @@ function fromLettacodeRule(rule) {
|
|
|
65780
67205
|
const { payload } = splitLettacodeRule(rule);
|
|
65781
67206
|
if (payload === void 0) return {
|
|
65782
67207
|
category,
|
|
65783
|
-
pattern: CATCH_ALL_PATTERN$
|
|
67208
|
+
pattern: CATCH_ALL_PATTERN$6
|
|
65784
67209
|
};
|
|
65785
67210
|
const trimmed = payload.trim();
|
|
65786
67211
|
if (trimmed === "") return null;
|
|
@@ -65788,7 +67213,7 @@ function fromLettacodeRule(rule) {
|
|
|
65788
67213
|
const prefix = trimmed.slice(0, -2).trimEnd();
|
|
65789
67214
|
return {
|
|
65790
67215
|
category,
|
|
65791
|
-
pattern: prefix === "" ? CATCH_ALL_PATTERN$
|
|
67216
|
+
pattern: prefix === "" ? CATCH_ALL_PATTERN$6 : `${prefix} *`
|
|
65792
67217
|
};
|
|
65793
67218
|
}
|
|
65794
67219
|
return {
|
|
@@ -65823,11 +67248,11 @@ function buildLettacodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
65823
67248
|
});
|
|
65824
67249
|
if (rule === null) continue;
|
|
65825
67250
|
const existing = ranked.get(rule);
|
|
65826
|
-
if (existing === void 0 || ACTION_RANK$
|
|
67251
|
+
if (existing === void 0 || ACTION_RANK$2[action] > ACTION_RANK$2[existing]) ranked.set(rule, action);
|
|
65827
67252
|
}
|
|
65828
67253
|
}
|
|
65829
67254
|
const lists = {};
|
|
65830
|
-
for (const key of Object.keys(LIST_TO_ACTION)) lists[key] = toStringList$
|
|
67255
|
+
for (const key of Object.keys(LIST_TO_ACTION)) lists[key] = toStringList$2(existingPermissions[key]).filter((rule) => {
|
|
65831
67256
|
const category = categoryOfRule$1(rule);
|
|
65832
67257
|
return category === void 0 || !managedCategories.has(category);
|
|
65833
67258
|
});
|
|
@@ -65916,12 +67341,12 @@ var LettacodePermissions = class LettacodePermissions extends ToolPermissions {
|
|
|
65916
67341
|
});
|
|
65917
67342
|
const permissions = isRecord$1(settings[LETTACODE_PERMISSIONS_KEY]) ? settings[LETTACODE_PERMISSIONS_KEY] : {};
|
|
65918
67343
|
const permission = {};
|
|
65919
|
-
for (const [key, action] of Object.entries(LIST_TO_ACTION)) for (const rule of toStringList$
|
|
67344
|
+
for (const [key, action] of Object.entries(LIST_TO_ACTION)) for (const rule of toStringList$2(permissions[key])) {
|
|
65920
67345
|
const parsed = fromLettacodeRule(rule);
|
|
65921
67346
|
if (parsed === null || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
65922
67347
|
const rules = permission[parsed.category] ??= {};
|
|
65923
67348
|
const existing = rules[parsed.pattern];
|
|
65924
|
-
if (existing === void 0 || ACTION_RANK$
|
|
67349
|
+
if (existing === void 0 || ACTION_RANK$2[action] > ACTION_RANK$2[existing]) rules[parsed.pattern] = action;
|
|
65925
67350
|
}
|
|
65926
67351
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
65927
67352
|
}
|
|
@@ -66105,7 +67530,11 @@ var OpencodePermissions = class extends ToolPermissions {
|
|
|
66105
67530
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
66106
67531
|
if (fileContent) relativeFilePath = this.layout.jsonFileName;
|
|
66107
67532
|
}
|
|
66108
|
-
const parsed =
|
|
67533
|
+
const parsed = (await readOpencodeDotDirConfigLayers({
|
|
67534
|
+
outputRoot,
|
|
67535
|
+
global,
|
|
67536
|
+
layout: this.layout
|
|
67537
|
+
})).map(({ config }) => config).reduce(mergeOpencodeConfigValues, parse$1(fileContent ?? "{}"));
|
|
66109
67538
|
const record = isRecord$1(parsed) ? parsed : {};
|
|
66110
67539
|
const nextJson = {
|
|
66111
67540
|
...record,
|
|
@@ -66156,6 +67585,16 @@ var OpencodePermissions = class extends ToolPermissions {
|
|
|
66156
67585
|
value,
|
|
66157
67586
|
logger
|
|
66158
67587
|
});
|
|
67588
|
+
warnOpencodeDotDirOverrides({
|
|
67589
|
+
generated: { permission },
|
|
67590
|
+
overlays: await readOpencodeDotDirConfigLayers({
|
|
67591
|
+
outputRoot,
|
|
67592
|
+
global,
|
|
67593
|
+
layout: this.layout
|
|
67594
|
+
}),
|
|
67595
|
+
rootFileName: relativeFilePath,
|
|
67596
|
+
logger
|
|
67597
|
+
});
|
|
66159
67598
|
return new this({
|
|
66160
67599
|
outputRoot,
|
|
66161
67600
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -66245,7 +67684,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
66245
67684
|
};
|
|
66246
67685
|
//#endregion
|
|
66247
67686
|
//#region src/features/permissions/omp-permissions.ts
|
|
66248
|
-
const CATCH_ALL_PATTERN$
|
|
67687
|
+
const CATCH_ALL_PATTERN$5 = "*";
|
|
66249
67688
|
const APPROVAL_MODE_KEY = "approvalMode";
|
|
66250
67689
|
const ACTION_TO_OMP_APPROVAL = {
|
|
66251
67690
|
allow: "allow",
|
|
@@ -66442,7 +67881,7 @@ function allToolsFloor(rules) {
|
|
|
66442
67881
|
let patternSpecific = false;
|
|
66443
67882
|
for (const [pattern, action] of Object.entries(rules ?? {})) {
|
|
66444
67883
|
if (action === "allow" || isPrototypePollutionKey(pattern)) continue;
|
|
66445
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
67884
|
+
if (pattern === CATCH_ALL_PATTERN$5) floor = stricter(floor, action);
|
|
66446
67885
|
else {
|
|
66447
67886
|
patternSpecific = true;
|
|
66448
67887
|
floor = stricter(floor, "ask");
|
|
@@ -66477,11 +67916,11 @@ function convertBashRules$1({ rules, logger }) {
|
|
|
66477
67916
|
const entries = Object.entries(rules).filter(([pattern]) => !isPrototypePollutionKey(pattern));
|
|
66478
67917
|
const hasAsk = entries.some(([, action]) => action === "ask");
|
|
66479
67918
|
let policy = hasAsk ? "ask" : void 0;
|
|
66480
|
-
if (hasAsk && rules[CATCH_ALL_PATTERN$
|
|
67919
|
+
if (hasAsk && rules[CATCH_ALL_PATTERN$5] !== "ask") logger?.warn("oh-my-pi lets a critical command (such as `rm -rf /`) skip a bash prompt pattern unless the bash tool policy prompts, so `tools.approval.bash` was set to \"prompt\": a command no bash allow pattern matches now prompts in every approval mode.");
|
|
66481
67920
|
for (const [pattern, action] of entries) {
|
|
66482
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
67921
|
+
if (pattern === CATCH_ALL_PATTERN$5 && action === "allow") {
|
|
66483
67922
|
if (hasAsk) patterns.push({
|
|
66484
|
-
match: CATCH_ALL_PATTERN$
|
|
67923
|
+
match: CATCH_ALL_PATTERN$5,
|
|
66485
67924
|
approval: "allow"
|
|
66486
67925
|
});
|
|
66487
67926
|
else policy = "allow";
|
|
@@ -66584,12 +68023,12 @@ function toOmpApproval(value) {
|
|
|
66584
68023
|
* ask rules (see `convertBashRules`), so it adds no catch-all.
|
|
66585
68024
|
*/
|
|
66586
68025
|
function importBashPolicy(bashRules, action) {
|
|
66587
|
-
const catchAll = bashRules[CATCH_ALL_PATTERN$
|
|
68026
|
+
const catchAll = bashRules[CATCH_ALL_PATTERN$5];
|
|
66588
68027
|
const hasAskPattern = Object.values(bashRules).includes("ask");
|
|
66589
|
-
if (action === "deny") bashRules[CATCH_ALL_PATTERN$
|
|
68028
|
+
if (action === "deny") bashRules[CATCH_ALL_PATTERN$5] = "deny";
|
|
66590
68029
|
else if (catchAll === void 0) {
|
|
66591
|
-
if (action !== void 0 && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$
|
|
66592
|
-
} else if (catchAll === "allow" && action !== "allow" && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$
|
|
68030
|
+
if (action !== void 0 && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$5] = action;
|
|
68031
|
+
} else if (catchAll === "allow" && action !== "allow" && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$5] = "ask";
|
|
66593
68032
|
}
|
|
66594
68033
|
/** `bash.patterns` as canonical `bash` rules, in first-match order. */
|
|
66595
68034
|
function importBashPatterns(value) {
|
|
@@ -66617,7 +68056,7 @@ function convertOmpToRulesync(config) {
|
|
|
66617
68056
|
const action = OMP_APPROVAL_TO_ACTION[approval];
|
|
66618
68057
|
if (tool === "bash") continue;
|
|
66619
68058
|
const category = renamed(OMP_TOOL_TO_CATEGORY, tool);
|
|
66620
|
-
permission[category] = { [CATCH_ALL_PATTERN$
|
|
68059
|
+
permission[category] = { [CATCH_ALL_PATTERN$5]: stricter(permission[category]?.[CATCH_ALL_PATTERN$5], action) };
|
|
66621
68060
|
}
|
|
66622
68061
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
66623
68062
|
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
@@ -66747,7 +68186,7 @@ const POOL_DENY_KEY = "deny";
|
|
|
66747
68186
|
const POOL_DISABLED_KEY = "disabled";
|
|
66748
68187
|
const POOL_PATH_KEY = "path";
|
|
66749
68188
|
const POOL_WRITE_KEY = "write";
|
|
66750
|
-
const CATCH_ALL_PATTERN$
|
|
68189
|
+
const CATCH_ALL_PATTERN$4 = "*";
|
|
66751
68190
|
const PATHS_CATCH_ALL_PATTERN = "**";
|
|
66752
68191
|
const CANONICAL_TO_POOL_TOOL_NAMES = {
|
|
66753
68192
|
bash: "shell",
|
|
@@ -67255,7 +68694,7 @@ function pathOfEntry(entry) {
|
|
|
67255
68694
|
*/
|
|
67256
68695
|
function importPoolTool({ toolConfig, bucket }) {
|
|
67257
68696
|
if (toolConfig[POOL_DISABLED_KEY] === true) {
|
|
67258
|
-
bucket[CATCH_ALL_PATTERN$
|
|
68697
|
+
bucket[CATCH_ALL_PATTERN$4] = "deny";
|
|
67259
68698
|
return;
|
|
67260
68699
|
}
|
|
67261
68700
|
const denied = isStringArray$1(toolConfig[POOL_DENY_KEY]) ? toolConfig[POOL_DENY_KEY] : [];
|
|
@@ -67312,9 +68751,58 @@ const QODER_PERMISSIONS_KEY = "permissions";
|
|
|
67312
68751
|
* @see https://docs.qoder.com/cli/settings-reference
|
|
67313
68752
|
*/
|
|
67314
68753
|
const QODER_OUTPUT_STYLE_KEY = "outputStyle";
|
|
67315
|
-
|
|
68754
|
+
/**
|
|
68755
|
+
* The settings groups the `qoder` override may author, each with the only keys
|
|
68756
|
+
* of that group rulesync writes and imports. A group's other keys are kept as
|
|
68757
|
+
* the file has them; an override key not listed here is skipped with a
|
|
68758
|
+
* warning, so a shared permissions file cannot reach `tools.shell.pager`,
|
|
68759
|
+
* `general.preferredEditor` and the like.
|
|
68760
|
+
* @see https://docs.qoder.com/cli/settings-reference
|
|
68761
|
+
*/
|
|
68762
|
+
const QODER_OVERRIDE_GROUP_KEYS = {
|
|
68763
|
+
general: ["defaultPermissionMode"],
|
|
68764
|
+
tools: ["core", "exclude"],
|
|
68765
|
+
mcp: [
|
|
68766
|
+
"allowed",
|
|
68767
|
+
"excluded",
|
|
68768
|
+
"enableAllProjectMcpServers",
|
|
68769
|
+
"enabledProjectMcpServers",
|
|
68770
|
+
"lazyLoad"
|
|
68771
|
+
],
|
|
68772
|
+
agents: ["overrides"]
|
|
68773
|
+
};
|
|
68774
|
+
const QODER_OVERRIDE_GROUPS = Object.keys(QODER_OVERRIDE_GROUP_KEYS);
|
|
68775
|
+
/**
|
|
68776
|
+
* Top-level Auto-mode classifier guidance. Qoder reads it from the user
|
|
68777
|
+
* settings only (a workspace file could otherwise bias the classifier), so it
|
|
68778
|
+
* is written and imported in global mode alone.
|
|
68779
|
+
* @see https://docs.qoder.com/en/cli/permissions
|
|
68780
|
+
*/
|
|
68781
|
+
const QODER_AUTO_MODE_KEY = "autoMode";
|
|
68782
|
+
/** The `autoMode` keys rulesync writes and imports. */
|
|
68783
|
+
const QODER_AUTO_MODE_KEYS = [
|
|
68784
|
+
"allow",
|
|
68785
|
+
"soft_deny",
|
|
68786
|
+
"environment"
|
|
68787
|
+
];
|
|
68788
|
+
/** Top-level globs that keep project/local memory files from loading. */
|
|
68789
|
+
const QODER_AGENTS_MD_EXCLUDES_KEY = "agentsMdExcludes";
|
|
68790
|
+
/**
|
|
68791
|
+
* `general.defaultPermissionMode` values (lower-cased; Qoder matches them
|
|
68792
|
+
* case-insensitively) that let actions run without a prompt.
|
|
68793
|
+
* @see https://docs.qoder.com/en/cli/permissions
|
|
68794
|
+
*/
|
|
68795
|
+
const QODER_PERMISSIVE_MODES = /* @__PURE__ */ new Set([
|
|
68796
|
+
"accept_edits",
|
|
68797
|
+
"acceptedits",
|
|
68798
|
+
"auto",
|
|
68799
|
+
"bypass_permissions",
|
|
68800
|
+
"bypasspermissions",
|
|
68801
|
+
"yolo"
|
|
68802
|
+
]);
|
|
68803
|
+
const CATCH_ALL_PATTERN$3 = "*";
|
|
67316
68804
|
/** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
|
|
67317
|
-
const MCP_TOOL_PREFIX = "mcp__";
|
|
68805
|
+
const MCP_TOOL_PREFIX$1 = "mcp__";
|
|
67318
68806
|
const CATEGORY_TO_QODER_TOOL = {
|
|
67319
68807
|
bash: "Bash",
|
|
67320
68808
|
read: "Read",
|
|
@@ -67344,12 +68832,12 @@ const ACTIONS = [
|
|
|
67344
68832
|
"ask",
|
|
67345
68833
|
"deny"
|
|
67346
68834
|
];
|
|
67347
|
-
const ACTION_RANK = {
|
|
68835
|
+
const ACTION_RANK$1 = {
|
|
67348
68836
|
allow: 0,
|
|
67349
68837
|
ask: 1,
|
|
67350
68838
|
deny: 2
|
|
67351
68839
|
};
|
|
67352
|
-
function toStringList(value) {
|
|
68840
|
+
function toStringList$1(value) {
|
|
67353
68841
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
67354
68842
|
}
|
|
67355
68843
|
/**
|
|
@@ -67371,7 +68859,7 @@ function splitQoderRule(rule) {
|
|
|
67371
68859
|
function categoryOfRule(rule) {
|
|
67372
68860
|
const { tool } = splitQoderRule(rule);
|
|
67373
68861
|
if (tool === null) return;
|
|
67374
|
-
if (tool === "*" || tool.startsWith(MCP_TOOL_PREFIX)) return tool;
|
|
68862
|
+
if (tool === "*" || tool.startsWith(MCP_TOOL_PREFIX$1)) return tool;
|
|
67375
68863
|
return Object.hasOwn(QODER_TOOL_TO_CATEGORY, tool) ? QODER_TOOL_TO_CATEGORY[tool] : void 0;
|
|
67376
68864
|
}
|
|
67377
68865
|
/**
|
|
@@ -67390,18 +68878,18 @@ function categoryOfRule(rule) {
|
|
|
67390
68878
|
*/
|
|
67391
68879
|
function toQoderRule({ category, pattern, logger }) {
|
|
67392
68880
|
if (category === "*") {
|
|
67393
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
68881
|
+
if (pattern === CATCH_ALL_PATTERN$3) return CATCH_ALL_PATTERN$3;
|
|
67394
68882
|
logger?.warn(`Qoder permissions: skipping the all-tools '*' rule '${pattern}' — a Qoder rule with a payload names one tool. Write it under the categories it is meant for (for example 'bash' or 'read').`);
|
|
67395
68883
|
return null;
|
|
67396
68884
|
}
|
|
67397
|
-
if (category.startsWith(MCP_TOOL_PREFIX)) {
|
|
67398
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
68885
|
+
if (category.startsWith(MCP_TOOL_PREFIX$1)) {
|
|
68886
|
+
if (pattern === CATCH_ALL_PATTERN$3) return category;
|
|
67399
68887
|
logger?.warn(`Qoder permissions: skipping the '${category}' rule '${pattern}' — Qoder matches MCP tools by name only.`);
|
|
67400
68888
|
return null;
|
|
67401
68889
|
}
|
|
67402
68890
|
const tool = CATEGORY_TO_QODER_TOOL[category];
|
|
67403
68891
|
if (tool === void 0) return null;
|
|
67404
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
68892
|
+
if (pattern === CATCH_ALL_PATTERN$3) return tool;
|
|
67405
68893
|
return `${QODER_PATH_RULE_ALIASES[tool] ?? tool}(${pattern})`;
|
|
67406
68894
|
}
|
|
67407
68895
|
/** Read a Qoder rule back into a canonical category and pattern. */
|
|
@@ -67411,9 +68899,9 @@ function fromQoderRule(rule) {
|
|
|
67411
68899
|
const { payload } = splitQoderRule(rule);
|
|
67412
68900
|
if (payload === void 0) return {
|
|
67413
68901
|
category,
|
|
67414
|
-
pattern: CATCH_ALL_PATTERN$
|
|
68902
|
+
pattern: CATCH_ALL_PATTERN$3
|
|
67415
68903
|
};
|
|
67416
|
-
if (category === "*" || category.startsWith(MCP_TOOL_PREFIX)) return null;
|
|
68904
|
+
if (category === "*" || category.startsWith(MCP_TOOL_PREFIX$1)) return null;
|
|
67417
68905
|
const trimmed = payload.trim();
|
|
67418
68906
|
if (trimmed === "") return null;
|
|
67419
68907
|
return {
|
|
@@ -67433,7 +68921,7 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
67433
68921
|
const ranked = /* @__PURE__ */ new Map();
|
|
67434
68922
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
67435
68923
|
for (const [category, rules] of Object.entries(permission)) {
|
|
67436
|
-
if (category !== "*" && !category.startsWith(MCP_TOOL_PREFIX) && !Object.hasOwn(CATEGORY_TO_QODER_TOOL, category)) {
|
|
68924
|
+
if (category !== "*" && !category.startsWith(MCP_TOOL_PREFIX$1) && !Object.hasOwn(CATEGORY_TO_QODER_TOOL, category)) {
|
|
67437
68925
|
logger?.warn(`Qoder permissions: skipping the '${category}' category — Qoder has no permission rule for it.`);
|
|
67438
68926
|
continue;
|
|
67439
68927
|
}
|
|
@@ -67447,11 +68935,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
67447
68935
|
});
|
|
67448
68936
|
if (rule === null) continue;
|
|
67449
68937
|
const existing = ranked.get(rule);
|
|
67450
|
-
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) ranked.set(rule, action);
|
|
68938
|
+
if (existing === void 0 || ACTION_RANK$1[action] > ACTION_RANK$1[existing]) ranked.set(rule, action);
|
|
67451
68939
|
}
|
|
67452
68940
|
}
|
|
67453
68941
|
const lists = {};
|
|
67454
|
-
for (const action of ACTIONS) lists[action] = toStringList(existingPermissions[action]).filter((rule) => {
|
|
68942
|
+
for (const action of ACTIONS) lists[action] = toStringList$1(existingPermissions[action]).filter((rule) => {
|
|
67455
68943
|
if (ranked.has(rule)) return false;
|
|
67456
68944
|
const category = categoryOfRule(rule);
|
|
67457
68945
|
return category === void 0 || !managedCategories.has(category);
|
|
@@ -67461,6 +68949,130 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
67461
68949
|
return lists;
|
|
67462
68950
|
}
|
|
67463
68951
|
/**
|
|
68952
|
+
* The keys of one authored override group that rulesync writes; any other key
|
|
68953
|
+
* is skipped with a warning.
|
|
68954
|
+
*/
|
|
68955
|
+
function pickQoderGroupKeys({ group, allowedKeys, authored, where, logger }) {
|
|
68956
|
+
const picked = {};
|
|
68957
|
+
for (const [key, value] of Object.entries(authored)) {
|
|
68958
|
+
if (isPrototypePollutionKey(key) || value === void 0) continue;
|
|
68959
|
+
if (!allowedKeys.includes(key)) {
|
|
68960
|
+
logger?.warn(`Qoder permissions: skipping qoder.${group}.${key} — Rulesync writes only ${allowedKeys.map((k) => `${group}.${k}`).join(", ")} from the qoder override; set other keys in ${where} by hand.`);
|
|
68961
|
+
continue;
|
|
68962
|
+
}
|
|
68963
|
+
picked[key] = value;
|
|
68964
|
+
}
|
|
68965
|
+
return picked;
|
|
68966
|
+
}
|
|
68967
|
+
/** Name every written override setting that lets Qoder act with fewer prompts. */
|
|
68968
|
+
function announcePermissiveQoderSettings({ patch, where, logger }) {
|
|
68969
|
+
const mode = (isRecord$1(patch.general) ? patch.general : {}).defaultPermissionMode;
|
|
68970
|
+
if (typeof mode === "string" && QODER_PERMISSIVE_MODES.has(mode.trim().toLowerCase())) logger?.warn(`Qoder permissions: qoder.general.defaultPermissionMode = ${JSON.stringify(mode)} was written to ${where}, so Qoder starts every session it applies to in a mode that runs actions without asking first.`);
|
|
68971
|
+
const mcp = isRecord$1(patch.mcp) ? patch.mcp : {};
|
|
68972
|
+
if (mcp.enableAllProjectMcpServers === true) logger?.warn(`Qoder permissions: qoder.mcp.enableAllProjectMcpServers = true was written to ${where}, so Qoder starts every project-level MCP server without asking for approval.`);
|
|
68973
|
+
const enabledServers = toStringList$1(mcp.enabledProjectMcpServers);
|
|
68974
|
+
if (enabledServers.length > 0) logger?.warn(`Qoder permissions: qoder.mcp.enabledProjectMcpServers was written to ${where}, so Qoder starts the project-level MCP servers ${enabledServers.map((name) => JSON.stringify(name)).join(", ")} without asking for approval.`);
|
|
68975
|
+
if (toStringList$1((isRecord$1(patch[QODER_AUTO_MODE_KEY]) ? patch[QODER_AUTO_MODE_KEY] : {}).allow).length > 0) logger?.warn(`Qoder permissions: qoder.autoMode.allow was written to ${where}, so Qoder's Auto-mode classifier leans toward approving the actions it describes.`);
|
|
68976
|
+
}
|
|
68977
|
+
/**
|
|
68978
|
+
* The top-level keys the `qoder` override contributes to the settings patch,
|
|
68979
|
+
* besides `permissions` and `outputStyle`. Each group is the existing group
|
|
68980
|
+
* with the authored keys laid over it; a key is only in the patch when
|
|
68981
|
+
* authored. Every write that lets Qoder act with fewer prompts is announced.
|
|
68982
|
+
*/
|
|
68983
|
+
function buildQoderOverridePatch({ override, existing, global, filePath, logger }) {
|
|
68984
|
+
const patch = {};
|
|
68985
|
+
if (override === void 0) return patch;
|
|
68986
|
+
const where = `${filePath}${global ? " in the home directory" : ""}`;
|
|
68987
|
+
for (const group of QODER_OVERRIDE_GROUPS) {
|
|
68988
|
+
const authored = override[group];
|
|
68989
|
+
if (!isRecord$1(authored)) continue;
|
|
68990
|
+
const picked = pickQoderGroupKeys({
|
|
68991
|
+
group,
|
|
68992
|
+
allowedKeys: QODER_OVERRIDE_GROUP_KEYS[group],
|
|
68993
|
+
authored,
|
|
68994
|
+
where,
|
|
68995
|
+
logger
|
|
68996
|
+
});
|
|
68997
|
+
if (Object.keys(picked).length > 0) patch[group] = {
|
|
68998
|
+
...isRecord$1(existing[group]) ? existing[group] : {},
|
|
68999
|
+
...picked
|
|
69000
|
+
};
|
|
69001
|
+
}
|
|
69002
|
+
const autoMode = override[QODER_AUTO_MODE_KEY];
|
|
69003
|
+
if (isRecord$1(autoMode)) {
|
|
69004
|
+
if (global) {
|
|
69005
|
+
const picked = pickQoderGroupKeys({
|
|
69006
|
+
group: QODER_AUTO_MODE_KEY,
|
|
69007
|
+
allowedKeys: QODER_AUTO_MODE_KEYS,
|
|
69008
|
+
authored: autoMode,
|
|
69009
|
+
where,
|
|
69010
|
+
logger
|
|
69011
|
+
});
|
|
69012
|
+
if (Object.keys(picked).length > 0) {
|
|
69013
|
+
const existingAutoMode = isRecord$1(existing[QODER_AUTO_MODE_KEY]) ? existing[QODER_AUTO_MODE_KEY] : {};
|
|
69014
|
+
patch[QODER_AUTO_MODE_KEY] = {
|
|
69015
|
+
...existingAutoMode,
|
|
69016
|
+
...picked
|
|
69017
|
+
};
|
|
69018
|
+
}
|
|
69019
|
+
} else logger?.warn(`Qoder permissions: skipping qoder.autoMode in project mode — Qoder reads autoMode from the user settings only. Run 'rulesync generate --global' to write it to ~/.qoder/settings.json.`);
|
|
69020
|
+
}
|
|
69021
|
+
const excludes = override[QODER_AGENTS_MD_EXCLUDES_KEY];
|
|
69022
|
+
if (Array.isArray(excludes)) patch[QODER_AGENTS_MD_EXCLUDES_KEY] = toStringList$1(excludes);
|
|
69023
|
+
announcePermissiveQoderSettings({
|
|
69024
|
+
patch: {
|
|
69025
|
+
general: override.general,
|
|
69026
|
+
mcp: override.mcp,
|
|
69027
|
+
...global && { [QODER_AUTO_MODE_KEY]: autoMode }
|
|
69028
|
+
},
|
|
69029
|
+
where,
|
|
69030
|
+
logger
|
|
69031
|
+
});
|
|
69032
|
+
return patch;
|
|
69033
|
+
}
|
|
69034
|
+
/**
|
|
69035
|
+
* Whether a settings value has the shape the `qoder` override schema accepts
|
|
69036
|
+
* for that key, so an off-spec value is left in the file instead of making the
|
|
69037
|
+
* imported permissions file fail validation.
|
|
69038
|
+
*/
|
|
69039
|
+
function isLiftableQoderValue(key, value) {
|
|
69040
|
+
switch (key) {
|
|
69041
|
+
case "defaultPermissionMode": return typeof value === "string" && value.trim() !== "";
|
|
69042
|
+
case "enableAllProjectMcpServers":
|
|
69043
|
+
case "lazyLoad": return typeof value === "boolean";
|
|
69044
|
+
case "overrides": return isRecord$1(value) && Object.values(value).every(isRecord$1);
|
|
69045
|
+
default: return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
69046
|
+
}
|
|
69047
|
+
}
|
|
69048
|
+
/**
|
|
69049
|
+
* Lift the settings the `qoder` override authors (other than `outputStyle`)
|
|
69050
|
+
* back out of a settings file. `autoMode` is lifted only from the user file,
|
|
69051
|
+
* the one Qoder reads it from.
|
|
69052
|
+
*/
|
|
69053
|
+
function liftQoderOverride({ settings, global }) {
|
|
69054
|
+
const lifted = {};
|
|
69055
|
+
for (const group of QODER_OVERRIDE_GROUPS) {
|
|
69056
|
+
const existingGroup = settings[group];
|
|
69057
|
+
if (!isRecord$1(existingGroup)) continue;
|
|
69058
|
+
const picked = {};
|
|
69059
|
+
for (const key of QODER_OVERRIDE_GROUP_KEYS[group]) {
|
|
69060
|
+
const value = Object.hasOwn(existingGroup, key) ? existingGroup[key] : void 0;
|
|
69061
|
+
if (isLiftableQoderValue(key, value)) picked[key] = value;
|
|
69062
|
+
}
|
|
69063
|
+
if (Object.keys(picked).length > 0) lifted[group] = picked;
|
|
69064
|
+
}
|
|
69065
|
+
const autoMode = settings[QODER_AUTO_MODE_KEY];
|
|
69066
|
+
if (global && isRecord$1(autoMode)) {
|
|
69067
|
+
const picked = {};
|
|
69068
|
+
for (const key of QODER_AUTO_MODE_KEYS) if (Object.hasOwn(autoMode, key) && isLiftableQoderValue(key, autoMode[key])) picked[key] = autoMode[key];
|
|
69069
|
+
if (Object.keys(picked).length > 0) lifted[QODER_AUTO_MODE_KEY] = picked;
|
|
69070
|
+
}
|
|
69071
|
+
const excludes = settings[QODER_AGENTS_MD_EXCLUDES_KEY];
|
|
69072
|
+
if (isLiftableQoderValue(QODER_AGENTS_MD_EXCLUDES_KEY, excludes)) lifted[QODER_AGENTS_MD_EXCLUDES_KEY] = excludes;
|
|
69073
|
+
return lifted;
|
|
69074
|
+
}
|
|
69075
|
+
/**
|
|
67464
69076
|
* Qoder permissions.
|
|
67465
69077
|
*
|
|
67466
69078
|
* Rules live under the `permissions` key of `.qoder/settings.json` (project)
|
|
@@ -67472,7 +69084,9 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
67472
69084
|
* (`additionalDirectories`, `trustDirectories`) are kept, and the file is
|
|
67473
69085
|
* never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
|
|
67474
69086
|
* persist) is left to the user. The `qoder` override's `outputStyle` is
|
|
67475
|
-
* written to (and imported from) the top-level `outputStyle` key
|
|
69087
|
+
* written to (and imported from) the top-level `outputStyle` key, and its
|
|
69088
|
+
* other settings (`general`, `tools`, `mcp`, `agents`, `autoMode`,
|
|
69089
|
+
* `agentsMdExcludes`) through `buildQoderOverridePatch` / `liftQoderOverride`.
|
|
67476
69090
|
*
|
|
67477
69091
|
* @see https://docs.qoder.com/en/cli/permissions
|
|
67478
69092
|
* @see https://docs.qoder.com/cli/output-styles
|
|
@@ -67535,7 +69149,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
67535
69149
|
existingContent,
|
|
67536
69150
|
patch: {
|
|
67537
69151
|
[QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
|
|
67538
|
-
...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle }
|
|
69152
|
+
...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle },
|
|
69153
|
+
...buildQoderOverridePatch({
|
|
69154
|
+
override: config.qoder,
|
|
69155
|
+
existing,
|
|
69156
|
+
global,
|
|
69157
|
+
filePath: join(paths.relativeDirPath, paths.relativeFilePath),
|
|
69158
|
+
logger
|
|
69159
|
+
})
|
|
67539
69160
|
},
|
|
67540
69161
|
filePath
|
|
67541
69162
|
}),
|
|
@@ -67550,18 +69171,24 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
67550
69171
|
});
|
|
67551
69172
|
const permissions = isRecord$1(settings[QODER_PERMISSIONS_KEY]) ? settings[QODER_PERMISSIONS_KEY] : {};
|
|
67552
69173
|
const permission = {};
|
|
67553
|
-
for (const action of ACTIONS) for (const rule of toStringList(permissions[action])) {
|
|
69174
|
+
for (const action of ACTIONS) for (const rule of toStringList$1(permissions[action])) {
|
|
67554
69175
|
const parsed = fromQoderRule(rule);
|
|
67555
69176
|
if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
|
|
67556
69177
|
const rules = permission[parsed.category] ??= {};
|
|
67557
69178
|
const existing = rules[parsed.pattern];
|
|
67558
|
-
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
|
|
69179
|
+
if (existing === void 0 || ACTION_RANK$1[action] > ACTION_RANK$1[existing]) rules[parsed.pattern] = action;
|
|
67559
69180
|
}
|
|
67560
69181
|
const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
|
|
67561
|
-
const
|
|
69182
|
+
const qoder = {
|
|
69183
|
+
...typeof outputStyle === "string" && outputStyle.trim() !== "" && { outputStyle },
|
|
69184
|
+
...liftQoderOverride({
|
|
69185
|
+
settings,
|
|
69186
|
+
global: this.global
|
|
69187
|
+
})
|
|
69188
|
+
};
|
|
67562
69189
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
67563
69190
|
permission,
|
|
67564
|
-
...
|
|
69191
|
+
...Object.keys(qoder).length > 0 && { qoder }
|
|
67565
69192
|
}, null, 2) });
|
|
67566
69193
|
}
|
|
67567
69194
|
validate() {
|
|
@@ -68422,9 +70049,53 @@ const REASONIX_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
|
68422
70049
|
path: ["workspace_root"],
|
|
68423
70050
|
reason: "moves the root the file-writing tools and sandboxed Bash are confined to, so what they may reach is decided by this path rather than by the project directory",
|
|
68424
70051
|
widens: escapesTheProject
|
|
70052
|
+
},
|
|
70053
|
+
{
|
|
70054
|
+
path: ["allow_devices"],
|
|
70055
|
+
reason: "binds host device nodes into the Linux Bash sandbox, which otherwise sees a private /dev",
|
|
70056
|
+
widens: isNonEmptyList
|
|
70057
|
+
},
|
|
70058
|
+
{
|
|
70059
|
+
path: ["egress_unlisted"],
|
|
70060
|
+
reason: "lets sandboxed Bash reach hosts that allowed_domains does not name without asking first",
|
|
70061
|
+
widens: (value) => typeof value !== "string" || !["", "ask"].includes(value.trim().toLowerCase())
|
|
70062
|
+
},
|
|
70063
|
+
{
|
|
70064
|
+
path: ["host_authorities"],
|
|
70065
|
+
reason: "lets sandboxed Bash call host services beyond the SSH agent, such as a container daemon socket, which amounts to host-wide execution",
|
|
70066
|
+
widens: (value) => !Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.trim() !== "ssh_agent")
|
|
68425
70067
|
}
|
|
68426
70068
|
];
|
|
68427
70069
|
/**
|
|
70070
|
+
* `[sandbox]` keys Reasonix reads only from the user config. In project scope
|
|
70071
|
+
* `narrowSandbox` replaces each of them with the user's value (and reports a
|
|
70072
|
+
* project `egress_unlisted` as user-only), so a repository cannot hand itself a
|
|
70073
|
+
* device, a host service or unprompted egress. A project-scope generate skips
|
|
70074
|
+
* them with a warning, the way `applyPermissionsMode` treats `mode`.
|
|
70075
|
+
*
|
|
70076
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.44.0/internal/contract/config/project_scope.go
|
|
70077
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.44.0/internal/contract/config/config.go
|
|
70078
|
+
*/
|
|
70079
|
+
const REASONIX_USER_ONLY_SANDBOX_KEYS = [
|
|
70080
|
+
"allow_devices",
|
|
70081
|
+
"egress_unlisted",
|
|
70082
|
+
"host_authorities"
|
|
70083
|
+
];
|
|
70084
|
+
/**
|
|
70085
|
+
* Removes the user-only `[sandbox]` keys from a project-scope override, warning
|
|
70086
|
+
* once about what was skipped. The keys are left alone in the file, so whatever
|
|
70087
|
+
* it already had survives the shallow merge. Global scope keeps them all, and
|
|
70088
|
+
* a non-table override is returned as is, so the trust check still reports its
|
|
70089
|
+
* shape rather than seeing nothing.
|
|
70090
|
+
*/
|
|
70091
|
+
function dropUserOnlySandboxKeys({ sandbox, global, relativeFilePath, logger }) {
|
|
70092
|
+
if (global || !isRecord$1(sandbox)) return sandbox;
|
|
70093
|
+
const skipped = REASONIX_USER_ONLY_SANDBOX_KEYS.filter((key) => sandbox[key] !== void 0);
|
|
70094
|
+
if (skipped.length === 0) return sandbox;
|
|
70095
|
+
logger?.warn(`Reasonix permissions: skipping ${skipped.map((key) => `reasonix.sandbox.${key}`).join(", ")} for the project-scoped ${relativeFilePath}; Reasonix reads ${skipped.length === 1 ? "this key" : "these keys"} only from the user config and replaces a project value with the user's, so generate it with --global instead.`);
|
|
70096
|
+
return Object.fromEntries(Object.entries(sandbox).filter(([key]) => !skipped.includes(key)));
|
|
70097
|
+
}
|
|
70098
|
+
/**
|
|
68428
70099
|
* Whether a `workspace_root` points somewhere other than inside the project the
|
|
68429
70100
|
* generate runs in. The key moves the write confinement rather than adding to
|
|
68430
70101
|
* it, so the ordinary value — the project directory, spelled relatively — would
|
|
@@ -68661,14 +70332,20 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
68661
70332
|
}));
|
|
68662
70333
|
const patch = { permissions: mergedPermissions };
|
|
68663
70334
|
if (override?.sandbox !== void 0) {
|
|
70335
|
+
const authoredSandbox = dropUserOnlySandboxKeys({
|
|
70336
|
+
sandbox: override.sandbox,
|
|
70337
|
+
global,
|
|
70338
|
+
relativeFilePath: relativeFilePathForLog,
|
|
70339
|
+
logger
|
|
70340
|
+
});
|
|
68664
70341
|
const mergedSandbox = {
|
|
68665
70342
|
...asReasonixRecord(parsed.sandbox),
|
|
68666
|
-
...asReasonixRecord(
|
|
70343
|
+
...asReasonixRecord(authoredSandbox)
|
|
68667
70344
|
};
|
|
68668
70345
|
patch.sandbox = mergedSandbox;
|
|
68669
70346
|
trustAffecting.push(...collectSandboxOverlayEntries({
|
|
68670
70347
|
existing: parsed.sandbox,
|
|
68671
|
-
authored:
|
|
70348
|
+
authored: authoredSandbox,
|
|
68672
70349
|
merged: mergedSandbox
|
|
68673
70350
|
}));
|
|
68674
70351
|
}
|
|
@@ -68721,7 +70398,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
68721
70398
|
ask: askSplit.translated,
|
|
68722
70399
|
deny: denySplit.translated
|
|
68723
70400
|
});
|
|
68724
|
-
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
70401
|
+
const sandbox = Object.fromEntries(Object.entries(asReasonixRecord(this.toml.sandbox)).filter(([key]) => this.global || !REASONIX_USER_ONLY_SANDBOX_KEYS.includes(key)));
|
|
68725
70402
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
68726
70403
|
const reasonixOverride = {};
|
|
68727
70404
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
@@ -69027,7 +70704,7 @@ var RooPermissions = class extends ToolPermissions {
|
|
|
69027
70704
|
};
|
|
69028
70705
|
//#endregion
|
|
69029
70706
|
//#region src/features/permissions/rovodev-permissions.ts
|
|
69030
|
-
const CATCH_ALL_PATTERN$
|
|
70707
|
+
const CATCH_ALL_PATTERN$2 = "*";
|
|
69031
70708
|
const CATEGORY_TO_TOOL_KEYS = {
|
|
69032
70709
|
read: [
|
|
69033
70710
|
"open_files",
|
|
@@ -69358,7 +71035,7 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
69358
71035
|
logger
|
|
69359
71036
|
});
|
|
69360
71037
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
69361
|
-
if (category === CATCH_ALL_PATTERN$
|
|
71038
|
+
if (category === CATCH_ALL_PATTERN$2) {
|
|
69362
71039
|
const toolWideDefault = convertAllToolsRules({
|
|
69363
71040
|
rules,
|
|
69364
71041
|
logger
|
|
@@ -69377,7 +71054,7 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
69377
71054
|
continue;
|
|
69378
71055
|
}
|
|
69379
71056
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
69380
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
71057
|
+
if (pattern === CATCH_ALL_PATTERN$2) {
|
|
69381
71058
|
toolPermissions.tools ??= {};
|
|
69382
71059
|
for (const toolKey of toolKeys) toolPermissions.tools[toolKey] = strictestAction(toolPermissions.tools[toolKey], action);
|
|
69383
71060
|
continue;
|
|
@@ -69400,8 +71077,8 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
69400
71077
|
* resolution never grants more than the author asked for.
|
|
69401
71078
|
*/
|
|
69402
71079
|
function warnOnEditWriteConflict({ config, logger }) {
|
|
69403
|
-
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$
|
|
69404
|
-
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$
|
|
71080
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$2];
|
|
71081
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$2];
|
|
69405
71082
|
if (editCatchAll && writeCatchAll && editCatchAll !== writeCatchAll) logger?.warn(`Rovo Dev maps both "edit" and "write" onto the same file-mutation tools, but they have conflicting catch-all permissions ("edit": "${editCatchAll}", "write": "${writeCatchAll}"). The stricter of the two ("${strictestAction(editCatchAll, writeCatchAll)}") is used.`);
|
|
69406
71083
|
}
|
|
69407
71084
|
/**
|
|
@@ -69414,7 +71091,7 @@ function warnOnEditWriteConflict({ config, logger }) {
|
|
|
69414
71091
|
function convertAllToolsRules({ rules, logger }) {
|
|
69415
71092
|
let toolWideDefault;
|
|
69416
71093
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
69417
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
71094
|
+
if (pattern === CATCH_ALL_PATTERN$2) {
|
|
69418
71095
|
toolWideDefault = action;
|
|
69419
71096
|
continue;
|
|
69420
71097
|
}
|
|
@@ -69426,7 +71103,7 @@ function convertBashRules(rules) {
|
|
|
69426
71103
|
const bash = {};
|
|
69427
71104
|
const commands = [];
|
|
69428
71105
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
69429
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
71106
|
+
if (pattern === CATCH_ALL_PATTERN$2) {
|
|
69430
71107
|
bash.default = action;
|
|
69431
71108
|
continue;
|
|
69432
71109
|
}
|
|
@@ -69443,26 +71120,26 @@ function convertBashRules(rules) {
|
|
|
69443
71120
|
*/
|
|
69444
71121
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
69445
71122
|
const permission = {};
|
|
69446
|
-
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$
|
|
71123
|
+
if (isPermissionAction$1(toolPermissions.default)) permission[CATCH_ALL_PATTERN$2] = { [CATCH_ALL_PATTERN$2]: toolPermissions.default };
|
|
69447
71124
|
const bash = toolPermissions.bash;
|
|
69448
71125
|
if (isRecord$1(bash)) {
|
|
69449
71126
|
const bashRules = {};
|
|
69450
|
-
if (isPermissionAction(bash.default)) bashRules[CATCH_ALL_PATTERN$
|
|
71127
|
+
if (isPermissionAction$1(bash.default)) bashRules[CATCH_ALL_PATTERN$2] = bash.default;
|
|
69451
71128
|
if (Array.isArray(bash.commands)) {
|
|
69452
|
-
for (const entry of bash.commands) if (isRecord$1(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
71129
|
+
for (const entry of bash.commands) if (isRecord$1(entry) && typeof entry.command === "string" && isPermissionAction$1(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
69453
71130
|
}
|
|
69454
71131
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
69455
71132
|
}
|
|
69456
71133
|
const nestedTools = isRecord$1(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
69457
|
-
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
71134
|
+
const implicitLevel = isPermissionAction$1(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
69458
71135
|
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
69459
71136
|
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
69460
71137
|
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
69461
|
-
return isPermissionAction(value) ? value : void 0;
|
|
71138
|
+
return isPermissionAction$1(value) ? value : void 0;
|
|
69462
71139
|
});
|
|
69463
71140
|
if (levels.every((level) => level === void 0)) continue;
|
|
69464
71141
|
permission[category] ??= {};
|
|
69465
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
71142
|
+
permission[category][CATCH_ALL_PATTERN$2] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$2]);
|
|
69466
71143
|
}
|
|
69467
71144
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
69468
71145
|
permission.read ??= {};
|
|
@@ -69479,7 +71156,7 @@ function strictestAction(current, candidate) {
|
|
|
69479
71156
|
if (current === void 0) return candidate;
|
|
69480
71157
|
return ACTION_STRICTNESS.indexOf(current) <= ACTION_STRICTNESS.indexOf(candidate) ? current : candidate;
|
|
69481
71158
|
}
|
|
69482
|
-
function isPermissionAction(value) {
|
|
71159
|
+
function isPermissionAction$1(value) {
|
|
69483
71160
|
return PermissionActionSchema.safeParse(value).success;
|
|
69484
71161
|
}
|
|
69485
71162
|
//#endregion
|
|
@@ -70186,7 +71863,7 @@ const TAKT_SECURITY_POLICIES = {
|
|
|
70186
71863
|
const TAKT_SECURITY_POLICY_KEYS = Object.keys(TAKT_SECURITY_POLICIES);
|
|
70187
71864
|
const TAKT_DEFAULT_PROVIDER = "claude-sdk";
|
|
70188
71865
|
const TAKT_LEGACY_DEFAULT_PROVIDER = "claude";
|
|
70189
|
-
const CATCH_ALL_PATTERN = "*";
|
|
71866
|
+
const CATCH_ALL_PATTERN$1 = "*";
|
|
70190
71867
|
/**
|
|
70191
71868
|
* Permissions adapter for Takt (`.takt/config.yaml`).
|
|
70192
71869
|
*
|
|
@@ -70807,9 +72484,9 @@ function deriveTaktPermissionMode(config) {
|
|
|
70807
72484
|
*/
|
|
70808
72485
|
function taktModeToRulesyncConfig(mode) {
|
|
70809
72486
|
switch (mode) {
|
|
70810
|
-
case "full": return { permission: { bash: { [CATCH_ALL_PATTERN]: "allow" } } };
|
|
70811
|
-
case "edit": return { permission: { edit: { [CATCH_ALL_PATTERN]: "allow" } } };
|
|
70812
|
-
default: return { permission: { bash: { [CATCH_ALL_PATTERN]: "deny" } } };
|
|
72487
|
+
case "full": return { permission: { bash: { [CATCH_ALL_PATTERN$1]: "allow" } } };
|
|
72488
|
+
case "edit": return { permission: { edit: { [CATCH_ALL_PATTERN$1]: "allow" } } };
|
|
72489
|
+
default: return { permission: { bash: { [CATCH_ALL_PATTERN$1]: "deny" } } };
|
|
70813
72490
|
}
|
|
70814
72491
|
}
|
|
70815
72492
|
/**
|
|
@@ -70842,6 +72519,409 @@ function pickSecurityPolicies(source, report) {
|
|
|
70842
72519
|
return picked;
|
|
70843
72520
|
}
|
|
70844
72521
|
//#endregion
|
|
72522
|
+
//#region src/features/permissions/trae-permissions.ts
|
|
72523
|
+
const CUSTOM_PROFILES_KEY = "customProfiles";
|
|
72524
|
+
const DEFAULT_CUSTOM_PROFILE_KEY = "defaultCustomProfile";
|
|
72525
|
+
const APPROVAL_KEY = "approval";
|
|
72526
|
+
const COMMAND_RULES_KEY = "commandRules";
|
|
72527
|
+
const MCP_RULES_KEY = "mcpRules";
|
|
72528
|
+
const RESOURCE_AUTHORIZATION_KEY = "resourceAuthorization";
|
|
72529
|
+
const NETWORK_KEY = "network";
|
|
72530
|
+
const BASH_CATEGORY = "bash";
|
|
72531
|
+
const WEBFETCH_CATEGORY = "webfetch";
|
|
72532
|
+
const MCP_TOOL_PREFIX = "mcp__";
|
|
72533
|
+
const MCP_SEPARATOR = "__";
|
|
72534
|
+
const CATCH_ALL_PATTERN = "*";
|
|
72535
|
+
const REGEX_PREFIX = "r/";
|
|
72536
|
+
const REGEX_SUFFIX = "/";
|
|
72537
|
+
const ACTION_RANK = {
|
|
72538
|
+
allow: 0,
|
|
72539
|
+
ask: 1,
|
|
72540
|
+
deny: 2
|
|
72541
|
+
};
|
|
72542
|
+
const NETWORK_ACTIONS = ["allow", "deny"];
|
|
72543
|
+
function isPermissionAction(value) {
|
|
72544
|
+
return value === "allow" || value === "ask" || value === "deny";
|
|
72545
|
+
}
|
|
72546
|
+
function toStringList(value) {
|
|
72547
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
72548
|
+
}
|
|
72549
|
+
function escapeRegex(text) {
|
|
72550
|
+
return text.replace(/[\\^$.|?*+()[\]{}/]/g, "\\$&");
|
|
72551
|
+
}
|
|
72552
|
+
/** Build the anchored TRAE regex rule (`r/^...$/`) a canonical glob stands for. */
|
|
72553
|
+
function globToTraeRegex(pattern) {
|
|
72554
|
+
const body = pattern.split(CATCH_ALL_PATTERN).map(escapeRegex).join(".*");
|
|
72555
|
+
return `${REGEX_PREFIX}^${body}$${REGEX_SUFFIX}`;
|
|
72556
|
+
}
|
|
72557
|
+
/**
|
|
72558
|
+
* Translate a canonical `bash` glob into a TRAE `commandRules` key. TRAE
|
|
72559
|
+
* matches a key exactly (`git status`), as a prefix when it ends in `*`
|
|
72560
|
+
* (`git add *`), or as a regex when it is written `r/.../`. A glob whose only
|
|
72561
|
+
* `*` is the trailing one is already a TRAE prefix rule and a glob without
|
|
72562
|
+
* `*` an exact one; every other glob (a bare `*`, an inner `*`, or a literal
|
|
72563
|
+
* that would read as `r/.../`) becomes an anchored regex.
|
|
72564
|
+
*/
|
|
72565
|
+
function toTraeCommandKey(pattern) {
|
|
72566
|
+
const starCount = pattern.split(CATCH_ALL_PATTERN).length - 1;
|
|
72567
|
+
const looksLikeRegex = pattern.startsWith(REGEX_PREFIX) && pattern.endsWith(REGEX_SUFFIX);
|
|
72568
|
+
if (!looksLikeRegex && starCount === 0) return pattern;
|
|
72569
|
+
if (!looksLikeRegex && starCount === 1 && pattern.length > 1 && pattern.endsWith("*")) return pattern;
|
|
72570
|
+
return globToTraeRegex(pattern);
|
|
72571
|
+
}
|
|
72572
|
+
/**
|
|
72573
|
+
* Read a `commandRules` key back into a canonical `bash` glob, or `null` for a
|
|
72574
|
+
* key with no glob spelling: a regex rulesync did not write, or an exact key
|
|
72575
|
+
* with an inner `*` (TRAE matches that `*` literally).
|
|
72576
|
+
*/
|
|
72577
|
+
function fromTraeCommandKey(key) {
|
|
72578
|
+
if (!(key.startsWith(REGEX_PREFIX) && key.endsWith(REGEX_SUFFIX) && key.length > 2)) return toTraeCommandKey(key) === key ? key : null;
|
|
72579
|
+
const anchored = key.slice(2, -1).match(/^\^([\s\S]*)\$$/);
|
|
72580
|
+
if (!anchored) return null;
|
|
72581
|
+
const glob = (anchored[1] ?? "").split(".*").map((part) => part.replace(/\\(.)/g, "$1")).join(CATCH_ALL_PATTERN);
|
|
72582
|
+
return globToTraeRegex(glob) === key ? glob : null;
|
|
72583
|
+
}
|
|
72584
|
+
/**
|
|
72585
|
+
* Translate a canonical MCP category (`mcp__<server>__<tool>`,
|
|
72586
|
+
* `mcp__<server>__*` or `mcp__<server>`) into a TRAE `mcpRules` key
|
|
72587
|
+
* (`<server>__<tool>`, `<server>__*`, `<server>`). `null` when TRAE has no
|
|
72588
|
+
* spelling for it (`mcp__*`, which names no server).
|
|
72589
|
+
*/
|
|
72590
|
+
function toTraeMcpKey(category) {
|
|
72591
|
+
const rest = category.slice(5);
|
|
72592
|
+
const server = rest.split(MCP_SEPARATOR)[0] ?? "";
|
|
72593
|
+
if (server === "" || server.includes(CATCH_ALL_PATTERN)) return null;
|
|
72594
|
+
return rest;
|
|
72595
|
+
}
|
|
72596
|
+
/** Read a TRAE `mcpRules` key back into its canonical MCP category. */
|
|
72597
|
+
function fromTraeMcpKey(key) {
|
|
72598
|
+
if (key === "" || key.startsWith(MCP_SEPARATOR)) return null;
|
|
72599
|
+
return key.includes(MCP_SEPARATOR) ? `${MCP_TOOL_PREFIX}${key}` : `${MCP_TOOL_PREFIX}${key}${MCP_SEPARATOR}${CATCH_ALL_PATTERN}`;
|
|
72600
|
+
}
|
|
72601
|
+
function readRecord(source, path) {
|
|
72602
|
+
let current = source;
|
|
72603
|
+
for (const key of path) {
|
|
72604
|
+
if (!isRecord$1(current)) return {};
|
|
72605
|
+
current = current[key];
|
|
72606
|
+
}
|
|
72607
|
+
return isRecord$1(current) ? current : {};
|
|
72608
|
+
}
|
|
72609
|
+
/**
|
|
72610
|
+
* Return a copy of `root` with `value` at `path` (or the leaf removed when
|
|
72611
|
+
* `value` is `undefined`), creating the parent objects only when setting.
|
|
72612
|
+
*/
|
|
72613
|
+
function withPath(root, path, value) {
|
|
72614
|
+
const [head, ...rest] = path;
|
|
72615
|
+
if (head === void 0) return root;
|
|
72616
|
+
const next = { ...root };
|
|
72617
|
+
if (rest.length === 0) {
|
|
72618
|
+
if (value === void 0) delete next[head];
|
|
72619
|
+
else next[head] = value;
|
|
72620
|
+
return next;
|
|
72621
|
+
}
|
|
72622
|
+
const child = next[head];
|
|
72623
|
+
if (!isRecord$1(child)) {
|
|
72624
|
+
if (value === void 0) return next;
|
|
72625
|
+
next[head] = withPath({}, rest, value);
|
|
72626
|
+
return next;
|
|
72627
|
+
}
|
|
72628
|
+
next[head] = withPath(child, rest, value);
|
|
72629
|
+
return next;
|
|
72630
|
+
}
|
|
72631
|
+
const COMMAND_RULES_PATH = [
|
|
72632
|
+
DEFAULT_CUSTOM_PROFILE_KEY,
|
|
72633
|
+
APPROVAL_KEY,
|
|
72634
|
+
COMMAND_RULES_KEY
|
|
72635
|
+
];
|
|
72636
|
+
const MCP_RULES_PATH = [
|
|
72637
|
+
DEFAULT_CUSTOM_PROFILE_KEY,
|
|
72638
|
+
APPROVAL_KEY,
|
|
72639
|
+
MCP_RULES_KEY
|
|
72640
|
+
];
|
|
72641
|
+
function setRanked(ranked, key, action) {
|
|
72642
|
+
const existing = ranked.get(key);
|
|
72643
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) ranked.set(key, action);
|
|
72644
|
+
}
|
|
72645
|
+
/**
|
|
72646
|
+
* Rebuild `commandRules` from the canonical `bash` category. An existing
|
|
72647
|
+
* entry rulesync cannot read back (a hand-written regex) is kept, and the
|
|
72648
|
+
* `execEnv` of an entry this run rewrites is carried over.
|
|
72649
|
+
*/
|
|
72650
|
+
function buildCommandRules({ bashRules, existing }) {
|
|
72651
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
72652
|
+
for (const [pattern, action] of Object.entries(bashRules)) {
|
|
72653
|
+
if (isPrototypePollutionKey(pattern)) continue;
|
|
72654
|
+
setRanked(ranked, toTraeCommandKey(pattern), action);
|
|
72655
|
+
}
|
|
72656
|
+
const result = {};
|
|
72657
|
+
for (const [key, value] of Object.entries(existing)) if (!ranked.has(key) && fromTraeCommandKey(key) === null) result[key] = value;
|
|
72658
|
+
for (const [key, action] of ranked) {
|
|
72659
|
+
const previous = existing[key];
|
|
72660
|
+
const execEnv = isRecord$1(previous) ? previous.execEnv : void 0;
|
|
72661
|
+
result[key] = {
|
|
72662
|
+
approval: action,
|
|
72663
|
+
...typeof execEnv === "string" && { execEnv }
|
|
72664
|
+
};
|
|
72665
|
+
}
|
|
72666
|
+
return result;
|
|
72667
|
+
}
|
|
72668
|
+
/**
|
|
72669
|
+
* Rebuild `mcpRules`: every MCP category of the canonical config is written,
|
|
72670
|
+
* and an existing entry whose category the config does not name is kept.
|
|
72671
|
+
*/
|
|
72672
|
+
function buildMcpRules({ permission, existing, logger }) {
|
|
72673
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
72674
|
+
const managedCategories = /* @__PURE__ */ new Set();
|
|
72675
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
72676
|
+
if (!category.startsWith(MCP_TOOL_PREFIX)) continue;
|
|
72677
|
+
const key = toTraeMcpKey(category);
|
|
72678
|
+
if (key === null) {
|
|
72679
|
+
logger?.warn(`TRAE permissions: skipping the '${category}' category — TRAE's mcpRules name an MCP server ('server', 'server__*' or 'server__tool').`);
|
|
72680
|
+
continue;
|
|
72681
|
+
}
|
|
72682
|
+
managedCategories.add(category);
|
|
72683
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
72684
|
+
if (pattern !== CATCH_ALL_PATTERN) {
|
|
72685
|
+
logger?.warn(`TRAE permissions: skipping the '${category}' rule '${pattern}' — TRAE matches MCP tools by name only.`);
|
|
72686
|
+
continue;
|
|
72687
|
+
}
|
|
72688
|
+
setRanked(ranked, key, action);
|
|
72689
|
+
}
|
|
72690
|
+
}
|
|
72691
|
+
const result = {};
|
|
72692
|
+
for (const [key, value] of Object.entries(existing)) {
|
|
72693
|
+
const category = fromTraeMcpKey(key);
|
|
72694
|
+
if (!ranked.has(key) && (category === null || !managedCategories.has(category))) result[key] = value;
|
|
72695
|
+
}
|
|
72696
|
+
for (const [key, action] of ranked) result[key] = { approval: action };
|
|
72697
|
+
return result;
|
|
72698
|
+
}
|
|
72699
|
+
/** Split the canonical `webfetch` rules into TRAE's network allow/deny lists. */
|
|
72700
|
+
function buildNetworkLists({ webfetchRules, logger }) {
|
|
72701
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
72702
|
+
for (const [pattern, action] of Object.entries(webfetchRules)) {
|
|
72703
|
+
if (isPrototypePollutionKey(pattern)) continue;
|
|
72704
|
+
if (pattern === CATCH_ALL_PATTERN) {
|
|
72705
|
+
logger?.warn(`TRAE permissions: skipping the 'webfetch' rule '*' — TRAE sets the default network policy in the custom permission mode ('customProfiles.defaultCustomProfile.network.default'), not in resourceAuthorization.`);
|
|
72706
|
+
continue;
|
|
72707
|
+
}
|
|
72708
|
+
if (action === "ask") {
|
|
72709
|
+
logger?.warn(`TRAE permissions: skipping the 'webfetch' ask rule '${pattern}' — resourceAuthorization.network has only allow and deny lists.`);
|
|
72710
|
+
continue;
|
|
72711
|
+
}
|
|
72712
|
+
setRanked(ranked, pattern, action);
|
|
72713
|
+
}
|
|
72714
|
+
const lists = {
|
|
72715
|
+
allow: [],
|
|
72716
|
+
deny: []
|
|
72717
|
+
};
|
|
72718
|
+
for (const [pattern, action] of ranked) if (action === "allow" || action === "deny") lists[action].push(pattern);
|
|
72719
|
+
return lists;
|
|
72720
|
+
}
|
|
72721
|
+
function warnUnmappedCategories({ permission, logger }) {
|
|
72722
|
+
for (const category of Object.keys(permission)) if (category !== BASH_CATEGORY && category !== WEBFETCH_CATEGORY && !category.startsWith(MCP_TOOL_PREFIX)) logger?.warn(`TRAE permissions: skipping the '${category}' category — TRAE's global permission file has no rule for it (only 'bash', 'webfetch' and 'mcp__*' are mapped).`);
|
|
72723
|
+
}
|
|
72724
|
+
/** A rebuilt map, or `undefined` (remove the key) when it ended up empty. */
|
|
72725
|
+
function nonEmpty(value) {
|
|
72726
|
+
return Object.keys(value).length > 0 ? value : void 0;
|
|
72727
|
+
}
|
|
72728
|
+
/**
|
|
72729
|
+
* The new `customProfiles` value: the existing one with `commandRules` and
|
|
72730
|
+
* `mcpRules` rebuilt for the categories the config names. The existing value
|
|
72731
|
+
* is returned untouched when the config names none of them.
|
|
72732
|
+
*/
|
|
72733
|
+
function buildCustomProfiles({ existing, permission, logger }) {
|
|
72734
|
+
const bashRules = permission[BASH_CATEGORY];
|
|
72735
|
+
const managesMcp = Object.keys(permission).some((category) => category.startsWith(MCP_TOOL_PREFIX));
|
|
72736
|
+
if (bashRules === void 0 && !managesMcp) return existing;
|
|
72737
|
+
let result = isRecord$1(existing) ? existing : {};
|
|
72738
|
+
if (bashRules !== void 0) {
|
|
72739
|
+
const commandRules = buildCommandRules({
|
|
72740
|
+
bashRules,
|
|
72741
|
+
existing: readRecord(result, COMMAND_RULES_PATH)
|
|
72742
|
+
});
|
|
72743
|
+
result = withPath(result, COMMAND_RULES_PATH, nonEmpty(commandRules));
|
|
72744
|
+
}
|
|
72745
|
+
if (managesMcp) {
|
|
72746
|
+
const mcpRules = buildMcpRules({
|
|
72747
|
+
permission,
|
|
72748
|
+
existing: readRecord(result, MCP_RULES_PATH),
|
|
72749
|
+
logger
|
|
72750
|
+
});
|
|
72751
|
+
result = withPath(result, MCP_RULES_PATH, nonEmpty(mcpRules));
|
|
72752
|
+
}
|
|
72753
|
+
return existing === void 0 ? nonEmpty(result) : result;
|
|
72754
|
+
}
|
|
72755
|
+
/**
|
|
72756
|
+
* The new `resourceAuthorization` value: the existing one with
|
|
72757
|
+
* `network.allow` / `deny` rebuilt from the `webfetch` category, or the
|
|
72758
|
+
* existing value untouched when the config has no `webfetch` category.
|
|
72759
|
+
*/
|
|
72760
|
+
function buildResourceAuthorization({ existing, permission, logger }) {
|
|
72761
|
+
const webfetchRules = permission[WEBFETCH_CATEGORY];
|
|
72762
|
+
if (webfetchRules === void 0) return existing;
|
|
72763
|
+
const lists = buildNetworkLists({
|
|
72764
|
+
webfetchRules,
|
|
72765
|
+
logger
|
|
72766
|
+
});
|
|
72767
|
+
let result = isRecord$1(existing) ? existing : {};
|
|
72768
|
+
for (const action of NETWORK_ACTIONS) result = withPath(result, [NETWORK_KEY, action], nonEmpty(lists[action]));
|
|
72769
|
+
return existing === void 0 ? nonEmpty(result) : result;
|
|
72770
|
+
}
|
|
72771
|
+
/**
|
|
72772
|
+
* TRAE permissions (global scope only).
|
|
72773
|
+
*
|
|
72774
|
+
* TRAE keeps its global permission policy and its custom permission mode in
|
|
72775
|
+
* one user file, `~/.trae/permission/global.json`. Rulesync writes three parts
|
|
72776
|
+
* of it and leaves the rest (`shellSandbox`, `approval.reviewer`,
|
|
72777
|
+
* `sceneRules`, the default `filesystem` / `network` policy,
|
|
72778
|
+
* `resourceAuthorization.filesystem`) as it is:
|
|
72779
|
+
*
|
|
72780
|
+
* - `bash` → `customProfiles.defaultCustomProfile.approval.commandRules`,
|
|
72781
|
+
* `{ "<pattern>": { "approval": "allow" | "ask" | "deny" } }`. A pattern is
|
|
72782
|
+
* matched exactly, as a prefix when it ends in `*`, or as a regex written
|
|
72783
|
+
* `r/.../`; other globs become anchored regexes.
|
|
72784
|
+
* - `mcp__<server>__<tool>` / `mcp__<server>__*` →
|
|
72785
|
+
* `customProfiles.defaultCustomProfile.approval.mcpRules`
|
|
72786
|
+
* (`<server>__<tool>` / `<server>__*`).
|
|
72787
|
+
* - `webfetch` domains → `resourceAuthorization.network.allow` / `deny`.
|
|
72788
|
+
*
|
|
72789
|
+
* The file is never deleted, and an unparseable file is an error rather than
|
|
72790
|
+
* being replaced.
|
|
72791
|
+
*
|
|
72792
|
+
* @see https://docs.trae.ai/ide/permission-and-approval?_lang=en
|
|
72793
|
+
* @see https://docs.trae.ai/ide/custom-global-permission-configuration-reference?_lang=en
|
|
72794
|
+
* @see https://docs.trae.ai/ide/custom-permission-mode-configuration-reference?_lang=en
|
|
72795
|
+
*/
|
|
72796
|
+
var TraePermissions = class TraePermissions extends ToolPermissions {
|
|
72797
|
+
constructor(params) {
|
|
72798
|
+
super({
|
|
72799
|
+
...params,
|
|
72800
|
+
fileContent: params.fileContent ?? "{}"
|
|
72801
|
+
});
|
|
72802
|
+
}
|
|
72803
|
+
isDeletable() {
|
|
72804
|
+
return false;
|
|
72805
|
+
}
|
|
72806
|
+
static getSettablePaths(_options = {}) {
|
|
72807
|
+
return {
|
|
72808
|
+
relativeDirPath: TRAE_PERMISSION_DIR_PATH,
|
|
72809
|
+
relativeFilePath: TRAE_PERMISSION_FILE_NAME
|
|
72810
|
+
};
|
|
72811
|
+
}
|
|
72812
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = true }) {
|
|
72813
|
+
const paths = TraePermissions.getSettablePaths({ global });
|
|
72814
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
72815
|
+
return new TraePermissions({
|
|
72816
|
+
outputRoot,
|
|
72817
|
+
relativeDirPath: paths.relativeDirPath,
|
|
72818
|
+
relativeFilePath: paths.relativeFilePath,
|
|
72819
|
+
fileContent,
|
|
72820
|
+
validate,
|
|
72821
|
+
global
|
|
72822
|
+
});
|
|
72823
|
+
}
|
|
72824
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = true, logger }) {
|
|
72825
|
+
const paths = TraePermissions.getSettablePaths({ global });
|
|
72826
|
+
const relativePath = join(paths.relativeDirPath, paths.relativeFilePath);
|
|
72827
|
+
const filePath = join(outputRoot, relativePath);
|
|
72828
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
72829
|
+
const existing = parseTraePermissionConfig({
|
|
72830
|
+
fileContent: existingContent,
|
|
72831
|
+
relativePath
|
|
72832
|
+
});
|
|
72833
|
+
const permission = honorAllToolsOnBash(rulesyncPermissions.getJson().permission);
|
|
72834
|
+
warnUnmappedCategories({
|
|
72835
|
+
permission,
|
|
72836
|
+
logger
|
|
72837
|
+
});
|
|
72838
|
+
return new TraePermissions({
|
|
72839
|
+
outputRoot,
|
|
72840
|
+
relativeDirPath: paths.relativeDirPath,
|
|
72841
|
+
relativeFilePath: paths.relativeFilePath,
|
|
72842
|
+
fileContent: applySharedConfigPatch({
|
|
72843
|
+
fileKey: sharedConfigFileKey(paths),
|
|
72844
|
+
feature: "permissions",
|
|
72845
|
+
existingContent,
|
|
72846
|
+
patch: {
|
|
72847
|
+
[CUSTOM_PROFILES_KEY]: buildCustomProfiles({
|
|
72848
|
+
existing: existing[CUSTOM_PROFILES_KEY],
|
|
72849
|
+
permission,
|
|
72850
|
+
logger
|
|
72851
|
+
}),
|
|
72852
|
+
[RESOURCE_AUTHORIZATION_KEY]: buildResourceAuthorization({
|
|
72853
|
+
existing: existing[RESOURCE_AUTHORIZATION_KEY],
|
|
72854
|
+
permission,
|
|
72855
|
+
logger
|
|
72856
|
+
})
|
|
72857
|
+
},
|
|
72858
|
+
filePath
|
|
72859
|
+
}),
|
|
72860
|
+
validate: true,
|
|
72861
|
+
global
|
|
72862
|
+
});
|
|
72863
|
+
}
|
|
72864
|
+
toRulesyncPermissions() {
|
|
72865
|
+
const config = parseTraePermissionConfig({
|
|
72866
|
+
fileContent: this.getFileContent() || "{}",
|
|
72867
|
+
relativePath: join(this.getRelativeDirPath(), this.getRelativeFilePath())
|
|
72868
|
+
});
|
|
72869
|
+
const customProfiles = readRecord(config, [CUSTOM_PROFILES_KEY]);
|
|
72870
|
+
const permission = {};
|
|
72871
|
+
const put = (category, pattern, action) => {
|
|
72872
|
+
if (isPrototypePollutionKey(category) || isPrototypePollutionKey(pattern)) return;
|
|
72873
|
+
const rules = permission[category] ??= {};
|
|
72874
|
+
const existing = rules[pattern];
|
|
72875
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[pattern] = action;
|
|
72876
|
+
};
|
|
72877
|
+
for (const [key, value] of Object.entries(readRecord(customProfiles, COMMAND_RULES_PATH))) {
|
|
72878
|
+
const pattern = fromTraeCommandKey(key);
|
|
72879
|
+
const approval = isRecord$1(value) ? value.approval : void 0;
|
|
72880
|
+
if (pattern !== null && pattern.trim() !== "" && isPermissionAction(approval)) put(BASH_CATEGORY, pattern, approval);
|
|
72881
|
+
}
|
|
72882
|
+
for (const [key, value] of Object.entries(readRecord(customProfiles, MCP_RULES_PATH))) {
|
|
72883
|
+
const category = fromTraeMcpKey(key);
|
|
72884
|
+
const approval = isRecord$1(value) ? value.approval : void 0;
|
|
72885
|
+
if (category !== null && isPermissionAction(approval)) put(category, CATCH_ALL_PATTERN, approval);
|
|
72886
|
+
}
|
|
72887
|
+
const network = readRecord(config, [RESOURCE_AUTHORIZATION_KEY, NETWORK_KEY]);
|
|
72888
|
+
for (const action of NETWORK_ACTIONS) for (const pattern of toStringList(network[action])) if (pattern.trim() !== "") put(WEBFETCH_CATEGORY, pattern, action);
|
|
72889
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
72890
|
+
}
|
|
72891
|
+
validate() {
|
|
72892
|
+
return {
|
|
72893
|
+
success: true,
|
|
72894
|
+
error: null
|
|
72895
|
+
};
|
|
72896
|
+
}
|
|
72897
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = true }) {
|
|
72898
|
+
return new TraePermissions({
|
|
72899
|
+
outputRoot,
|
|
72900
|
+
relativeDirPath,
|
|
72901
|
+
relativeFilePath,
|
|
72902
|
+
fileContent: "{}",
|
|
72903
|
+
validate: false,
|
|
72904
|
+
global
|
|
72905
|
+
});
|
|
72906
|
+
}
|
|
72907
|
+
};
|
|
72908
|
+
/**
|
|
72909
|
+
* Fail closed on a syntax error or non-object root, so a broken file is
|
|
72910
|
+
* surfaced rather than partially imported or overwritten.
|
|
72911
|
+
*/
|
|
72912
|
+
function parseTraePermissionConfig({ fileContent, relativePath }) {
|
|
72913
|
+
try {
|
|
72914
|
+
return parseSharedConfig({
|
|
72915
|
+
format: "json",
|
|
72916
|
+
fileContent,
|
|
72917
|
+
filePath: relativePath,
|
|
72918
|
+
invalidRootPolicy: "error"
|
|
72919
|
+
});
|
|
72920
|
+
} catch (error) {
|
|
72921
|
+
throw new Error(`Failed to parse TRAE permission config in ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
72922
|
+
}
|
|
72923
|
+
}
|
|
72924
|
+
//#endregion
|
|
70845
72925
|
//#region src/utils/display-width.ts
|
|
70846
72926
|
/**
|
|
70847
72927
|
* Characters a terminal draws two columns wide: the East Asian Wide and
|
|
@@ -74127,6 +76207,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
74127
76207
|
supportsImport: true
|
|
74128
76208
|
}
|
|
74129
76209
|
}],
|
|
76210
|
+
["trae", {
|
|
76211
|
+
class: TraePermissions,
|
|
76212
|
+
meta: {
|
|
76213
|
+
supportsProject: false,
|
|
76214
|
+
supportsGlobal: true,
|
|
76215
|
+
supportsImport: true
|
|
76216
|
+
}
|
|
76217
|
+
}],
|
|
74130
76218
|
["zoocode", {
|
|
74131
76219
|
class: ZoocodePermissions,
|
|
74132
76220
|
meta: {
|
|
@@ -74231,7 +76319,7 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
74231
76319
|
})];
|
|
74232
76320
|
}
|
|
74233
76321
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
74234
|
-
return toolFiles.filter((f) => f instanceof ToolPermissions).map((p) => p.toRulesyncPermissions());
|
|
76322
|
+
return toolFiles.filter((f) => f instanceof ToolPermissions).map((p) => p.toRulesyncPermissions({ logger: this.logger }));
|
|
74235
76323
|
}
|
|
74236
76324
|
static getToolTargets({ global = false, importOnly = false } = {}) {
|
|
74237
76325
|
return [...toolPermissionsFactories.entries()].filter(([, f]) => global ? f.meta.supportsGlobal : f.meta.supportsProject).filter(([, f]) => importOnly ? f.meta.supportsImport : true).map(([target]) => target);
|
|
@@ -79144,8 +81232,13 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
|
|
|
79144
81232
|
* slash command), so this target reuses {@link AgentsSkillsSkill} with GitLab's
|
|
79145
81233
|
* locations:
|
|
79146
81234
|
*
|
|
79147
|
-
* - Project skills
|
|
79148
|
-
* root, which both the GitLab UI flows and the GitLab Duo CLI read.
|
|
81235
|
+
* - Project skills are written to the cross-tool `.agents/skills/<name>/SKILL.md`
|
|
81236
|
+
* root, which both the GitLab UI flows and the GitLab Duo CLI read. GitLab
|
|
81237
|
+
* Duo also reads a non-hidden `skills/<name>/SKILL.md` at the project root
|
|
81238
|
+
* (`.agents/skills/` wins on a name clash); rulesync imports from it but
|
|
81239
|
+
* never writes or sweeps it, because that directory routinely holds
|
|
81240
|
+
* hand-authored skills. Earlier rulesync versions wrote there, so a skill
|
|
81241
|
+
* generated by one of them has to be removed from `skills/` by hand.
|
|
79149
81242
|
* - User skills live in `~/.gitlab/duo/skills/` and are loaded by the CLI only
|
|
79150
81243
|
* when started with `--enable-global-skills true` (or
|
|
79151
81244
|
* `GITLAB_ENABLE_GLOBAL_SKILLS=true`).
|
|
@@ -79154,18 +81247,20 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
|
|
|
79154
81247
|
*/
|
|
79155
81248
|
var GitlabduoSkill = class extends AgentsSkillsSkill {
|
|
79156
81249
|
static getSettablePaths({ global = false } = {}) {
|
|
79157
|
-
return { relativeDirPath:
|
|
81250
|
+
if (global) return { relativeDirPath: GITLABDUO_GLOBAL_SKILLS_DIR };
|
|
81251
|
+
return {
|
|
81252
|
+
relativeDirPath: GITLABDUO_SKILLS_DIR,
|
|
81253
|
+
importOnlySkillRoots: [GITLABDUO_LEGACY_SKILLS_DIR]
|
|
81254
|
+
};
|
|
79158
81255
|
}
|
|
79159
81256
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
79160
81257
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
79161
81258
|
return targets.includes("*") || targets.includes("gitlabduo");
|
|
79162
81259
|
}
|
|
79163
81260
|
/**
|
|
79164
|
-
* The project
|
|
79165
|
-
*
|
|
79166
|
-
*
|
|
79167
|
-
* `--delete` orphan sweep skips that root entirely through the factory's
|
|
79168
|
-
* `committedOutput` meta; this filter still applies to the global sweep.)
|
|
81261
|
+
* The import-only project-root `skills/` directory is one many repositories
|
|
81262
|
+
* already use for unrelated content, so only a subdirectory holding a
|
|
81263
|
+
* `SKILL.md` is treated as a skill, in every root this target reads.
|
|
79169
81264
|
*/
|
|
79170
81265
|
static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
|
|
79171
81266
|
return await fileExists(join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME));
|
|
@@ -80718,9 +82813,44 @@ var MimocodeSkill = class extends OpenCodeSkill {
|
|
|
80718
82813
|
//#region src/features/skills/musecode-skill.ts
|
|
80719
82814
|
const MusecodeSkillFrontmatterSchema = z.looseObject({
|
|
80720
82815
|
name: z.string(),
|
|
80721
|
-
description: z.string()
|
|
82816
|
+
description: z.string(),
|
|
82817
|
+
license: z.optional(z.string()),
|
|
82818
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
82819
|
+
metadata: z.optional(z.looseObject({}))
|
|
80722
82820
|
});
|
|
80723
82821
|
/**
|
|
82822
|
+
* Build the Muse Code frontmatter. `license`, `compatibility` and `metadata`
|
|
82823
|
+
* come from the `musecode` section with the root-level values as fallback, and
|
|
82824
|
+
* are normalized exactly as the `agentsskills` writer normalizes them: at
|
|
82825
|
+
* project scope both targets write the same `.agents/skills/<name>/SKILL.md`,
|
|
82826
|
+
* so for a skill without per-target overrides the file no longer depends on
|
|
82827
|
+
* which of the two runs last.
|
|
82828
|
+
*/
|
|
82829
|
+
function buildMusecodeFrontmatter(rulesyncFrontmatter) {
|
|
82830
|
+
const section = rulesyncFrontmatter.musecode;
|
|
82831
|
+
const license = resolveLicense({
|
|
82832
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
82833
|
+
section
|
|
82834
|
+
});
|
|
82835
|
+
const compatibility = resolveCompatibility({
|
|
82836
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
82837
|
+
section
|
|
82838
|
+
});
|
|
82839
|
+
const metadata = resolveMetadata({
|
|
82840
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
82841
|
+
section
|
|
82842
|
+
});
|
|
82843
|
+
return {
|
|
82844
|
+
name: rulesyncFrontmatter.name,
|
|
82845
|
+
description: rulesyncFrontmatter.description,
|
|
82846
|
+
...toSpecConformantAgentSkillFields({
|
|
82847
|
+
...license !== void 0 && { license },
|
|
82848
|
+
...compatibility !== void 0 && { compatibility },
|
|
82849
|
+
...metadata !== void 0 && { metadata }
|
|
82850
|
+
})
|
|
82851
|
+
};
|
|
82852
|
+
}
|
|
82853
|
+
/**
|
|
80724
82854
|
* Represents a Meta Muse Code skill directory.
|
|
80725
82855
|
*
|
|
80726
82856
|
* Muse Code reads Agent Skills as `<skill-id>/SKILL.md` directories from the
|
|
@@ -80777,10 +82907,16 @@ var MusecodeSkill = class MusecodeSkill extends ToolSkill {
|
|
|
80777
82907
|
}
|
|
80778
82908
|
toRulesyncSkill() {
|
|
80779
82909
|
const frontmatter = this.getFrontmatter();
|
|
82910
|
+
const musecodeSection = {
|
|
82911
|
+
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
82912
|
+
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
82913
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
82914
|
+
};
|
|
80780
82915
|
const rulesyncFrontmatter = {
|
|
80781
82916
|
name: frontmatter.name,
|
|
80782
82917
|
description: frontmatter.description,
|
|
80783
|
-
targets: ["*"]
|
|
82918
|
+
targets: ["*"],
|
|
82919
|
+
...Object.keys(musecodeSection).length > 0 && { musecode: musecodeSection }
|
|
80784
82920
|
};
|
|
80785
82921
|
return new RulesyncSkill({
|
|
80786
82922
|
outputRoot: this.outputRoot,
|
|
@@ -80795,11 +82931,7 @@ var MusecodeSkill = class MusecodeSkill extends ToolSkill {
|
|
|
80795
82931
|
}
|
|
80796
82932
|
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
80797
82933
|
const settablePaths = MusecodeSkill.getSettablePaths({ global });
|
|
80798
|
-
const
|
|
80799
|
-
const musecodeFrontmatter = {
|
|
80800
|
-
name: rulesyncFrontmatter.name,
|
|
80801
|
-
description: rulesyncFrontmatter.description
|
|
80802
|
-
};
|
|
82934
|
+
const musecodeFrontmatter = buildMusecodeFrontmatter(rulesyncSkill.getFrontmatter());
|
|
80803
82935
|
return new MusecodeSkill({
|
|
80804
82936
|
outputRoot,
|
|
80805
82937
|
relativeDirPath: settablePaths.relativeDirPath,
|
|
@@ -83250,8 +85382,7 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
83250
85382
|
supportsProject: true,
|
|
83251
85383
|
supportsSimulated: false,
|
|
83252
85384
|
supportsGlobal: true,
|
|
83253
|
-
lenientImport: true
|
|
83254
|
-
committedOutput: true
|
|
85385
|
+
lenientImport: true
|
|
83255
85386
|
}
|
|
83256
85387
|
}],
|
|
83257
85388
|
["grokcli", {
|
|
@@ -83864,10 +85995,6 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
83864
85995
|
}
|
|
83865
85996
|
async loadToolDirsToDelete() {
|
|
83866
85997
|
const factory = this.getFactory(this.toolTarget);
|
|
83867
|
-
if (factory.meta.committedOutput === true && !this.global) {
|
|
83868
|
-
this.logger.debug(`Skipping the orphan sweep for ${this.toolTarget} skills: the project root is committed and user-owned`);
|
|
83869
|
-
return [];
|
|
83870
|
-
}
|
|
83871
85998
|
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
83872
85999
|
const roots = toolSkillSearchRoots(paths);
|
|
83873
86000
|
const toolSkills = [];
|
|
@@ -85412,6 +87539,14 @@ const PLUGIN_FORBIDDEN_FIELDS = [
|
|
|
85412
87539
|
"mcpServers",
|
|
85413
87540
|
"permissionMode"
|
|
85414
87541
|
];
|
|
87542
|
+
/**
|
|
87543
|
+
* Claude Code ignores these for plugin-shipped agents without any security
|
|
87544
|
+
* rationale. They are dropped with a separate warning so the output does not
|
|
87545
|
+
* suggest a behavior the plugin agent never gets.
|
|
87546
|
+
*
|
|
87547
|
+
* @see https://code.claude.com/docs/en/sub-agents#supported-frontmatter-fields
|
|
87548
|
+
*/
|
|
87549
|
+
const PLUGIN_IGNORED_FIELDS = ["initialPrompt"];
|
|
85415
87550
|
/** The only `isolation` value plugin agents accept. */
|
|
85416
87551
|
const PLUGIN_ISOLATION_VALUE = "worktree";
|
|
85417
87552
|
var ClaudecodePluginSubagent = class extends ClaudecodeSubagent {
|
|
@@ -85431,6 +87566,9 @@ var ClaudecodePluginSubagent = class extends ClaudecodeSubagent {
|
|
|
85431
87566
|
const dropped = PLUGIN_FORBIDDEN_FIELDS.filter((field) => sanitized[field] !== void 0);
|
|
85432
87567
|
for (const field of PLUGIN_FORBIDDEN_FIELDS) delete sanitized[field];
|
|
85433
87568
|
if (dropped.length > 0) logger?.warn(`Dropping ${dropped.join(", ")} from claudecode-plugin subagent ${relativeFilePath}: Claude Code does not support these fields for plugin-shipped agents.`);
|
|
87569
|
+
const ignored = PLUGIN_IGNORED_FIELDS.filter((field) => sanitized[field] !== void 0);
|
|
87570
|
+
for (const field of PLUGIN_IGNORED_FIELDS) delete sanitized[field];
|
|
87571
|
+
if (ignored.length > 0) logger?.warn(`Dropping ${ignored.join(", ")} from claudecode-plugin subagent ${relativeFilePath}: Claude Code ignores these fields for plugin-shipped agents.`);
|
|
85434
87572
|
if (sanitized.isolation !== void 0 && sanitized.isolation !== PLUGIN_ISOLATION_VALUE) {
|
|
85435
87573
|
logger?.warn(`Dropping isolation "${sanitized.isolation}" from claudecode-plugin subagent ${relativeFilePath}: "${PLUGIN_ISOLATION_VALUE}" is the only value Claude Code accepts for plugin-shipped agents.`);
|
|
85436
87574
|
delete sanitized.isolation;
|
|
@@ -88884,6 +91022,32 @@ async function resolveOpenCodeAgentPrompt({ prompt, configDir }) {
|
|
|
88884
91022
|
configDir
|
|
88885
91023
|
});
|
|
88886
91024
|
}
|
|
91025
|
+
/**
|
|
91026
|
+
* Resolves the `prompt` of each inline agent entry of one config file against
|
|
91027
|
+
* that file's directory, so entries from several files can be merged without
|
|
91028
|
+
* losing where a `"{file:./path}"` reference points.
|
|
91029
|
+
*/
|
|
91030
|
+
async function resolveOpenCodeAgentPrompts({ entries, configDir }) {
|
|
91031
|
+
if (entries === null) return null;
|
|
91032
|
+
const resolved = {};
|
|
91033
|
+
for (const [name, rawEntry] of Object.entries(entries)) {
|
|
91034
|
+
const entry = asOpencodeEntries(rawEntry);
|
|
91035
|
+
const value = entry === null || !Object.hasOwn(entry, "prompt") ? rawEntry : {
|
|
91036
|
+
...entry,
|
|
91037
|
+
prompt: await resolveOpenCodeAgentPrompt({
|
|
91038
|
+
prompt: entry.prompt,
|
|
91039
|
+
configDir
|
|
91040
|
+
})
|
|
91041
|
+
};
|
|
91042
|
+
Object.defineProperty(resolved, name, {
|
|
91043
|
+
value,
|
|
91044
|
+
enumerable: true,
|
|
91045
|
+
configurable: true,
|
|
91046
|
+
writable: true
|
|
91047
|
+
});
|
|
91048
|
+
}
|
|
91049
|
+
return resolved;
|
|
91050
|
+
}
|
|
88887
91051
|
var OpenCodeSubagent = class extends OpenCodeStyleSubagent {
|
|
88888
91052
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
88889
91053
|
static layout = OPENCODE_LAYOUT;
|
|
@@ -88955,8 +91119,9 @@ var OpenCodeSubagent = class extends OpenCodeStyleSubagent {
|
|
|
88955
91119
|
});
|
|
88956
91120
|
}
|
|
88957
91121
|
/**
|
|
88958
|
-
* Imports agents defined inline in `opencode.json` / `opencode.jsonc`
|
|
88959
|
-
*
|
|
91122
|
+
* Imports agents defined inline in `opencode.json` / `opencode.jsonc` (and,
|
|
91123
|
+
* at project scope, `.opencode/opencode.json(c)`, which OpenCode merges over
|
|
91124
|
+
* them) under the top-level `agent` key — or its V2 spelling `agents`, see
|
|
88960
91125
|
* `getOpencodeAgentEntries` — (in addition to the Markdown files under
|
|
88961
91126
|
* `.opencode/agents/`). Each entry's `prompt` becomes the subagent body
|
|
88962
91127
|
* (a `"{file:./path}"` reference is resolved relative to the config file's
|
|
@@ -88969,30 +91134,26 @@ var OpenCodeSubagent = class extends OpenCodeStyleSubagent {
|
|
|
88969
91134
|
* @see https://opencode.ai/docs/agents/#json
|
|
88970
91135
|
*/
|
|
88971
91136
|
static async loadAdditionalImportFiles({ outputRoot = process.cwd(), global = false } = {}) {
|
|
88972
|
-
const
|
|
88973
|
-
config: await readOpencodeConfig({
|
|
88974
|
-
outputRoot,
|
|
88975
|
-
global,
|
|
88976
|
-
layout: this.layout
|
|
88977
|
-
}),
|
|
88978
|
-
layout: this.layout
|
|
88979
|
-
});
|
|
88980
|
-
if (!agentEntries) return [];
|
|
88981
|
-
const paths = this.getSettablePaths({ global });
|
|
88982
|
-
const configDir = getOpencodeConfigDir({
|
|
91137
|
+
const layers = await readOpencodeConfigLayers({
|
|
88983
91138
|
outputRoot,
|
|
88984
91139
|
global,
|
|
88985
91140
|
layout: this.layout
|
|
88986
91141
|
});
|
|
91142
|
+
const agentEntries = mergeOpencodeEntryLayers(await Promise.all(layers.map(({ config, configDir }) => resolveOpenCodeAgentPrompts({
|
|
91143
|
+
entries: getOpencodeAgentEntries({
|
|
91144
|
+
config,
|
|
91145
|
+
layout: this.layout
|
|
91146
|
+
}),
|
|
91147
|
+
configDir
|
|
91148
|
+
}))));
|
|
91149
|
+
if (!agentEntries) return [];
|
|
91150
|
+
const paths = this.getSettablePaths({ global });
|
|
88987
91151
|
const subagents = [];
|
|
88988
91152
|
for (const [name, rawEntry] of Object.entries(agentEntries)) {
|
|
88989
91153
|
const entry = asOpencodeEntries(rawEntry);
|
|
88990
91154
|
if (!entry) continue;
|
|
88991
91155
|
const { prompt, ...frontmatterFields } = entry;
|
|
88992
|
-
const body =
|
|
88993
|
-
prompt,
|
|
88994
|
-
configDir
|
|
88995
|
-
});
|
|
91156
|
+
const body = typeof prompt === "string" ? prompt : "";
|
|
88996
91157
|
const parseResult = OpenCodeSubagentFrontmatterSchema.safeParse(frontmatterFields);
|
|
88997
91158
|
if (!parseResult.success) continue;
|
|
88998
91159
|
const frontmatter = parseResult.data;
|
|
@@ -91752,7 +93913,7 @@ var ToolRule = class extends ToolFile {
|
|
|
91752
93913
|
body: this.getFileContent()
|
|
91753
93914
|
});
|
|
91754
93915
|
}
|
|
91755
|
-
toRulesyncRuleDefault() {
|
|
93916
|
+
toRulesyncRuleDefault({ body } = {}) {
|
|
91756
93917
|
return new RulesyncRule({
|
|
91757
93918
|
outputRoot: process.cwd(),
|
|
91758
93919
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -91763,7 +93924,7 @@ var ToolRule = class extends ToolFile {
|
|
|
91763
93924
|
description: this.description,
|
|
91764
93925
|
globs: this.globs ?? (this.isRoot() ? ["**/*"] : [])
|
|
91765
93926
|
},
|
|
91766
|
-
body: this.getFileContent()
|
|
93927
|
+
body: body ?? this.getFileContent()
|
|
91767
93928
|
});
|
|
91768
93929
|
}
|
|
91769
93930
|
isRoot() {
|
|
@@ -92869,11 +95030,19 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
92869
95030
|
}
|
|
92870
95031
|
super({
|
|
92871
95032
|
...rest,
|
|
92872
|
-
fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
|
|
95033
|
+
fileContent: rest.root && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
|
|
92873
95034
|
});
|
|
92874
95035
|
this.frontmatter = frontmatter;
|
|
92875
95036
|
this.body = body;
|
|
92876
95037
|
}
|
|
95038
|
+
/**
|
|
95039
|
+
* Frontmatter written on the root rule. The project `AGENTS.md` and the
|
|
95040
|
+
* global `GEMINI.md` take none; a subclass whose root rule sits inside a
|
|
95041
|
+
* `rules/` directory, where every `.md` needs a `trigger`, overrides it.
|
|
95042
|
+
*/
|
|
95043
|
+
static getRootRuleFrontmatter() {
|
|
95044
|
+
return {};
|
|
95045
|
+
}
|
|
92877
95046
|
static getGlobalRootPath(excludeToolDir) {
|
|
92878
95047
|
return {
|
|
92879
95048
|
relativeDirPath: buildToolPath(ANTIGRAVITY_IDE_GEMINI_DIR, ".", excludeToolDir),
|
|
@@ -92900,13 +95069,16 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
92900
95069
|
const paths = this.getSettablePaths({ global });
|
|
92901
95070
|
if (relativeFilePath === paths.root.relativeFilePath && (relativeDirPath === void 0 || relativeDirPath === paths.root.relativeDirPath)) {
|
|
92902
95071
|
const rootPath = paths.root;
|
|
92903
|
-
const
|
|
95072
|
+
const rootFilePath = join(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath);
|
|
95073
|
+
const rootContent = await readFileContent(rootFilePath);
|
|
95074
|
+
const rootFrontmatter = this.getRootRuleFrontmatter();
|
|
95075
|
+
const rootBody = Object.keys(rootFrontmatter).length === 0 ? rootContent : parseFrontmatter(rootContent, rootFilePath).body;
|
|
92904
95076
|
return new this({
|
|
92905
95077
|
outputRoot,
|
|
92906
95078
|
relativeDirPath: rootPath.relativeDirPath,
|
|
92907
95079
|
relativeFilePath: rootPath.relativeFilePath,
|
|
92908
|
-
frontmatter:
|
|
92909
|
-
body:
|
|
95080
|
+
frontmatter: rootFrontmatter,
|
|
95081
|
+
body: rootBody,
|
|
92910
95082
|
validate,
|
|
92911
95083
|
root: true,
|
|
92912
95084
|
global
|
|
@@ -92966,7 +95138,7 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
92966
95138
|
outputRoot,
|
|
92967
95139
|
relativeDirPath: rootPath.relativeDirPath,
|
|
92968
95140
|
relativeFilePath: rootPath.relativeFilePath,
|
|
92969
|
-
frontmatter:
|
|
95141
|
+
frontmatter: this.getRootRuleFrontmatter(),
|
|
92970
95142
|
body: rulesyncRule.getBody(),
|
|
92971
95143
|
validate,
|
|
92972
95144
|
root: true,
|
|
@@ -92997,7 +95169,7 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
92997
95169
|
});
|
|
92998
95170
|
}
|
|
92999
95171
|
toRulesyncRule() {
|
|
93000
|
-
if (this.root) return this.toRulesyncRuleDefault();
|
|
95172
|
+
if (this.root) return this.toRulesyncRuleDefault({ body: this.body });
|
|
93001
95173
|
const strategy = STRATEGIES$1.find((s) => s.canHandle(this.frontmatter.trigger));
|
|
93002
95174
|
let rulesyncData = {
|
|
93003
95175
|
globs: [],
|
|
@@ -93075,6 +95247,9 @@ var AntigravityPluginRule = class extends AntigravityIdeRule {
|
|
|
93075
95247
|
static supportsSubprojectRules() {
|
|
93076
95248
|
return false;
|
|
93077
95249
|
}
|
|
95250
|
+
static getRootRuleFrontmatter() {
|
|
95251
|
+
return { trigger: "always_on" };
|
|
95252
|
+
}
|
|
93078
95253
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
93079
95254
|
const targets = rulesyncRule.getFrontmatter().targets;
|
|
93080
95255
|
return targets.includes("*") || targets.includes("antigravity-plugin");
|
|
@@ -94664,8 +96839,11 @@ var CodexcliRule = class extends NestedAgentsmdRule {
|
|
|
94664
96839
|
* `.codex/config.toml`, which Codex loads for trusted projects and which
|
|
94665
96840
|
* overrides the 32 KiB default budget for project `AGENTS.md` files.
|
|
94666
96841
|
*
|
|
94667
|
-
* Answers `
|
|
94668
|
-
*
|
|
96842
|
+
* Answers `0` when the key is set to `0`, which Codex treats as "project docs
|
|
96843
|
+
* disabled" (`codex-rs/core/src/agents_md.rs` loads nothing for a zero
|
|
96844
|
+
* budget). Answers `undefined` when the file is missing, is not valid TOML, or
|
|
96845
|
+
* does not set the key to a non-negative integer, so the caller falls back to
|
|
96846
|
+
* the default.
|
|
94669
96847
|
*
|
|
94670
96848
|
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
94671
96849
|
*/
|
|
@@ -94680,7 +96858,7 @@ async function readCodexcliProjectDocMaxBytes(outputRoot) {
|
|
|
94680
96858
|
}
|
|
94681
96859
|
if (!isPlainObject$1(parsed)) return;
|
|
94682
96860
|
const value = parsed.project_doc_max_bytes;
|
|
94683
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value
|
|
96861
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
94684
96862
|
}
|
|
94685
96863
|
//#endregion
|
|
94686
96864
|
//#region src/features/rules/commandcode-rule.ts
|
|
@@ -96042,10 +98220,13 @@ var DevinPluginRule = class extends DevinRule {
|
|
|
96042
98220
|
* Project scope writes the root `AGENTS.md` plus nested `<dir>/AGENTS.md`
|
|
96043
98221
|
* files — the project chain `dsh-agent-instructions` loads from the `.git`
|
|
96044
98222
|
* root down to the session cwd, broad to specific, so a per-directory file
|
|
96045
|
-
* applies only while working under that directory.
|
|
96046
|
-
*
|
|
96047
|
-
*
|
|
96048
|
-
*
|
|
98223
|
+
* applies only while working under that directory. A `localRoot` rule goes to
|
|
98224
|
+
* the project-root `AGENTS.local.md`, the additive local overlay the project
|
|
98225
|
+
* chain loads beside each `AGENTS.md`. Global scope writes the user-global
|
|
98226
|
+
* `~/.dsh/AGENTS.md` (`$DSH_HOME` default), which the harness reads as a single
|
|
98227
|
+
* root file with no `.local.md` overlay, so non-root rules fold into it. The
|
|
98228
|
+
* harness also reads a shared `~/.agents/AGENTS.md` (`$DSH_AGENTS_HOME`) after
|
|
98229
|
+
* it, which rulesync never writes.
|
|
96049
98230
|
*
|
|
96050
98231
|
* @see https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/context/agent-instructions/README.md
|
|
96051
98232
|
*/
|
|
@@ -97716,10 +99897,6 @@ var OmpRule = class OmpRule extends ToolRule {
|
|
|
97716
99897
|
}
|
|
97717
99898
|
};
|
|
97718
99899
|
//#endregion
|
|
97719
|
-
//#region src/constants/openclaw-paths.ts
|
|
97720
|
-
const OPENCLAW_WORKSPACE_DIR_PATH = join(".openclaw", "workspace");
|
|
97721
|
-
const OPENCLAW_RULE_FILE_NAME = "AGENTS.md";
|
|
97722
|
-
//#endregion
|
|
97723
99900
|
//#region src/features/rules/openclaw-rule.ts
|
|
97724
99901
|
var OpenclawRule = class OpenclawRule extends ToolRule {
|
|
97725
99902
|
constructor({ fileContent, root, ...rest }) {
|
|
@@ -99857,6 +102034,39 @@ const normalizeRelativeDir = (dir) => posix.normalize(toPosixPath(dir)).replace(
|
|
|
99857
102034
|
/** A tool rule's output path relative to its output root, for comparison. */
|
|
99858
102035
|
const toolOutputPath = (toolRule) => posix.join(normalizeRelativeDir(toolRule.getRelativeDirPath()), toolRule.getRelativeFilePath());
|
|
99859
102036
|
const formatRulePaths = (rules) => rules.map((r) => join(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
102037
|
+
/**
|
|
102038
|
+
* The leading `---` frontmatter block (with its closing delimiter line) that
|
|
102039
|
+
* every one of `contents` starts with, or `undefined` when one lacks it or the
|
|
102040
|
+
* blocks differ.
|
|
102041
|
+
*/
|
|
102042
|
+
const findSharedLeadingFrontmatter = (contents) => {
|
|
102043
|
+
const blocks = contents.map((content) => /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.exec(content)?.[0]);
|
|
102044
|
+
const first = blocks[0];
|
|
102045
|
+
return first !== void 0 && blocks.every((block) => block === first) ? first : void 0;
|
|
102046
|
+
};
|
|
102047
|
+
/**
|
|
102048
|
+
* Whether a group of rules colliding on one output path is composed into one
|
|
102049
|
+
* file, and the frontmatter block the composed file keeps, if any.
|
|
102050
|
+
*/
|
|
102051
|
+
const decideRuleComposition = ({ group, collisionPolicy }) => {
|
|
102052
|
+
const contents = group.map(({ toolRule }) => toolRule.getFileContent());
|
|
102053
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
102054
|
+
const allFragmentsArePlain = contents.every((content) => !/^---\r?\n/.test(content));
|
|
102055
|
+
const sharedRootFrontmatter = allGeneratedRulesAreRoots ? findSharedLeadingFrontmatter(contents) : void 0;
|
|
102056
|
+
return {
|
|
102057
|
+
shouldCompose: (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain || sharedRootFrontmatter !== void 0,
|
|
102058
|
+
sharedRootFrontmatter
|
|
102059
|
+
};
|
|
102060
|
+
};
|
|
102061
|
+
/**
|
|
102062
|
+
* Concatenate colliding rule fragments. With `sharedFrontmatter` (the block
|
|
102063
|
+
* every fragment starts with), the block is stripped from each fragment and
|
|
102064
|
+
* written once at the top.
|
|
102065
|
+
*/
|
|
102066
|
+
const composeRuleFragments = ({ contents, sharedFrontmatter }) => {
|
|
102067
|
+
const merged = contents.map((content) => content.slice(sharedFrontmatter?.length ?? 0).trim()).filter((content) => content.length > 0).join("\n\n");
|
|
102068
|
+
return sharedFrontmatter === void 0 ? merged : `${sharedFrontmatter}${merged}\n`;
|
|
102069
|
+
};
|
|
99860
102070
|
const RulesFeatureOptionsSchema = z.looseObject({
|
|
99861
102071
|
ruleDiscoveryMode: z.optional(z.enum(["none", "explicit"])),
|
|
99862
102072
|
includeLocalRoot: z.optional(z.boolean()),
|
|
@@ -100056,7 +102266,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
100056
102266
|
collisionPolicy: "fold",
|
|
100057
102267
|
projectInstructionBudget: {
|
|
100058
102268
|
bytes: CODEXCLI_PROJECT_DOC_MAX_BYTES,
|
|
100059
|
-
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default)
|
|
102269
|
+
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default): it silently truncates the file that crosses the limit and skips the files after it. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml` (Codex reads that file only if the project is trusted).",
|
|
100060
102270
|
readConfiguredBytes: readCodexcliProjectDocMaxBytes,
|
|
100061
102271
|
configuredSource: "`project_doc_max_bytes` in `.codex/config.toml` (honored only if the project is trusted)",
|
|
100062
102272
|
defaultLimitHint: "Rulesync does not read `~/.codex/config.toml`; ignore this warning if you already raised `project_doc_max_bytes` there."
|
|
@@ -100466,7 +102676,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
100466
102676
|
extension: "md",
|
|
100467
102677
|
supportsGlobal: true,
|
|
100468
102678
|
ruleDiscoveryMode: "auto",
|
|
100469
|
-
collisionPolicy: "fold"
|
|
102679
|
+
collisionPolicy: "fold",
|
|
102680
|
+
localRootMode: "separate-local-file",
|
|
102681
|
+
localRootFileName: "AGENTS.local.md"
|
|
100470
102682
|
}
|
|
100471
102683
|
}]
|
|
100472
102684
|
]);
|
|
@@ -100628,14 +102840,22 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100628
102840
|
* estimate can come out low there. The budget checked is the value set in the
|
|
100629
102841
|
* output root's tool config when the tool supports one (Codex's
|
|
100630
102842
|
* `project_doc_max_bytes` in `.codex/config.toml`), else the tool's default;
|
|
100631
|
-
* the warning names which one it used.
|
|
100632
|
-
*
|
|
100633
|
-
*
|
|
102843
|
+
* the warning names which one it used. A configured budget of `0` disables
|
|
102844
|
+
* project docs in Codex, so that case gets a single warning saying the
|
|
102845
|
+
* generated files will not be loaded instead of a size report. Global scope
|
|
102846
|
+
* is not checked because Codex loads the personal `~/.codex/AGENTS.md`
|
|
102847
|
+
* outside the project budget.
|
|
100634
102848
|
*/
|
|
100635
102849
|
async warnForInstructionBudget({ toolRules, meta }) {
|
|
100636
102850
|
const budget = meta.projectInstructionBudget;
|
|
100637
|
-
if (budget === void 0 || this.global) return;
|
|
102851
|
+
if (budget === void 0 || this.global || toolRules.length === 0) return;
|
|
100638
102852
|
const configuredBytes = await budget.readConfiguredBytes?.(this.outputRoot);
|
|
102853
|
+
const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
|
|
102854
|
+
if (configuredBytes === 0) {
|
|
102855
|
+
const paths = toolRules.map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
|
|
102856
|
+
this.logger.warn(`${budget.configuredSource ?? "The tool config"}${location} is set to 0, which disables ${this.toolTarget}'s project instruction files: the generated ${paths.join(", ")} will not be loaded. Remove the setting or set it to a positive byte count to load them.`);
|
|
102857
|
+
return;
|
|
102858
|
+
}
|
|
100639
102859
|
const limitBytes = configuredBytes ?? budget.bytes;
|
|
100640
102860
|
const limitLabel = budget.readConfiguredBytes === void 0 ? `${limitBytes}-byte limit` : configuredBytes === void 0 ? `${limitBytes}-byte default limit` : `${limitBytes}-byte limit set by ${budget.configuredSource ?? "the tool config"}`;
|
|
100641
102861
|
const remedy = configuredBytes === void 0 && budget.defaultLimitHint !== void 0 ? `${budget.remedy} ${budget.defaultLimitHint}` : budget.remedy;
|
|
@@ -100645,7 +102865,6 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100645
102865
|
bytes: Buffer.byteLength(addTrailingNewline(rule.getFileContent()), "utf8")
|
|
100646
102866
|
}));
|
|
100647
102867
|
const isAncestorOrSelf = (ancestor, dir) => ancestor === "." || ancestor === dir || dir.startsWith(`${ancestor}/`);
|
|
100648
|
-
const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
|
|
100649
102868
|
const rootFiles = files.filter(({ dir }) => dir === ".");
|
|
100650
102869
|
const rootBytes = rootFiles.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100651
102870
|
if (rootBytes > limitBytes) {
|
|
@@ -100953,10 +103172,11 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100953
103172
|
continue;
|
|
100954
103173
|
}
|
|
100955
103174
|
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
100956
|
-
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
100957
103175
|
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
100958
|
-
const
|
|
100959
|
-
|
|
103176
|
+
const { shouldCompose, sharedRootFrontmatter } = decideRuleComposition({
|
|
103177
|
+
group,
|
|
103178
|
+
collisionPolicy
|
|
103179
|
+
});
|
|
100960
103180
|
if (!shouldCompose && hasSourceRoot) throw new Error(`Multiple generated rules resolve to output path '${path}' for target '${this.toolTarget}', but this target cannot safely compose a collision involving a root rule. Source rules: ${formatRulePaths(group.map(({ rulesyncRule }) => rulesyncRule))}`);
|
|
100961
103181
|
if (!shouldCompose) {
|
|
100962
103182
|
for (const conversion of group) survivors.add(conversion);
|
|
@@ -100964,8 +103184,11 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100964
103184
|
}
|
|
100965
103185
|
const target = rootConversion ?? group[0];
|
|
100966
103186
|
if (!target) continue;
|
|
100967
|
-
const
|
|
100968
|
-
target.toolRule.setFileContent(
|
|
103187
|
+
const ordered = [target, ...group.filter((rule) => rule !== target)];
|
|
103188
|
+
target.toolRule.setFileContent(composeRuleFragments({
|
|
103189
|
+
contents: ordered.map(({ toolRule }) => toolRule.getFileContent()),
|
|
103190
|
+
sharedFrontmatter: sharedRootFrontmatter
|
|
103191
|
+
}));
|
|
100969
103192
|
survivors.add(target);
|
|
100970
103193
|
}
|
|
100971
103194
|
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
@@ -101049,7 +103272,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
101049
103272
|
/**
|
|
101050
103273
|
* Handle localRoot rule generation based on tool target.
|
|
101051
103274
|
* - `separate-local-file`: writes a dedicated `*.local.md` root file
|
|
101052
|
-
* (claudecode/legacy: `./CLAUDE.local.md`, rovodev: `./AGENTS.local.md`)
|
|
103275
|
+
* (claudecode/legacy: `./CLAUDE.local.md`, rovodev/dsh: `./AGENTS.local.md`)
|
|
101053
103276
|
* - `append-to-root` (default): appends the body to the root file
|
|
101054
103277
|
*/
|
|
101055
103278
|
handleLocalRootRule(toolRules, localRootRule, factory) {
|
|
@@ -101165,7 +103388,11 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
101165
103388
|
localRoot
|
|
101166
103389
|
});
|
|
101167
103390
|
}
|
|
101168
|
-
const workingRootClass = [
|
|
103391
|
+
const workingRootClass = [
|
|
103392
|
+
CrushRule,
|
|
103393
|
+
ReasonixRule,
|
|
103394
|
+
DshRule
|
|
103395
|
+
].find((base) => isClassOrSubclassOf({
|
|
101169
103396
|
candidate: factory.class,
|
|
101170
103397
|
base
|
|
101171
103398
|
}));
|
|
@@ -104563,6 +106790,6 @@ async function importChecksCore(params) {
|
|
|
104563
106790
|
return writtenCount;
|
|
104564
106791
|
}
|
|
104565
106792
|
//#endregion
|
|
104566
|
-
export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, parseCommaSeparatedList as Bn, getFileSizeAndExecutableBit as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_IGNORE_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, truncateText as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, ALL_FEATURES_WITH_WILDCARD as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as In, directoryExists as It, RulesyncRule as J, quoteForLog as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, hasDeceptiveHiddenCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_PERMISSIONS_SCHEMA_URL as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_RULES_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_USER_CONFIG_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CONFIG_SCHEMA_URL as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, DEPRECATED_FEATURE_REPLACEMENTS as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, formatError as Wn, isSymlink as Wt, RulesyncPermissions as X, stripControlCharactersKeepingLineFeeds as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharacters as Yn, posixRelativePathEscapesRoot as Yt, RulesyncMcp as Z, stripHiddenCharacters as Zn, readFileContentOrNull as Zt,
|
|
106793
|
+
export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, parseCommaSeparatedList as Bn, getFileSizeAndExecutableBit as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_IGNORE_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, truncateText as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, ALL_FEATURES_WITH_WILDCARD as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as In, directoryExists as It, RulesyncRule as J, quoteForLog as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, hasDeceptiveHiddenCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_PERMISSIONS_SCHEMA_URL as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_RULES_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_USER_CONFIG_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CONFIG_SCHEMA_URL as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, DEPRECATED_FEATURE_REPLACEMENTS as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, formatError as Wn, isSymlink as Wt, RulesyncPermissions as X, stripControlCharactersKeepingLineFeeds as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharacters as Yn, posixRelativePathEscapesRoot as Yt, RulesyncMcp as Z, stripHiddenCharacters as Zn, readFileContentOrNull as Zt, CRUSH_LOCAL_RULE_FILE_NAME as _, MAX_FILE_SIZE as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, LETTACODE_DIR as b, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, ALL_TOOL_TARGETS_WITH_WILDCARD as fn, mergeInputRootConfigs as ft, HooksProcessor as g, FEATURE_SOURCE_TREE_ENTRIES as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, CURATED_RULES_FEATURE_SUBDIR as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedRelativePath as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_MCP_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, ToolTargetSchema as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, PACKAGING_TOOL_TARGETS as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, hasEnclosingMarkOutsideKeycap as qn, listSubdirectoryNames as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, mapWithConcurrency as un, SKILL_FILE_NAME as ut, CommandsProcessor as v, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as wn, warnOnConflictingFlags as wt, LETTACODE_SETTINGS_LOCAL_FILE_NAME as x, RULESYNC_CONFIG_RELATIVE_FILE_PATH as xn, JsonLogger as xt, REASONIX_LOCAL_RULE_FILE_NAME as y, RULESYNC_CHECKS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as zn, findFilesByGlobs as zt };
|
|
104567
106794
|
|
|
104568
|
-
//# sourceMappingURL=import-
|
|
106795
|
+
//# sourceMappingURL=import-CoFTCNq7.js.map
|