rulesync 9.6.3 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/cli/index.cjs +10 -21
- package/dist/cli/index.js +10 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-A1yGuzwv.js → import-Dioh9ca8.js} +1135 -348
- package/dist/import-Dioh9ca8.js.map +1 -0
- package/dist/{import-DnpMhZOY.cjs → import-l0o1jVdc.cjs} +1143 -357
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-A1yGuzwv.js.map +0 -1
|
@@ -8,6 +8,7 @@ import { intersection, kebabCase, uniq } from "es-toolkit";
|
|
|
8
8
|
import { globbySync } from "globby";
|
|
9
9
|
import { isDeepStrictEqual } from "node:util";
|
|
10
10
|
import * as smolToml from "smol-toml";
|
|
11
|
+
import { parse as parse$1, stringify } from "smol-toml";
|
|
11
12
|
import matter from "gray-matter";
|
|
12
13
|
import { YAMLException, dump, load } from "js-yaml";
|
|
13
14
|
import { omit } from "es-toolkit/object";
|
|
@@ -321,7 +322,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
321
322
|
"junie",
|
|
322
323
|
"vibe",
|
|
323
324
|
"qwencode",
|
|
324
|
-
"reasonix"
|
|
325
|
+
"reasonix",
|
|
326
|
+
"grokcli"
|
|
325
327
|
];
|
|
326
328
|
const permissionsProcessorToolTargetTuple = [
|
|
327
329
|
"amp",
|
|
@@ -2677,6 +2679,13 @@ const CODEXCLI_MCP_FILE_NAME = "config.toml";
|
|
|
2677
2679
|
const CODEXCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
2678
2680
|
const CODEXCLI_BASH_RULES_FILE_NAME = "rulesync.rules";
|
|
2679
2681
|
const CODEXCLI_OPENAI_YAML_RELATIVE_PATH = join("agents", "openai.yaml");
|
|
2682
|
+
const CODEXCLI_OVERRIDE_KEYS = [
|
|
2683
|
+
"approval_policy",
|
|
2684
|
+
"sandbox_mode",
|
|
2685
|
+
"sandbox_workspace_write",
|
|
2686
|
+
"apps",
|
|
2687
|
+
"approvals_reviewer"
|
|
2688
|
+
];
|
|
2680
2689
|
//#endregion
|
|
2681
2690
|
//#region src/features/commands/codexcli-command.ts
|
|
2682
2691
|
const CodexcliCommandFrontmatterSchema = z.looseObject({
|
|
@@ -4055,14 +4064,22 @@ function sanitizeSharedConfigValue(value) {
|
|
|
4055
4064
|
* root follows `invalidRootPolicy`. Syntax errors are wrapped with the file
|
|
4056
4065
|
* path when one is given.
|
|
4057
4066
|
*/
|
|
4058
|
-
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty" }) {
|
|
4067
|
+
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty", jsoncParseErrors = "tolerate" }) {
|
|
4059
4068
|
if (fileContent.trim() === "") return {};
|
|
4060
4069
|
const at = filePath === void 0 ? "" : ` at ${filePath}`;
|
|
4061
4070
|
let parsed;
|
|
4062
4071
|
try {
|
|
4063
4072
|
if (format === "yaml") parsed = loadYaml(fileContent);
|
|
4073
|
+
else if (format === "toml") parsed = parse$1(fileContent);
|
|
4064
4074
|
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
4065
|
-
else
|
|
4075
|
+
else if (jsoncParseErrors === "error") {
|
|
4076
|
+
const errors = [];
|
|
4077
|
+
parsed = parse(fileContent, errors, { allowTrailingComma: true });
|
|
4078
|
+
if (errors.length > 0) {
|
|
4079
|
+
const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
|
|
4080
|
+
throw new Error(details);
|
|
4081
|
+
}
|
|
4082
|
+
} else parsed = parse(fileContent);
|
|
4066
4083
|
} catch (error) {
|
|
4067
4084
|
throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
|
|
4068
4085
|
}
|
|
@@ -4076,13 +4093,15 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
|
|
|
4076
4093
|
/**
|
|
4077
4094
|
* Serialize a shared config document. YAML output always ends with exactly one
|
|
4078
4095
|
* newline; JSON output matches the 2-space `JSON.stringify` shape the JSON
|
|
4079
|
-
* writers have always emitted (no trailing newline)
|
|
4096
|
+
* writers have always emitted (no trailing newline); TOML output matches the
|
|
4097
|
+
* `smol-toml` `stringify` shape the TOML writers have always emitted.
|
|
4080
4098
|
*/
|
|
4081
4099
|
function stringifySharedConfig({ format, document }) {
|
|
4082
4100
|
if (format === "yaml") return dump(document, {
|
|
4083
4101
|
noRefs: true,
|
|
4084
4102
|
sortKeys: false
|
|
4085
4103
|
}).trimEnd() + "\n";
|
|
4104
|
+
if (format === "toml") return stringify(document);
|
|
4086
4105
|
return JSON.stringify(document, null, 2);
|
|
4087
4106
|
}
|
|
4088
4107
|
/**
|
|
@@ -4121,6 +4140,25 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
4121
4140
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
4122
4141
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
4123
4142
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
4143
|
+
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
4144
|
+
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
4145
|
+
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
4146
|
+
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
4147
|
+
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
4148
|
+
/**
|
|
4149
|
+
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
4150
|
+
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
4151
|
+
* feature classes don't pull the processor registry through this module and
|
|
4152
|
+
* create an import cycle); the ownership lock-step test keeps the two aligned.
|
|
4153
|
+
* Lets a tool whose file lives at a scope-dependent path (`.zed/settings.json`
|
|
4154
|
+
* vs `.config/zed/settings.json`) resolve its declaration from the settable
|
|
4155
|
+
* paths it already holds.
|
|
4156
|
+
*/
|
|
4157
|
+
const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
4158
|
+
const dir = relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
4159
|
+
const file = relativeFilePath.replace(/\\/g, "/");
|
|
4160
|
+
return dir === "" || dir === "." ? file : `${dir}/${file}`;
|
|
4161
|
+
};
|
|
4124
4162
|
/**
|
|
4125
4163
|
* Who owns what in each gateway-managed shared config file, and which policy
|
|
4126
4164
|
* resolves conflicts. Keys are `dir/file` tokens matching
|
|
@@ -4177,6 +4215,298 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
4177
4215
|
},
|
|
4178
4216
|
permissions: { kind: "deep-merge" }
|
|
4179
4217
|
}
|
|
4218
|
+
},
|
|
4219
|
+
".zed/settings.json": {
|
|
4220
|
+
format: "json",
|
|
4221
|
+
features: {
|
|
4222
|
+
ignore: {
|
|
4223
|
+
kind: "replace-owned-keys",
|
|
4224
|
+
ownedKeys: ["private_files"]
|
|
4225
|
+
},
|
|
4226
|
+
mcp: {
|
|
4227
|
+
kind: "replace-owned-keys",
|
|
4228
|
+
ownedKeys: ["context_servers"]
|
|
4229
|
+
},
|
|
4230
|
+
permissions: {
|
|
4231
|
+
kind: "replace-owned-keys",
|
|
4232
|
+
ownedKeys: ["agent"]
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
},
|
|
4236
|
+
".config/zed/settings.json": {
|
|
4237
|
+
format: "json",
|
|
4238
|
+
features: {
|
|
4239
|
+
mcp: {
|
|
4240
|
+
kind: "replace-owned-keys",
|
|
4241
|
+
ownedKeys: ["context_servers"]
|
|
4242
|
+
},
|
|
4243
|
+
permissions: {
|
|
4244
|
+
kind: "replace-owned-keys",
|
|
4245
|
+
ownedKeys: ["agent"]
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
},
|
|
4249
|
+
".qwen/settings.json": {
|
|
4250
|
+
format: "json",
|
|
4251
|
+
features: {
|
|
4252
|
+
mcp: {
|
|
4253
|
+
kind: "replace-owned-keys",
|
|
4254
|
+
ownedKeys: ["mcpServers"]
|
|
4255
|
+
},
|
|
4256
|
+
hooks: {
|
|
4257
|
+
kind: "replace-owned-keys",
|
|
4258
|
+
ownedKeys: ["hooks", "disableAllHooks"]
|
|
4259
|
+
},
|
|
4260
|
+
permissions: {
|
|
4261
|
+
kind: "replace-owned-keys",
|
|
4262
|
+
ownedKeys: [
|
|
4263
|
+
"permissions",
|
|
4264
|
+
"tools",
|
|
4265
|
+
"security"
|
|
4266
|
+
]
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
},
|
|
4270
|
+
".augment/settings.json": {
|
|
4271
|
+
format: "json",
|
|
4272
|
+
features: {
|
|
4273
|
+
mcp: {
|
|
4274
|
+
kind: "replace-owned-keys",
|
|
4275
|
+
ownedKeys: ["mcpServers"]
|
|
4276
|
+
},
|
|
4277
|
+
hooks: {
|
|
4278
|
+
kind: "replace-owned-keys",
|
|
4279
|
+
ownedKeys: ["hooks"]
|
|
4280
|
+
},
|
|
4281
|
+
permissions: {
|
|
4282
|
+
kind: "replace-owned-keys",
|
|
4283
|
+
ownedKeys: ["toolPermissions"]
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
},
|
|
4287
|
+
".devin/config.json": {
|
|
4288
|
+
format: "json",
|
|
4289
|
+
features: {
|
|
4290
|
+
mcp: {
|
|
4291
|
+
kind: "replace-owned-keys",
|
|
4292
|
+
ownedKeys: ["mcpServers"]
|
|
4293
|
+
},
|
|
4294
|
+
permissions: {
|
|
4295
|
+
kind: "replace-owned-keys",
|
|
4296
|
+
ownedKeys: ["permissions"]
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
},
|
|
4300
|
+
".config/devin/config.json": {
|
|
4301
|
+
format: "json",
|
|
4302
|
+
features: {
|
|
4303
|
+
mcp: {
|
|
4304
|
+
kind: "replace-owned-keys",
|
|
4305
|
+
ownedKeys: ["mcpServers"]
|
|
4306
|
+
},
|
|
4307
|
+
hooks: {
|
|
4308
|
+
kind: "replace-owned-keys",
|
|
4309
|
+
ownedKeys: ["hooks"]
|
|
4310
|
+
},
|
|
4311
|
+
permissions: {
|
|
4312
|
+
kind: "replace-owned-keys",
|
|
4313
|
+
ownedKeys: ["permissions"]
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
},
|
|
4317
|
+
".kiro/agents/default.json": {
|
|
4318
|
+
format: "json",
|
|
4319
|
+
features: {
|
|
4320
|
+
hooks: {
|
|
4321
|
+
kind: "replace-owned-keys",
|
|
4322
|
+
ownedKeys: ["hooks"]
|
|
4323
|
+
},
|
|
4324
|
+
permissions: {
|
|
4325
|
+
kind: "replace-owned-keys",
|
|
4326
|
+
ownedKeys: ["allowedTools", "toolsSettings"]
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
},
|
|
4330
|
+
".amp/settings.json": {
|
|
4331
|
+
format: "jsonc",
|
|
4332
|
+
invalidRootPolicy: "error",
|
|
4333
|
+
jsoncParseErrors: "error",
|
|
4334
|
+
features: {
|
|
4335
|
+
mcp: {
|
|
4336
|
+
kind: "replace-owned-keys",
|
|
4337
|
+
ownedKeys: ["amp.mcpServers"]
|
|
4338
|
+
},
|
|
4339
|
+
permissions: {
|
|
4340
|
+
kind: "replace-owned-keys",
|
|
4341
|
+
ownedKeys: [
|
|
4342
|
+
"amp.tools.disable",
|
|
4343
|
+
"amp.permissions",
|
|
4344
|
+
"amp.guardedFiles.allowlist",
|
|
4345
|
+
"amp.dangerouslyAllowAll",
|
|
4346
|
+
"amp.mcpPermissions"
|
|
4347
|
+
]
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
},
|
|
4351
|
+
".config/amp/settings.json": {
|
|
4352
|
+
format: "jsonc",
|
|
4353
|
+
invalidRootPolicy: "error",
|
|
4354
|
+
jsoncParseErrors: "error",
|
|
4355
|
+
features: {
|
|
4356
|
+
mcp: {
|
|
4357
|
+
kind: "replace-owned-keys",
|
|
4358
|
+
ownedKeys: ["amp.mcpServers"]
|
|
4359
|
+
},
|
|
4360
|
+
permissions: {
|
|
4361
|
+
kind: "replace-owned-keys",
|
|
4362
|
+
ownedKeys: [
|
|
4363
|
+
"amp.tools.disable",
|
|
4364
|
+
"amp.permissions",
|
|
4365
|
+
"amp.guardedFiles.allowlist",
|
|
4366
|
+
"amp.dangerouslyAllowAll",
|
|
4367
|
+
"amp.mcpPermissions"
|
|
4368
|
+
]
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
},
|
|
4372
|
+
"opencode.json": {
|
|
4373
|
+
format: "jsonc",
|
|
4374
|
+
features: {
|
|
4375
|
+
mcp: {
|
|
4376
|
+
kind: "replace-owned-keys",
|
|
4377
|
+
ownedKeys: ["mcp", "tools"]
|
|
4378
|
+
},
|
|
4379
|
+
permissions: {
|
|
4380
|
+
kind: "replace-owned-keys",
|
|
4381
|
+
ownedKeys: ["permission"]
|
|
4382
|
+
},
|
|
4383
|
+
rules: {
|
|
4384
|
+
kind: "replace-owned-keys",
|
|
4385
|
+
ownedKeys: ["instructions"]
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
},
|
|
4389
|
+
".config/opencode/opencode.json": {
|
|
4390
|
+
format: "jsonc",
|
|
4391
|
+
features: {
|
|
4392
|
+
mcp: {
|
|
4393
|
+
kind: "replace-owned-keys",
|
|
4394
|
+
ownedKeys: ["mcp", "tools"]
|
|
4395
|
+
},
|
|
4396
|
+
permissions: {
|
|
4397
|
+
kind: "replace-owned-keys",
|
|
4398
|
+
ownedKeys: ["permission"]
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
},
|
|
4402
|
+
"kilo.json": {
|
|
4403
|
+
format: "jsonc",
|
|
4404
|
+
features: {
|
|
4405
|
+
mcp: {
|
|
4406
|
+
kind: "replace-owned-keys",
|
|
4407
|
+
ownedKeys: ["mcp", "tools"]
|
|
4408
|
+
},
|
|
4409
|
+
rules: {
|
|
4410
|
+
kind: "replace-owned-keys",
|
|
4411
|
+
ownedKeys: ["instructions"]
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
},
|
|
4415
|
+
".config/kilo/kilo.json": {
|
|
4416
|
+
format: "jsonc",
|
|
4417
|
+
features: { mcp: {
|
|
4418
|
+
kind: "replace-owned-keys",
|
|
4419
|
+
ownedKeys: ["mcp", "tools"]
|
|
4420
|
+
} }
|
|
4421
|
+
},
|
|
4422
|
+
[CODEXCLI_CONFIG_SHARED_FILE_KEY]: {
|
|
4423
|
+
format: "toml",
|
|
4424
|
+
features: {
|
|
4425
|
+
hooks: {
|
|
4426
|
+
kind: "replace-owned-keys",
|
|
4427
|
+
ownedKeys: ["features"]
|
|
4428
|
+
},
|
|
4429
|
+
mcp: {
|
|
4430
|
+
kind: "replace-owned-keys",
|
|
4431
|
+
ownedKeys: ["mcp_servers"]
|
|
4432
|
+
},
|
|
4433
|
+
permissions: {
|
|
4434
|
+
kind: "replace-owned-keys",
|
|
4435
|
+
ownedKeys: [
|
|
4436
|
+
"permissions",
|
|
4437
|
+
"default_permissions",
|
|
4438
|
+
...CODEXCLI_OVERRIDE_KEYS
|
|
4439
|
+
]
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
},
|
|
4443
|
+
[GROKCLI_CONFIG_SHARED_FILE_KEY]: {
|
|
4444
|
+
format: "toml",
|
|
4445
|
+
features: {
|
|
4446
|
+
mcp: {
|
|
4447
|
+
kind: "replace-owned-keys",
|
|
4448
|
+
ownedKeys: ["mcp_servers"]
|
|
4449
|
+
},
|
|
4450
|
+
permissions: {
|
|
4451
|
+
kind: "replace-owned-keys",
|
|
4452
|
+
ownedKeys: ["permission", "ui"]
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
},
|
|
4456
|
+
[VIBE_CONFIG_SHARED_FILE_KEY]: {
|
|
4457
|
+
format: "toml",
|
|
4458
|
+
features: {
|
|
4459
|
+
hooks: {
|
|
4460
|
+
kind: "replace-owned-keys",
|
|
4461
|
+
ownedKeys: ["enable_experimental_hooks"]
|
|
4462
|
+
},
|
|
4463
|
+
mcp: {
|
|
4464
|
+
kind: "replace-owned-keys",
|
|
4465
|
+
ownedKeys: ["mcp_servers"]
|
|
4466
|
+
},
|
|
4467
|
+
permissions: {
|
|
4468
|
+
kind: "replace-owned-keys",
|
|
4469
|
+
ownedKeys: [
|
|
4470
|
+
"tools",
|
|
4471
|
+
"enabled_tools",
|
|
4472
|
+
"disabled_tools"
|
|
4473
|
+
]
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
},
|
|
4477
|
+
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
4478
|
+
format: "toml",
|
|
4479
|
+
features: {
|
|
4480
|
+
mcp: {
|
|
4481
|
+
kind: "replace-owned-keys",
|
|
4482
|
+
ownedKeys: ["plugins"]
|
|
4483
|
+
},
|
|
4484
|
+
permissions: {
|
|
4485
|
+
kind: "replace-owned-keys",
|
|
4486
|
+
ownedKeys: [
|
|
4487
|
+
"permissions",
|
|
4488
|
+
"sandbox",
|
|
4489
|
+
"agent"
|
|
4490
|
+
]
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
},
|
|
4494
|
+
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
4495
|
+
format: "toml",
|
|
4496
|
+
features: {
|
|
4497
|
+
mcp: {
|
|
4498
|
+
kind: "replace-owned-keys",
|
|
4499
|
+
ownedKeys: ["plugins"]
|
|
4500
|
+
},
|
|
4501
|
+
permissions: {
|
|
4502
|
+
kind: "replace-owned-keys",
|
|
4503
|
+
ownedKeys: [
|
|
4504
|
+
"permissions",
|
|
4505
|
+
"sandbox",
|
|
4506
|
+
"agent"
|
|
4507
|
+
]
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4180
4510
|
}
|
|
4181
4511
|
};
|
|
4182
4512
|
/**
|
|
@@ -4197,17 +4527,20 @@ function applySharedConfigPatch({ fileKey, feature, existingContent, patch, file
|
|
|
4197
4527
|
format: declaration.format,
|
|
4198
4528
|
fileContent: existingContent,
|
|
4199
4529
|
filePath,
|
|
4200
|
-
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy }
|
|
4530
|
+
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy },
|
|
4531
|
+
...declaration.jsoncParseErrors !== void 0 && { jsoncParseErrors: declaration.jsoncParseErrors }
|
|
4201
4532
|
});
|
|
4202
4533
|
if (policy.kind === "replace-owned-keys") {
|
|
4203
4534
|
const unowned = Object.keys(patch).filter((key) => !policy.ownedKeys.includes(key));
|
|
4204
4535
|
if (unowned.length > 0) throw new Error(`Feature '${feature}' tried to write undeclared keys [${unowned.join(", ")}] to '${fileKey}'; extend its ownedKeys declaration if that ownership is intended.`);
|
|
4536
|
+
const document = mergeSharedConfigShallow({
|
|
4537
|
+
base,
|
|
4538
|
+
patch
|
|
4539
|
+
});
|
|
4540
|
+
for (const [key, value] of Object.entries(patch)) if (value === void 0) delete document[key];
|
|
4205
4541
|
return stringifySharedConfig({
|
|
4206
4542
|
format: declaration.format,
|
|
4207
|
-
document
|
|
4208
|
-
base,
|
|
4209
|
-
patch
|
|
4210
|
-
})
|
|
4543
|
+
document
|
|
4211
4544
|
});
|
|
4212
4545
|
}
|
|
4213
4546
|
const merged = mergeSharedConfigDeep({
|
|
@@ -5937,10 +6270,13 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
5937
6270
|
* Hook events supported by Devin Local (native `.devin/` hooks).
|
|
5938
6271
|
*
|
|
5939
6272
|
* Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
|
|
5940
|
-
*
|
|
5941
|
-
* `UserPromptSubmit`, `Stop`, `SessionStart`,
|
|
6273
|
+
* eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
6274
|
+
* `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
|
|
6275
|
+
* `PostCompaction` (fires after context compaction; added in the Devin CLI
|
|
6276
|
+
* stable changelog — https://docs.devin.ai/cli/changelog/stable). The
|
|
5942
6277
|
* tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
|
|
5943
|
-
* a `matcher` (regex against `tool_name`); the session/turn events
|
|
6278
|
+
* a `matcher` (regex against `tool_name`); the session/turn/compaction events
|
|
6279
|
+
* do not.
|
|
5944
6280
|
*
|
|
5945
6281
|
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
5946
6282
|
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
@@ -5955,7 +6291,8 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
5955
6291
|
"postToolUse",
|
|
5956
6292
|
"beforeSubmitPrompt",
|
|
5957
6293
|
"stop",
|
|
5958
|
-
"permissionRequest"
|
|
6294
|
+
"permissionRequest",
|
|
6295
|
+
"postCompact"
|
|
5959
6296
|
];
|
|
5960
6297
|
/** Hook events supported by OpenCode. */
|
|
5961
6298
|
const OPENCODE_HOOK_EVENTS = [
|
|
@@ -6149,7 +6486,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
|
|
|
6149
6486
|
* Hook events supported by AugmentCode (Auggie CLI).
|
|
6150
6487
|
* Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
|
|
6151
6488
|
* PreToolUse / PostToolUse (tool events, matcher-aware) plus the
|
|
6152
|
-
* SessionStart / SessionEnd / Stop session events (no matcher).
|
|
6489
|
+
* SessionStart / SessionEnd / Stop / Notification session events (no matcher).
|
|
6153
6490
|
* @see https://docs.augmentcode.com/cli/hooks
|
|
6154
6491
|
*/
|
|
6155
6492
|
const AUGMENTCODE_HOOK_EVENTS = [
|
|
@@ -6157,7 +6494,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
|
|
|
6157
6494
|
"postToolUse",
|
|
6158
6495
|
"sessionStart",
|
|
6159
6496
|
"sessionEnd",
|
|
6160
|
-
"stop"
|
|
6497
|
+
"stop",
|
|
6498
|
+
"notification"
|
|
6161
6499
|
];
|
|
6162
6500
|
/**
|
|
6163
6501
|
* Hook events supported by Mistral Vibe (mistral-vibe).
|
|
@@ -6209,6 +6547,10 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
6209
6547
|
* The Qwen-specific events
|
|
6210
6548
|
* `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
|
|
6211
6549
|
* `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
|
|
6550
|
+
* Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
|
|
6551
|
+
* `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
|
|
6552
|
+
* `InstructionsLoaded`, which map to the canonical `postToolBatch`,
|
|
6553
|
+
* `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
|
|
6212
6554
|
* @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
|
|
6213
6555
|
*/
|
|
6214
6556
|
const QWENCODE_HOOK_EVENTS = [
|
|
@@ -6217,7 +6559,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6217
6559
|
"preToolUse",
|
|
6218
6560
|
"postToolUse",
|
|
6219
6561
|
"postToolUseFailure",
|
|
6562
|
+
"postToolBatch",
|
|
6220
6563
|
"beforeSubmitPrompt",
|
|
6564
|
+
"userPromptExpansion",
|
|
6221
6565
|
"stop",
|
|
6222
6566
|
"stopFailure",
|
|
6223
6567
|
"subagentStart",
|
|
@@ -6225,7 +6569,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6225
6569
|
"preCompact",
|
|
6226
6570
|
"postCompact",
|
|
6227
6571
|
"permissionRequest",
|
|
6572
|
+
"permissionDenied",
|
|
6228
6573
|
"notification",
|
|
6574
|
+
"instructionsLoaded",
|
|
6229
6575
|
"todoCreated",
|
|
6230
6576
|
"todoCompleted"
|
|
6231
6577
|
];
|
|
@@ -6235,11 +6581,11 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6235
6581
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
6236
6582
|
* (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
|
|
6237
6583
|
* `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
|
|
6238
|
-
* `SubagentStop`, `Notification`, `PreCompact`).
|
|
6239
|
-
*
|
|
6584
|
+
* `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
|
|
6585
|
+
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
6240
6586
|
* `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
|
|
6241
|
-
* `SessionEnd`, `SubagentStop`,
|
|
6242
|
-
*
|
|
6587
|
+
* `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
|
|
6588
|
+
* `Notification` ← `notification`, and `PreCompact` ← `preCompact`.
|
|
6243
6589
|
* `match` (Reasonix's matcher field name) is honored only on
|
|
6244
6590
|
* `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
|
|
6245
6591
|
* tool-event scoping used by other adapters.
|
|
@@ -6253,7 +6599,39 @@ const REASONIX_HOOK_EVENTS = [
|
|
|
6253
6599
|
"sessionStart",
|
|
6254
6600
|
"sessionEnd",
|
|
6255
6601
|
"subagentStop",
|
|
6256
|
-
"postModelInvocation"
|
|
6602
|
+
"postModelInvocation",
|
|
6603
|
+
"notification",
|
|
6604
|
+
"preCompact"
|
|
6605
|
+
];
|
|
6606
|
+
/**
|
|
6607
|
+
* Hook events supported by Grok CLI (xAI Grok Build).
|
|
6608
|
+
*
|
|
6609
|
+
* Grok Build documents a Claude-Code-compatible hooks surface with fourteen
|
|
6610
|
+
* PascalCase events, all of which map 1:1 onto an existing canonical arm:
|
|
6611
|
+
* `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
|
|
6612
|
+
* `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
|
|
6613
|
+
* `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
|
|
6614
|
+
* `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
|
|
6615
|
+
* name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
|
|
6616
|
+
* `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
|
|
6617
|
+
* the remaining lifecycle events are matcher-less.
|
|
6618
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
6619
|
+
*/
|
|
6620
|
+
const GROKCLI_HOOK_EVENTS = [
|
|
6621
|
+
"sessionStart",
|
|
6622
|
+
"sessionEnd",
|
|
6623
|
+
"beforeSubmitPrompt",
|
|
6624
|
+
"preToolUse",
|
|
6625
|
+
"postToolUse",
|
|
6626
|
+
"postToolUseFailure",
|
|
6627
|
+
"permissionDenied",
|
|
6628
|
+
"stop",
|
|
6629
|
+
"stopFailure",
|
|
6630
|
+
"notification",
|
|
6631
|
+
"subagentStart",
|
|
6632
|
+
"subagentStop",
|
|
6633
|
+
"preCompact",
|
|
6634
|
+
"postCompact"
|
|
6257
6635
|
];
|
|
6258
6636
|
/**
|
|
6259
6637
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
@@ -6327,6 +6705,7 @@ const HooksConfigSchema = z.looseObject({
|
|
|
6327
6705
|
junie: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6328
6706
|
vibe: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6329
6707
|
reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6708
|
+
grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6330
6709
|
qwencode: z.optional(z.looseObject({
|
|
6331
6710
|
hooks: z.optional(hooksRecordSchema),
|
|
6332
6711
|
disableAllHooks: z.optional(z.boolean())
|
|
@@ -6385,7 +6764,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
|
6385
6764
|
postToolUse: "PostToolUse",
|
|
6386
6765
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6387
6766
|
stop: "Stop",
|
|
6388
|
-
permissionRequest: "PermissionRequest"
|
|
6767
|
+
permissionRequest: "PermissionRequest",
|
|
6768
|
+
postCompact: "PostCompaction"
|
|
6389
6769
|
};
|
|
6390
6770
|
/**
|
|
6391
6771
|
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
@@ -6400,7 +6780,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6400
6780
|
postToolUse: "PostToolUse",
|
|
6401
6781
|
sessionStart: "SessionStart",
|
|
6402
6782
|
sessionEnd: "SessionEnd",
|
|
6403
|
-
stop: "Stop"
|
|
6783
|
+
stop: "Stop",
|
|
6784
|
+
notification: "Notification"
|
|
6404
6785
|
};
|
|
6405
6786
|
/**
|
|
6406
6787
|
* Map AugmentCode PascalCase event names to canonical camelCase.
|
|
@@ -6670,7 +7051,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
6670
7051
|
preToolUse: "PreToolUse",
|
|
6671
7052
|
postToolUse: "PostToolUse",
|
|
6672
7053
|
postToolUseFailure: "PostToolUseFailure",
|
|
7054
|
+
postToolBatch: "PostToolBatch",
|
|
6673
7055
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7056
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
6674
7057
|
stop: "Stop",
|
|
6675
7058
|
subagentStart: "SubagentStart",
|
|
6676
7059
|
subagentStop: "SubagentStop",
|
|
@@ -6678,7 +7061,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
6678
7061
|
preCompact: "PreCompact",
|
|
6679
7062
|
postCompact: "PostCompact",
|
|
6680
7063
|
permissionRequest: "PermissionRequest",
|
|
7064
|
+
permissionDenied: "PermissionDenied",
|
|
6681
7065
|
notification: "Notification",
|
|
7066
|
+
instructionsLoaded: "InstructionsLoaded",
|
|
6682
7067
|
todoCreated: "TodoCreated",
|
|
6683
7068
|
todoCompleted: "TodoCompleted"
|
|
6684
7069
|
};
|
|
@@ -6700,12 +7085,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
6700
7085
|
sessionStart: "SessionStart",
|
|
6701
7086
|
sessionEnd: "SessionEnd",
|
|
6702
7087
|
subagentStop: "SubagentStop",
|
|
6703
|
-
postModelInvocation: "PostLLMCall"
|
|
7088
|
+
postModelInvocation: "PostLLMCall",
|
|
7089
|
+
notification: "Notification",
|
|
7090
|
+
preCompact: "PreCompact"
|
|
6704
7091
|
};
|
|
6705
7092
|
/**
|
|
6706
7093
|
* Map Reasonix PascalCase event names to canonical camelCase.
|
|
6707
7094
|
*/
|
|
6708
7095
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7096
|
+
/**
|
|
7097
|
+
* Map canonical camelCase event names to Grok CLI PascalCase.
|
|
7098
|
+
* Grok Build reuses the same Claude-style PascalCase event names, so each
|
|
7099
|
+
* canonical arm maps to its PascalCase equivalent.
|
|
7100
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
7101
|
+
*/
|
|
7102
|
+
const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
7103
|
+
sessionStart: "SessionStart",
|
|
7104
|
+
sessionEnd: "SessionEnd",
|
|
7105
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7106
|
+
preToolUse: "PreToolUse",
|
|
7107
|
+
postToolUse: "PostToolUse",
|
|
7108
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7109
|
+
permissionDenied: "PermissionDenied",
|
|
7110
|
+
stop: "Stop",
|
|
7111
|
+
stopFailure: "StopFailure",
|
|
7112
|
+
notification: "Notification",
|
|
7113
|
+
subagentStart: "SubagentStart",
|
|
7114
|
+
subagentStop: "SubagentStop",
|
|
7115
|
+
preCompact: "PreCompact",
|
|
7116
|
+
postCompact: "PostCompact"
|
|
7117
|
+
};
|
|
7118
|
+
/**
|
|
7119
|
+
* Map Grok CLI PascalCase event names to canonical camelCase.
|
|
7120
|
+
*/
|
|
7121
|
+
const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6709
7122
|
//#endregion
|
|
6710
7123
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
6711
7124
|
function isToolMatcherEntry(x) {
|
|
@@ -7230,7 +7643,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
|
|
|
7230
7643
|
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
7231
7644
|
"sessionStart",
|
|
7232
7645
|
"sessionEnd",
|
|
7233
|
-
"stop"
|
|
7646
|
+
"stop",
|
|
7647
|
+
"notification"
|
|
7234
7648
|
])
|
|
7235
7649
|
};
|
|
7236
7650
|
/**
|
|
@@ -7280,12 +7694,6 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
7280
7694
|
const paths = AugmentcodeHooks.getSettablePaths({ global });
|
|
7281
7695
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7282
7696
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
7283
|
-
let settings;
|
|
7284
|
-
try {
|
|
7285
|
-
settings = JSON.parse(existingContent);
|
|
7286
|
-
} catch (error) {
|
|
7287
|
-
throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7288
|
-
}
|
|
7289
7697
|
const config = rulesyncHooks.getJson();
|
|
7290
7698
|
const augmentHooks = canonicalToToolHooks({
|
|
7291
7699
|
config,
|
|
@@ -7293,11 +7701,13 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
7293
7701
|
converterConfig: AUGMENTCODE_CONVERTER_CONFIG,
|
|
7294
7702
|
logger
|
|
7295
7703
|
});
|
|
7296
|
-
const
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7704
|
+
const fileContent = applySharedConfigPatch({
|
|
7705
|
+
fileKey: sharedConfigFileKey(paths),
|
|
7706
|
+
feature: "hooks",
|
|
7707
|
+
existingContent,
|
|
7708
|
+
patch: { hooks: augmentHooks },
|
|
7709
|
+
filePath
|
|
7710
|
+
});
|
|
7301
7711
|
return new AugmentcodeHooks({
|
|
7302
7712
|
outputRoot,
|
|
7303
7713
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -7464,15 +7874,28 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
7464
7874
|
*/
|
|
7465
7875
|
async function buildCodexConfigTomlContent({ outputRoot }) {
|
|
7466
7876
|
const configPath = join(outputRoot, CODEXCLI_DIR, CODEXCLI_MCP_FILE_NAME);
|
|
7467
|
-
const existingContent = await readFileContentOrNull(configPath) ??
|
|
7877
|
+
const existingContent = await readFileContentOrNull(configPath) ?? "";
|
|
7468
7878
|
let configToml;
|
|
7469
7879
|
try {
|
|
7470
|
-
configToml = smolToml.parse(existingContent);
|
|
7880
|
+
configToml = smolToml.parse(existingContent || smolToml.stringify({}));
|
|
7471
7881
|
} catch (error) {
|
|
7472
7882
|
throw new Error(`Failed to parse existing Codex CLI config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
7473
7883
|
}
|
|
7474
|
-
|
|
7475
|
-
|
|
7884
|
+
let features;
|
|
7885
|
+
if (typeof configToml.features === "object" && configToml.features !== null) {
|
|
7886
|
+
features = { ...configToml.features };
|
|
7887
|
+
delete features.codex_hooks;
|
|
7888
|
+
}
|
|
7889
|
+
return applySharedConfigPatch({
|
|
7890
|
+
fileKey: sharedConfigFileKey({
|
|
7891
|
+
relativeDirPath: CODEXCLI_DIR,
|
|
7892
|
+
relativeFilePath: CODEXCLI_MCP_FILE_NAME
|
|
7893
|
+
}),
|
|
7894
|
+
feature: "hooks",
|
|
7895
|
+
existingContent,
|
|
7896
|
+
patch: { features },
|
|
7897
|
+
filePath: configPath
|
|
7898
|
+
});
|
|
7476
7899
|
}
|
|
7477
7900
|
/**
|
|
7478
7901
|
* Represents the `.codex/config.toml` file as a generated ToolFile,
|
|
@@ -8320,7 +8743,8 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
8320
8743
|
"sessionStart",
|
|
8321
8744
|
"sessionEnd",
|
|
8322
8745
|
"stop",
|
|
8323
|
-
"beforeSubmitPrompt"
|
|
8746
|
+
"beforeSubmitPrompt",
|
|
8747
|
+
"postCompact"
|
|
8324
8748
|
])
|
|
8325
8749
|
};
|
|
8326
8750
|
/**
|
|
@@ -8388,17 +8812,13 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
8388
8812
|
let fileContent;
|
|
8389
8813
|
if (global) {
|
|
8390
8814
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
...isRecord(parsedSettings) ? parsedSettings : {},
|
|
8399
|
-
hooks: devinHooks
|
|
8400
|
-
};
|
|
8401
|
-
fileContent = JSON.stringify(merged, null, 2);
|
|
8815
|
+
fileContent = applySharedConfigPatch({
|
|
8816
|
+
fileKey: sharedConfigFileKey(paths),
|
|
8817
|
+
feature: "hooks",
|
|
8818
|
+
existingContent,
|
|
8819
|
+
patch: { hooks: devinHooks },
|
|
8820
|
+
filePath
|
|
8821
|
+
});
|
|
8402
8822
|
} else {
|
|
8403
8823
|
await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8404
8824
|
fileContent = JSON.stringify(devinHooks, null, 2);
|
|
@@ -8633,6 +9053,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8633
9053
|
}
|
|
8634
9054
|
};
|
|
8635
9055
|
//#endregion
|
|
9056
|
+
//#region src/constants/grokcli-paths.ts
|
|
9057
|
+
/**
|
|
9058
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9059
|
+
*
|
|
9060
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9061
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9062
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9063
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9064
|
+
*
|
|
9065
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9066
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9067
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9068
|
+
* @see https://docs.x.ai/build/overview
|
|
9069
|
+
*/
|
|
9070
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9071
|
+
const GROKCLI_DIR = ".grok";
|
|
9072
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9073
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9074
|
+
/**
|
|
9075
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9076
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9077
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9078
|
+
*/
|
|
9079
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9080
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9081
|
+
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
9082
|
+
/**
|
|
9083
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9084
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9085
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9086
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9087
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9088
|
+
*/
|
|
9089
|
+
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
9090
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9091
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9092
|
+
/**
|
|
9093
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9094
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9095
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9096
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9097
|
+
*/
|
|
9098
|
+
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
9099
|
+
/**
|
|
9100
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9101
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9102
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9103
|
+
* MCP/skills/subagents adapters).
|
|
9104
|
+
*/
|
|
9105
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9106
|
+
//#endregion
|
|
9107
|
+
//#region src/features/hooks/grokcli-hooks.ts
|
|
9108
|
+
const GROKCLI_CONVERTER_CONFIG = {
|
|
9109
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
9110
|
+
canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
|
|
9111
|
+
toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
|
|
9112
|
+
projectDirVar: "",
|
|
9113
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
9114
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
9115
|
+
"sessionStart",
|
|
9116
|
+
"sessionEnd",
|
|
9117
|
+
"beforeSubmitPrompt",
|
|
9118
|
+
"stop",
|
|
9119
|
+
"stopFailure",
|
|
9120
|
+
"notification",
|
|
9121
|
+
"subagentStart",
|
|
9122
|
+
"subagentStop",
|
|
9123
|
+
"preCompact",
|
|
9124
|
+
"postCompact"
|
|
9125
|
+
])
|
|
9126
|
+
};
|
|
9127
|
+
/**
|
|
9128
|
+
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
9129
|
+
*
|
|
9130
|
+
* Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
|
|
9131
|
+
* maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
|
|
9132
|
+
* matcher groups under a top-level `hooks` key. rulesync writes all its hooks
|
|
9133
|
+
* into a single standalone `rulesync.json` file discovered from
|
|
9134
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
|
|
9135
|
+
* is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
|
|
9136
|
+
* as an orphan.
|
|
9137
|
+
*
|
|
9138
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9139
|
+
*/
|
|
9140
|
+
var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
9141
|
+
constructor(params) {
|
|
9142
|
+
super({
|
|
9143
|
+
...params,
|
|
9144
|
+
fileContent: params.fileContent ?? "{}"
|
|
9145
|
+
});
|
|
9146
|
+
}
|
|
9147
|
+
static getSettablePaths(_options = {}) {
|
|
9148
|
+
return {
|
|
9149
|
+
relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
|
|
9150
|
+
relativeFilePath: GROKCLI_HOOKS_FILE_NAME
|
|
9151
|
+
};
|
|
9152
|
+
}
|
|
9153
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9154
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9155
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
9156
|
+
return new GrokcliHooks({
|
|
9157
|
+
outputRoot,
|
|
9158
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9159
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9160
|
+
fileContent,
|
|
9161
|
+
validate
|
|
9162
|
+
});
|
|
9163
|
+
}
|
|
9164
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
9165
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9166
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9167
|
+
const config = rulesyncHooks.getJson();
|
|
9168
|
+
const grokHooks = canonicalToToolHooks({
|
|
9169
|
+
config,
|
|
9170
|
+
toolOverrideHooks: config.grokcli?.hooks,
|
|
9171
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
9172
|
+
logger
|
|
9173
|
+
});
|
|
9174
|
+
await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
|
|
9175
|
+
const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
|
|
9176
|
+
return new GrokcliHooks({
|
|
9177
|
+
outputRoot,
|
|
9178
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9179
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9180
|
+
fileContent,
|
|
9181
|
+
validate
|
|
9182
|
+
});
|
|
9183
|
+
}
|
|
9184
|
+
toRulesyncHooks() {
|
|
9185
|
+
let parsed;
|
|
9186
|
+
try {
|
|
9187
|
+
parsed = JSON.parse(this.getFileContent());
|
|
9188
|
+
} catch (error) {
|
|
9189
|
+
throw new Error(`Failed to parse Grok hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9190
|
+
}
|
|
9191
|
+
const hooks = toolHooksToCanonical({
|
|
9192
|
+
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
9193
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
9194
|
+
});
|
|
9195
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9196
|
+
version: 1,
|
|
9197
|
+
hooks
|
|
9198
|
+
}, null, 2) });
|
|
9199
|
+
}
|
|
9200
|
+
validate() {
|
|
9201
|
+
return {
|
|
9202
|
+
success: true,
|
|
9203
|
+
error: null
|
|
9204
|
+
};
|
|
9205
|
+
}
|
|
9206
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9207
|
+
return new GrokcliHooks({
|
|
9208
|
+
outputRoot,
|
|
9209
|
+
relativeDirPath,
|
|
9210
|
+
relativeFilePath,
|
|
9211
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
9212
|
+
validate: false
|
|
9213
|
+
});
|
|
9214
|
+
}
|
|
9215
|
+
};
|
|
9216
|
+
//#endregion
|
|
8636
9217
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8637
9218
|
/**
|
|
8638
9219
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -9210,18 +9791,14 @@ var KiroHooks = class KiroHooks extends ToolHooks {
|
|
|
9210
9791
|
const paths = KiroHooks.getSettablePaths({ global });
|
|
9211
9792
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9212
9793
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
9213
|
-
let agentConfig;
|
|
9214
|
-
try {
|
|
9215
|
-
agentConfig = JSON.parse(existingContent);
|
|
9216
|
-
} catch (error) {
|
|
9217
|
-
throw new Error(`Failed to parse existing Kiro agent config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
9218
|
-
}
|
|
9219
9794
|
const kiroHooks = canonicalToKiroHooks(rulesyncHooks.getJson(), this.getOverrideKey());
|
|
9220
|
-
const
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9795
|
+
const fileContent = applySharedConfigPatch({
|
|
9796
|
+
fileKey: sharedConfigFileKey(paths),
|
|
9797
|
+
feature: "hooks",
|
|
9798
|
+
existingContent,
|
|
9799
|
+
patch: { hooks: kiroHooks },
|
|
9800
|
+
filePath
|
|
9801
|
+
});
|
|
9225
9802
|
return new KiroHooks({
|
|
9226
9803
|
outputRoot,
|
|
9227
9804
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -9716,21 +10293,17 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
9716
10293
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
9717
10294
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9718
10295
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
9719
|
-
let settings;
|
|
9720
|
-
try {
|
|
9721
|
-
settings = JSON.parse(existingContent);
|
|
9722
|
-
} catch (error) {
|
|
9723
|
-
throw new Error(`Failed to parse existing Qwen Code settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
9724
|
-
}
|
|
9725
10296
|
const config = rulesyncHooks.getJson();
|
|
9726
|
-
const
|
|
9727
|
-
const merged = {
|
|
9728
|
-
...settings,
|
|
9729
|
-
hooks: qwencodeHooks
|
|
9730
|
-
};
|
|
10297
|
+
const patch = { hooks: canonicalToQwencodeHooks(config) };
|
|
9731
10298
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
9732
|
-
if (typeof disableAllHooks === "boolean")
|
|
9733
|
-
const fileContent =
|
|
10299
|
+
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
10300
|
+
const fileContent = applySharedConfigPatch({
|
|
10301
|
+
fileKey: sharedConfigFileKey(paths),
|
|
10302
|
+
feature: "hooks",
|
|
10303
|
+
existingContent,
|
|
10304
|
+
patch,
|
|
10305
|
+
filePath
|
|
10306
|
+
});
|
|
9734
10307
|
return new QwencodeHooks({
|
|
9735
10308
|
outputRoot,
|
|
9736
10309
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -9847,9 +10420,9 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
9847
10420
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
9848
10421
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
9849
10422
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
9850
|
-
*
|
|
10423
|
+
* All ten upstream events have a clean canonical equivalent and are mapped:
|
|
9851
10424
|
* PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
|
|
9852
|
-
* SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
|
|
10425
|
+
* SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
9853
10426
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
9854
10427
|
*/
|
|
9855
10428
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -9989,10 +10562,6 @@ function canonicalToVibeHooks(config, toolOverride) {
|
|
|
9989
10562
|
}
|
|
9990
10563
|
return { hooks };
|
|
9991
10564
|
}
|
|
9992
|
-
/**
|
|
9993
|
-
* Reverse {@link canonicalToVibeHooks}: parse the flat `[[hooks]]` array back
|
|
9994
|
-
* into a canonical event → definition[] record.
|
|
9995
|
-
*/
|
|
9996
10565
|
/** Convert one raw `[[hooks]]` entry to a canonical definition, or null to skip. */
|
|
9997
10566
|
function vibeEntryToCanonicalDef(raw) {
|
|
9998
10567
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
@@ -10012,6 +10581,10 @@ function vibeEntryToCanonicalDef(raw) {
|
|
|
10012
10581
|
def
|
|
10013
10582
|
};
|
|
10014
10583
|
}
|
|
10584
|
+
/**
|
|
10585
|
+
* Reverse {@link canonicalToVibeHooks}: parse the flat `[[hooks]]` array back
|
|
10586
|
+
* into a canonical event → definition[] record.
|
|
10587
|
+
*/
|
|
10015
10588
|
function vibeHooksToCanonical(parsed) {
|
|
10016
10589
|
const canonical = {};
|
|
10017
10590
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return canonical;
|
|
@@ -10040,15 +10613,22 @@ function parseVibeToml(fileContent) {
|
|
|
10040
10613
|
*/
|
|
10041
10614
|
async function buildVibeConfigTomlContent({ outputRoot }) {
|
|
10042
10615
|
const configPath = join(outputRoot, VIBE_DIR, VIBE_CONFIG_FILE_NAME);
|
|
10043
|
-
const existingContent = await readFileContentOrNull(configPath) ??
|
|
10044
|
-
let config;
|
|
10616
|
+
const existingContent = await readFileContentOrNull(configPath) ?? "";
|
|
10045
10617
|
try {
|
|
10046
|
-
|
|
10618
|
+
parseVibeToml(existingContent);
|
|
10047
10619
|
} catch (error) {
|
|
10048
10620
|
throw new Error(`Failed to parse existing Vibe config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
10049
10621
|
}
|
|
10050
|
-
|
|
10051
|
-
|
|
10622
|
+
return applySharedConfigPatch({
|
|
10623
|
+
fileKey: sharedConfigFileKey({
|
|
10624
|
+
relativeDirPath: VIBE_DIR,
|
|
10625
|
+
relativeFilePath: VIBE_CONFIG_FILE_NAME
|
|
10626
|
+
}),
|
|
10627
|
+
feature: "hooks",
|
|
10628
|
+
existingContent,
|
|
10629
|
+
patch: { enable_experimental_hooks: true },
|
|
10630
|
+
filePath: configPath
|
|
10631
|
+
});
|
|
10052
10632
|
}
|
|
10053
10633
|
/**
|
|
10054
10634
|
* Represents the `.vibe/config.toml` file as a generated ToolFile so it goes
|
|
@@ -10430,6 +11010,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
10430
11010
|
supportedEvents: REASONIX_HOOK_EVENTS,
|
|
10431
11011
|
supportedHookTypes: ["command"],
|
|
10432
11012
|
supportsMatcher: true
|
|
11013
|
+
}],
|
|
11014
|
+
["grokcli", {
|
|
11015
|
+
class: GrokcliHooks,
|
|
11016
|
+
meta: {
|
|
11017
|
+
supportsProject: true,
|
|
11018
|
+
supportsGlobal: true,
|
|
11019
|
+
supportsImport: true
|
|
11020
|
+
},
|
|
11021
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
11022
|
+
supportedHookTypes: ["command"],
|
|
11023
|
+
supportsMatcher: true
|
|
10433
11024
|
}]
|
|
10434
11025
|
]);
|
|
10435
11026
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -11548,17 +12139,18 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
11548
12139
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
11549
12140
|
const filePath = join(outputRoot, this.getSettablePaths().relativeDirPath, this.getSettablePaths().relativeFilePath);
|
|
11550
12141
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
11551
|
-
const
|
|
11552
|
-
const mergedPatterns = uniq([...existingJsonValue.private_files ?? [], ...patterns].toSorted());
|
|
11553
|
-
const jsonValue = {
|
|
11554
|
-
...existingJsonValue,
|
|
11555
|
-
private_files: mergedPatterns
|
|
11556
|
-
};
|
|
12142
|
+
const mergedPatterns = uniq([...JSON.parse(existingFileContent).private_files ?? [], ...patterns].toSorted());
|
|
11557
12143
|
return new ZedIgnore({
|
|
11558
12144
|
outputRoot,
|
|
11559
12145
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
11560
12146
|
relativeFilePath: this.getSettablePaths().relativeFilePath,
|
|
11561
|
-
fileContent:
|
|
12147
|
+
fileContent: applySharedConfigPatch({
|
|
12148
|
+
fileKey: sharedConfigFileKey(this.getSettablePaths()),
|
|
12149
|
+
feature: "ignore",
|
|
12150
|
+
existingContent: existingFileContent,
|
|
12151
|
+
patch: { private_files: mergedPatterns },
|
|
12152
|
+
filePath
|
|
12153
|
+
}),
|
|
11562
12154
|
validate: true
|
|
11563
12155
|
});
|
|
11564
12156
|
}
|
|
@@ -12015,17 +12607,17 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
12015
12607
|
const basePaths = this.getSettablePaths({ global });
|
|
12016
12608
|
const jsonDir = join(outputRoot, basePaths.relativeDirPath);
|
|
12017
12609
|
const { fileContent, relativeFilePath } = await this.resolveSettingsFile(jsonDir);
|
|
12018
|
-
const json = fileContent ? parseAmpSettingsJsonc(fileContent) : { [AMP_MCP_SERVERS_KEY]: {} };
|
|
12019
|
-
const filteredMcpServers = filterMcpServers(rulesyncMcp.getMcpServers());
|
|
12020
|
-
const newJson = {
|
|
12021
|
-
...json,
|
|
12022
|
-
[AMP_MCP_SERVERS_KEY]: filteredMcpServers
|
|
12023
|
-
};
|
|
12024
12610
|
return new AmpMcp({
|
|
12025
12611
|
outputRoot,
|
|
12026
12612
|
relativeDirPath: basePaths.relativeDirPath,
|
|
12027
12613
|
relativeFilePath,
|
|
12028
|
-
fileContent:
|
|
12614
|
+
fileContent: applySharedConfigPatch({
|
|
12615
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
12616
|
+
feature: "mcp",
|
|
12617
|
+
existingContent: fileContent ?? "",
|
|
12618
|
+
patch: { [AMP_MCP_SERVERS_KEY]: filterMcpServers(rulesyncMcp.getMcpServers()) },
|
|
12619
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
12620
|
+
}),
|
|
12029
12621
|
validate,
|
|
12030
12622
|
global
|
|
12031
12623
|
});
|
|
@@ -12312,15 +12904,19 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12312
12904
|
}
|
|
12313
12905
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12314
12906
|
const paths = this.getSettablePaths({ global });
|
|
12315
|
-
const
|
|
12316
|
-
|
|
12317
|
-
mcpServers: rulesyncMcp.getMcpServers()
|
|
12318
|
-
};
|
|
12907
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
12908
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
12319
12909
|
return new AugmentcodeMcp({
|
|
12320
12910
|
outputRoot,
|
|
12321
12911
|
relativeDirPath: paths.relativeDirPath,
|
|
12322
12912
|
relativeFilePath: paths.relativeFilePath,
|
|
12323
|
-
fileContent:
|
|
12913
|
+
fileContent: applySharedConfigPatch({
|
|
12914
|
+
fileKey: sharedConfigFileKey(paths),
|
|
12915
|
+
feature: "mcp",
|
|
12916
|
+
existingContent,
|
|
12917
|
+
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
12918
|
+
filePath
|
|
12919
|
+
}),
|
|
12324
12920
|
validate,
|
|
12325
12921
|
global
|
|
12326
12922
|
});
|
|
@@ -12680,7 +13276,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12680
13276
|
...rest,
|
|
12681
13277
|
validate: false
|
|
12682
13278
|
});
|
|
12683
|
-
|
|
13279
|
+
let toml;
|
|
13280
|
+
try {
|
|
13281
|
+
toml = smolToml.parse(this.fileContent);
|
|
13282
|
+
} catch (error) {
|
|
13283
|
+
throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
|
|
13284
|
+
}
|
|
13285
|
+
this.toml = toml;
|
|
12684
13286
|
if (rest.validate) {
|
|
12685
13287
|
const result = this.validate();
|
|
12686
13288
|
if (!result.success) throw result.error;
|
|
@@ -12714,8 +13316,14 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12714
13316
|
}
|
|
12715
13317
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12716
13318
|
const paths = this.getSettablePaths({ global });
|
|
12717
|
-
const
|
|
12718
|
-
const
|
|
13319
|
+
const configTomlFilePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13320
|
+
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13321
|
+
let configToml;
|
|
13322
|
+
try {
|
|
13323
|
+
configToml = smolToml.parse(configTomlFileContent || smolToml.stringify({}));
|
|
13324
|
+
} catch (error) {
|
|
13325
|
+
throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
|
|
13326
|
+
}
|
|
12719
13327
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
12720
13328
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
12721
13329
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
@@ -12728,7 +13336,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12728
13336
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
12729
13337
|
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(void 0, `MCP server "${name}" had no non-empty configuration and was dropped from the codex CLI config`);
|
|
12730
13338
|
const existingMcpServers = isRecord(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
12731
|
-
|
|
13339
|
+
const mergedMcpServers = Object.fromEntries(Object.entries(filteredMcpServers).map(([name, serverConfig]) => {
|
|
12732
13340
|
const existingServer = isRecord(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
12733
13341
|
const serverRecord = serverConfig;
|
|
12734
13342
|
if (existingServer && isRecord(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
@@ -12741,7 +13349,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12741
13349
|
outputRoot,
|
|
12742
13350
|
relativeDirPath: paths.relativeDirPath,
|
|
12743
13351
|
relativeFilePath: paths.relativeFilePath,
|
|
12744
|
-
fileContent:
|
|
13352
|
+
fileContent: applySharedConfigPatch({
|
|
13353
|
+
fileKey: sharedConfigFileKey(paths),
|
|
13354
|
+
feature: "mcp",
|
|
13355
|
+
existingContent: configTomlFileContent,
|
|
13356
|
+
patch: { mcp_servers: mergedMcpServers },
|
|
13357
|
+
filePath: configTomlFilePath
|
|
13358
|
+
}),
|
|
12745
13359
|
validate
|
|
12746
13360
|
});
|
|
12747
13361
|
}
|
|
@@ -13308,16 +13922,19 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
13308
13922
|
}
|
|
13309
13923
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13310
13924
|
const paths = this.getSettablePaths({ global });
|
|
13311
|
-
const
|
|
13312
|
-
const
|
|
13313
|
-
...this.parseJsonOrThrow(fileContent, paths.relativeDirPath, paths.relativeFilePath),
|
|
13314
|
-
mcpServers: rulesyncMcp.getMcpServers()
|
|
13315
|
-
};
|
|
13925
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13926
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({ mcpServers: {} }, null, 2));
|
|
13316
13927
|
return new DevinMcp({
|
|
13317
13928
|
outputRoot,
|
|
13318
13929
|
relativeDirPath: paths.relativeDirPath,
|
|
13319
13930
|
relativeFilePath: paths.relativeFilePath,
|
|
13320
|
-
fileContent:
|
|
13931
|
+
fileContent: applySharedConfigPatch({
|
|
13932
|
+
fileKey: sharedConfigFileKey(paths),
|
|
13933
|
+
feature: "mcp",
|
|
13934
|
+
existingContent,
|
|
13935
|
+
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
13936
|
+
filePath
|
|
13937
|
+
}),
|
|
13321
13938
|
validate,
|
|
13322
13939
|
global
|
|
13323
13940
|
});
|
|
@@ -13680,47 +14297,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
13680
14297
|
}
|
|
13681
14298
|
};
|
|
13682
14299
|
//#endregion
|
|
13683
|
-
//#region src/constants/grokcli-paths.ts
|
|
13684
|
-
/**
|
|
13685
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
13686
|
-
*
|
|
13687
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
13688
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
13689
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
13690
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
13691
|
-
*
|
|
13692
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
13693
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
13694
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
13695
|
-
* @see https://docs.x.ai/build/overview
|
|
13696
|
-
*/
|
|
13697
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
13698
|
-
const GROKCLI_DIR = ".grok";
|
|
13699
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
13700
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
13701
|
-
/**
|
|
13702
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
13703
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
13704
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
13705
|
-
*/
|
|
13706
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
13707
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
13708
|
-
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
13709
|
-
/**
|
|
13710
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
13711
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
13712
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
13713
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
13714
|
-
*/
|
|
13715
|
-
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
13716
|
-
/**
|
|
13717
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
13718
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
13719
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
13720
|
-
* MCP/skills/subagents adapters).
|
|
13721
|
-
*/
|
|
13722
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
13723
|
-
//#endregion
|
|
13724
14300
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
13725
14301
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
13726
14302
|
/**
|
|
@@ -13804,17 +14380,22 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
13804
14380
|
}
|
|
13805
14381
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13806
14382
|
const paths = this.getSettablePaths({ global });
|
|
13807
|
-
const
|
|
13808
|
-
const
|
|
14383
|
+
const configTomlFilePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
14384
|
+
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13809
14385
|
const converted = convertToGrokFormat(rulesyncMcp.getMcpServers());
|
|
13810
14386
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
13811
14387
|
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(void 0, `MCP server "${name}" had no non-empty configuration and was dropped from the grok CLI config`);
|
|
13812
|
-
configToml["mcp_servers"] = filteredMcpServers;
|
|
13813
14388
|
return new GrokcliMcp({
|
|
13814
14389
|
outputRoot,
|
|
13815
14390
|
relativeDirPath: paths.relativeDirPath,
|
|
13816
14391
|
relativeFilePath: paths.relativeFilePath,
|
|
13817
|
-
fileContent:
|
|
14392
|
+
fileContent: applySharedConfigPatch({
|
|
14393
|
+
fileKey: sharedConfigFileKey(paths),
|
|
14394
|
+
feature: "mcp",
|
|
14395
|
+
existingContent: configTomlFileContent,
|
|
14396
|
+
patch: { mcp_servers: filteredMcpServers },
|
|
14397
|
+
filePath: configTomlFilePath
|
|
14398
|
+
}),
|
|
13818
14399
|
validate
|
|
13819
14400
|
});
|
|
13820
14401
|
}
|
|
@@ -13875,6 +14456,54 @@ function resolveHermesTimeout(config) {
|
|
|
13875
14456
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
13876
14457
|
}
|
|
13877
14458
|
/**
|
|
14459
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
14460
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
14461
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
14462
|
+
* `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
|
|
14463
|
+
* from `source` to `target`. Field names are identical on both sides (the
|
|
14464
|
+
* canonical `McpServerSchema` is a `looseObject`), so this serves export and
|
|
14465
|
+
* import alike. See the Hermes mcp-config-reference.
|
|
14466
|
+
*/
|
|
14467
|
+
function copyHermesAdvancedFields(source, target) {
|
|
14468
|
+
if (typeof source.auth === "string") target.auth = source.auth;
|
|
14469
|
+
if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
|
|
14470
|
+
if (typeof source.client_key === "string") target.client_key = source.client_key;
|
|
14471
|
+
if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
|
|
14472
|
+
if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
|
|
14473
|
+
}
|
|
14474
|
+
/**
|
|
14475
|
+
* Builds Hermes's per-server `tools` block from a canonical server config. The
|
|
14476
|
+
* canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
|
|
14477
|
+
* and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
|
|
14478
|
+
* `prompts`/`resources` capability flags. Returns an empty object when the
|
|
14479
|
+
* server has no tool scoping (the caller omits the block in that case).
|
|
14480
|
+
*
|
|
14481
|
+
* Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
|
|
14482
|
+
* than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
|
|
14483
|
+
* is reserved as a `string[]` (used by other tools) — reusing it for an object
|
|
14484
|
+
* would fail validation on the next `generate`.
|
|
14485
|
+
*/
|
|
14486
|
+
function buildHermesToolsBlock(config) {
|
|
14487
|
+
const tools = {};
|
|
14488
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
14489
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14490
|
+
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
14491
|
+
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
14492
|
+
return tools;
|
|
14493
|
+
}
|
|
14494
|
+
/**
|
|
14495
|
+
* Applies a Hermes per-server `tools` block back onto a canonical server config
|
|
14496
|
+
* (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
|
|
14497
|
+
* `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
|
|
14498
|
+
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
14499
|
+
*/
|
|
14500
|
+
function applyHermesToolsBlock(hermesTools, server) {
|
|
14501
|
+
if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
14502
|
+
if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
14503
|
+
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
14504
|
+
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
14505
|
+
}
|
|
14506
|
+
/**
|
|
13878
14507
|
* Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
|
|
13879
14508
|
*
|
|
13880
14509
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
@@ -13908,9 +14537,8 @@ function convertServerToHermes(config) {
|
|
|
13908
14537
|
if (config.disabled === true) out.enabled = false;
|
|
13909
14538
|
const timeout = resolveHermesTimeout(config);
|
|
13910
14539
|
if (timeout !== void 0) out.timeout = timeout;
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14540
|
+
copyHermesAdvancedFields(config, out);
|
|
14541
|
+
const tools = buildHermesToolsBlock(config);
|
|
13914
14542
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
13915
14543
|
return out;
|
|
13916
14544
|
}
|
|
@@ -13953,10 +14581,8 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
13953
14581
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
13954
14582
|
if (config.enabled === false) server.disabled = true;
|
|
13955
14583
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
13959
|
-
}
|
|
14584
|
+
copyHermesAdvancedFields(config, server);
|
|
14585
|
+
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
13960
14586
|
result[name] = server;
|
|
13961
14587
|
}
|
|
13962
14588
|
return result;
|
|
@@ -14347,20 +14973,21 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
14347
14973
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
14348
14974
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
14349
14975
|
}
|
|
14350
|
-
if (!fileContent) fileContent = JSON.stringify({ mcp: {} }, null, 2);
|
|
14351
|
-
const json = parse(fileContent);
|
|
14352
14976
|
const { mcp: convertedMcp, tools: mcpTools } = convertToKiloFormat(rulesyncMcp.getMcpServers());
|
|
14353
|
-
const { tools: _existingTools, ...jsonWithoutTools } = json;
|
|
14354
|
-
const newJson = {
|
|
14355
|
-
...jsonWithoutTools,
|
|
14356
|
-
mcp: convertedMcp,
|
|
14357
|
-
...Object.keys(mcpTools).length > 0 && { tools: mcpTools }
|
|
14358
|
-
};
|
|
14359
14977
|
return new KiloMcp({
|
|
14360
14978
|
outputRoot,
|
|
14361
14979
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14362
14980
|
relativeFilePath,
|
|
14363
|
-
fileContent:
|
|
14981
|
+
fileContent: applySharedConfigPatch({
|
|
14982
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
14983
|
+
feature: "mcp",
|
|
14984
|
+
existingContent: fileContent ?? "",
|
|
14985
|
+
patch: {
|
|
14986
|
+
mcp: convertedMcp,
|
|
14987
|
+
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
14988
|
+
},
|
|
14989
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
14990
|
+
}),
|
|
14364
14991
|
validate
|
|
14365
14992
|
});
|
|
14366
14993
|
}
|
|
@@ -14389,15 +15016,17 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
14389
15016
|
const json = fileContent ? parse(fileContent) : {};
|
|
14390
15017
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
14391
15018
|
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...existingInstructions, ...instructions])).toSorted();
|
|
14392
|
-
const newJson = {
|
|
14393
|
-
...json,
|
|
14394
|
-
instructions: mergedInstructions
|
|
14395
|
-
};
|
|
14396
15019
|
return new KiloMcp({
|
|
14397
15020
|
outputRoot,
|
|
14398
15021
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14399
15022
|
relativeFilePath,
|
|
14400
|
-
fileContent:
|
|
15023
|
+
fileContent: applySharedConfigPatch({
|
|
15024
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15025
|
+
feature: "rules",
|
|
15026
|
+
existingContent: fileContent ?? "",
|
|
15027
|
+
patch: { instructions: mergedInstructions },
|
|
15028
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
15029
|
+
}),
|
|
14401
15030
|
validate
|
|
14402
15031
|
});
|
|
14403
15032
|
}
|
|
@@ -14669,23 +15298,24 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
14669
15298
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
14670
15299
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
14671
15300
|
}
|
|
14672
|
-
if (!fileContent) fileContent = JSON.stringify({ mcp: {} }, null, 2);
|
|
14673
|
-
const json = parse(fileContent);
|
|
14674
15301
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(convertEnvVarRefsToToolFormat({
|
|
14675
15302
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
14676
15303
|
replacement: "{env:$1}"
|
|
14677
15304
|
}));
|
|
14678
|
-
const { tools: _existingTools, ...jsonWithoutTools } = json;
|
|
14679
|
-
const newJson = {
|
|
14680
|
-
...jsonWithoutTools,
|
|
14681
|
-
mcp: convertedMcp,
|
|
14682
|
-
...Object.keys(mcpTools).length > 0 && { tools: mcpTools }
|
|
14683
|
-
};
|
|
14684
15305
|
return new OpencodeMcp({
|
|
14685
15306
|
outputRoot,
|
|
14686
15307
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14687
15308
|
relativeFilePath,
|
|
14688
|
-
fileContent:
|
|
15309
|
+
fileContent: applySharedConfigPatch({
|
|
15310
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15311
|
+
feature: "mcp",
|
|
15312
|
+
existingContent: fileContent ?? "",
|
|
15313
|
+
patch: {
|
|
15314
|
+
mcp: convertedMcp,
|
|
15315
|
+
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
15316
|
+
},
|
|
15317
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
15318
|
+
}),
|
|
14689
15319
|
validate
|
|
14690
15320
|
});
|
|
14691
15321
|
}
|
|
@@ -14720,15 +15350,17 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
14720
15350
|
const json = fileContent ? parse(fileContent) : {};
|
|
14721
15351
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
14722
15352
|
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...existingInstructions, ...instructions])).toSorted();
|
|
14723
|
-
const newJson = {
|
|
14724
|
-
...json,
|
|
14725
|
-
instructions: mergedInstructions
|
|
14726
|
-
};
|
|
14727
15353
|
return new OpencodeMcp({
|
|
14728
15354
|
outputRoot,
|
|
14729
15355
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14730
15356
|
relativeFilePath,
|
|
14731
|
-
fileContent:
|
|
15357
|
+
fileContent: applySharedConfigPatch({
|
|
15358
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15359
|
+
feature: "rules",
|
|
15360
|
+
existingContent: fileContent ?? "",
|
|
15361
|
+
patch: { instructions: mergedInstructions },
|
|
15362
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
15363
|
+
}),
|
|
14732
15364
|
validate
|
|
14733
15365
|
});
|
|
14734
15366
|
}
|
|
@@ -14838,16 +15470,19 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
14838
15470
|
}
|
|
14839
15471
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14840
15472
|
const paths = this.getSettablePaths({ global });
|
|
14841
|
-
const
|
|
14842
|
-
const
|
|
14843
|
-
...JSON.parse(fileContent),
|
|
14844
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers())
|
|
14845
|
-
};
|
|
15473
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15474
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({ mcpServers: {} }, null, 2));
|
|
14846
15475
|
return new QwencodeMcp({
|
|
14847
15476
|
outputRoot,
|
|
14848
15477
|
relativeDirPath: paths.relativeDirPath,
|
|
14849
15478
|
relativeFilePath: paths.relativeFilePath,
|
|
14850
|
-
fileContent:
|
|
15479
|
+
fileContent: applySharedConfigPatch({
|
|
15480
|
+
fileKey: sharedConfigFileKey(paths),
|
|
15481
|
+
feature: "mcp",
|
|
15482
|
+
existingContent,
|
|
15483
|
+
patch: { mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()) },
|
|
15484
|
+
filePath
|
|
15485
|
+
}),
|
|
14851
15486
|
validate
|
|
14852
15487
|
});
|
|
14853
15488
|
}
|
|
@@ -14932,13 +15567,20 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
14932
15567
|
}
|
|
14933
15568
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14934
15569
|
const paths = this.getSettablePaths({ global });
|
|
14935
|
-
const
|
|
14936
|
-
|
|
15570
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15571
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
15572
|
+
const plugins = Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => rulesyncMcpServerToReasonix(name, server));
|
|
14937
15573
|
return new ReasonixMcp({
|
|
14938
15574
|
outputRoot,
|
|
14939
15575
|
relativeDirPath: paths.relativeDirPath,
|
|
14940
15576
|
relativeFilePath: paths.relativeFilePath,
|
|
14941
|
-
fileContent:
|
|
15577
|
+
fileContent: applySharedConfigPatch({
|
|
15578
|
+
fileKey: sharedConfigFileKey(paths),
|
|
15579
|
+
feature: "mcp",
|
|
15580
|
+
existingContent,
|
|
15581
|
+
patch: { plugins },
|
|
15582
|
+
filePath
|
|
15583
|
+
}),
|
|
14942
15584
|
validate,
|
|
14943
15585
|
global
|
|
14944
15586
|
});
|
|
@@ -15413,13 +16055,20 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
15413
16055
|
}
|
|
15414
16056
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
15415
16057
|
const paths = this.getSettablePaths({ global });
|
|
15416
|
-
const
|
|
15417
|
-
|
|
16058
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16059
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
16060
|
+
const mcpServers = Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => rulesyncMcpServerToVibe(name, server));
|
|
15418
16061
|
return new VibeMcp({
|
|
15419
16062
|
outputRoot,
|
|
15420
16063
|
relativeDirPath: paths.relativeDirPath,
|
|
15421
16064
|
relativeFilePath: paths.relativeFilePath,
|
|
15422
|
-
fileContent:
|
|
16065
|
+
fileContent: applySharedConfigPatch({
|
|
16066
|
+
fileKey: sharedConfigFileKey(paths),
|
|
16067
|
+
feature: "mcp",
|
|
16068
|
+
existingContent,
|
|
16069
|
+
patch: { mcp_servers: mcpServers },
|
|
16070
|
+
filePath
|
|
16071
|
+
}),
|
|
15423
16072
|
validate,
|
|
15424
16073
|
global
|
|
15425
16074
|
});
|
|
@@ -15650,16 +16299,19 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
15650
16299
|
}
|
|
15651
16300
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
15652
16301
|
const paths = this.getSettablePaths({ global });
|
|
15653
|
-
const
|
|
15654
|
-
const
|
|
15655
|
-
...JSON.parse(fileContent),
|
|
15656
|
-
context_servers: rulesyncMcp.getMcpServers()
|
|
15657
|
-
};
|
|
16302
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16303
|
+
const existingContent = await readOrInitializeFileContent(filePath, "{}");
|
|
15658
16304
|
return new ZedMcp({
|
|
15659
16305
|
outputRoot,
|
|
15660
16306
|
relativeDirPath: paths.relativeDirPath,
|
|
15661
16307
|
relativeFilePath: paths.relativeFilePath,
|
|
15662
|
-
fileContent:
|
|
16308
|
+
fileContent: applySharedConfigPatch({
|
|
16309
|
+
fileKey: sharedConfigFileKey(paths),
|
|
16310
|
+
feature: "mcp",
|
|
16311
|
+
existingContent,
|
|
16312
|
+
patch: { context_servers: rulesyncMcp.getMcpServers() },
|
|
16313
|
+
filePath
|
|
16314
|
+
}),
|
|
15663
16315
|
validate
|
|
15664
16316
|
});
|
|
15665
16317
|
}
|
|
@@ -16225,18 +16877,27 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
16225
16877
|
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
16226
16878
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
16227
16879
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
16228
|
-
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`).
|
|
16229
|
-
*
|
|
16230
|
-
*
|
|
16231
|
-
*
|
|
16232
|
-
*
|
|
16880
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
|
|
16881
|
+
* exposes `permissions.autoMode` (the Auto Mode classifier config:
|
|
16882
|
+
* `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
|
|
16883
|
+
* https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
|
|
16884
|
+
* likewise has no canonical category. Fields placed here are merged into the
|
|
16885
|
+
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
16886
|
+
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
16887
|
+
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
16888
|
+
* `tools`/`security`/`autoMode` key can be authored.
|
|
16233
16889
|
*
|
|
16234
16890
|
* @example
|
|
16235
|
-
* {
|
|
16891
|
+
* {
|
|
16892
|
+
* "tools": { "approvalMode": "auto-edit" },
|
|
16893
|
+
* "security": { "folderTrust": { "enabled": true } },
|
|
16894
|
+
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
16895
|
+
* }
|
|
16236
16896
|
*/
|
|
16237
16897
|
const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
16238
16898
|
tools: z.optional(z.looseObject({})),
|
|
16239
|
-
security: z.optional(z.looseObject({}))
|
|
16899
|
+
security: z.optional(z.looseObject({})),
|
|
16900
|
+
autoMode: z.optional(z.looseObject({}))
|
|
16240
16901
|
});
|
|
16241
16902
|
/**
|
|
16242
16903
|
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
@@ -16263,9 +16924,11 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
16263
16924
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
16264
16925
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
16265
16926
|
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
16266
|
-
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
16927
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
16928
|
+
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
16267
16929
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
16268
|
-
* `interactionMode`). Fields placed here are merged
|
|
16930
|
+
* `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
|
|
16931
|
+
* into `settings.json` and
|
|
16269
16932
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
16270
16933
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
16271
16934
|
*
|
|
@@ -16458,6 +17121,41 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
|
|
|
16458
17121
|
}))
|
|
16459
17122
|
})) });
|
|
16460
17123
|
/**
|
|
17124
|
+
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
17125
|
+
* `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
|
|
17126
|
+
* Codex still accepts, so it is included so existing configs round-trip. The
|
|
17127
|
+
* granular table form (`{ granular = { … } }`) is modeled separately in the
|
|
17128
|
+
* override union.
|
|
17129
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17130
|
+
*/
|
|
17131
|
+
const CodexApprovalPolicySchema = z.enum([
|
|
17132
|
+
"untrusted",
|
|
17133
|
+
"on-request",
|
|
17134
|
+
"on-failure",
|
|
17135
|
+
"never"
|
|
17136
|
+
]);
|
|
17137
|
+
/**
|
|
17138
|
+
* Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
|
|
17139
|
+
* `.codex/config.toml`.
|
|
17140
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17141
|
+
*/
|
|
17142
|
+
const CodexSandboxModeSchema = z.enum([
|
|
17143
|
+
"read-only",
|
|
17144
|
+
"workspace-write",
|
|
17145
|
+
"danger-full-access"
|
|
17146
|
+
]);
|
|
17147
|
+
/**
|
|
17148
|
+
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
17149
|
+
* value Codex still accepts for backward compatibility, so it is included so
|
|
17150
|
+
* existing configs round-trip through the rulesync model.
|
|
17151
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17152
|
+
*/
|
|
17153
|
+
const CodexApprovalsReviewerSchema = z.enum([
|
|
17154
|
+
"user",
|
|
17155
|
+
"auto_review",
|
|
17156
|
+
"guardian_subagent"
|
|
17157
|
+
]);
|
|
17158
|
+
/**
|
|
16461
17159
|
* Codex CLI-scoped permission override.
|
|
16462
17160
|
*
|
|
16463
17161
|
* Codex CLI's permission surface is richer than the canonical allow/ask/deny
|
|
@@ -16466,15 +17164,16 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
|
|
|
16466
17164
|
* override whose fields are written verbatim as top-level `.codex/config.toml`
|
|
16467
17165
|
* keys (the override wins per key; existing sibling keys the user set directly
|
|
16468
17166
|
* are preserved):
|
|
16469
|
-
* - `approval_policy` — `untrusted` | `on-request`
|
|
16470
|
-
* `{ granular = { … } }` table (kept verbatim; the granular
|
|
16471
|
-
* required fields that are brittle to model as typed keys).
|
|
17167
|
+
* - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
|
|
17168
|
+
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
17169
|
+
* schema has required fields that are brittle to model as typed keys).
|
|
16472
17170
|
* - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
|
|
16473
17171
|
* with the sibling `sandbox_workspace_write` table (`network_access`,
|
|
16474
17172
|
* `writable_roots`, …).
|
|
16475
17173
|
* - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
|
|
16476
17174
|
* `.enabled`, `apps.<id>.default_tools_approval_mode`).
|
|
16477
|
-
* - `approvals_reviewer` — the reviewer-approval surface
|
|
17175
|
+
* - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
|
|
17176
|
+
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
16478
17177
|
*
|
|
16479
17178
|
* Two surfaces are deliberately NOT authorable here so the override can never
|
|
16480
17179
|
* clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
|
|
@@ -16492,11 +17191,11 @@ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(
|
|
|
16492
17191
|
* "sandbox_workspace_write": { "network_access": true } }
|
|
16493
17192
|
*/
|
|
16494
17193
|
const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
16495
|
-
approval_policy: z.optional(z.union([
|
|
16496
|
-
sandbox_mode: z.optional(
|
|
17194
|
+
approval_policy: z.optional(z.union([CodexApprovalPolicySchema, z.looseObject({})])),
|
|
17195
|
+
sandbox_mode: z.optional(CodexSandboxModeSchema),
|
|
16497
17196
|
sandbox_workspace_write: z.optional(z.looseObject({})),
|
|
16498
17197
|
apps: z.optional(z.looseObject({})),
|
|
16499
|
-
approvals_reviewer: z.optional(z.union([
|
|
17198
|
+
approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})]))
|
|
16500
17199
|
});
|
|
16501
17200
|
/**
|
|
16502
17201
|
* Permissions configuration.
|
|
@@ -16828,25 +17527,29 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16828
17527
|
const override = config.amp;
|
|
16829
17528
|
const authoredExtras = override?.permissions ? toPermissionsList(override.permissions) : [];
|
|
16830
17529
|
const preservedDelegates = override?.permissions ? [] : toPermissionsList(json[AMP_PERMISSIONS_KEY]).filter((entry) => entry.action === "delegate");
|
|
16831
|
-
const newJson = {
|
|
16832
|
-
...json,
|
|
16833
|
-
[AMP_TOOLS_DISABLE_KEY]: disable
|
|
16834
|
-
};
|
|
16835
17530
|
const mergedPermissions = mergeAmpPermissions([
|
|
16836
17531
|
...permissions,
|
|
16837
17532
|
...authoredExtras,
|
|
16838
17533
|
...preservedDelegates
|
|
16839
17534
|
]);
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
if (override?.
|
|
17535
|
+
const patch = {
|
|
17536
|
+
[AMP_TOOLS_DISABLE_KEY]: disable,
|
|
17537
|
+
[AMP_PERMISSIONS_KEY]: mergedPermissions.length > 0 ? mergedPermissions : void 0
|
|
17538
|
+
};
|
|
17539
|
+
if (override?.guardedFiles?.allowlist !== void 0) patch[AMP_GUARDED_FILES_ALLOWLIST_KEY] = override.guardedFiles.allowlist;
|
|
17540
|
+
if (override?.dangerouslyAllowAll !== void 0) patch[AMP_DANGEROUSLY_ALLOW_ALL_KEY] = override.dangerouslyAllowAll;
|
|
17541
|
+
if (override?.mcpPermissions !== void 0) patch[AMP_MCP_PERMISSIONS_KEY] = override.mcpPermissions;
|
|
16845
17542
|
return new AmpPermissions({
|
|
16846
17543
|
outputRoot,
|
|
16847
17544
|
relativeDirPath: basePaths.relativeDirPath,
|
|
16848
17545
|
relativeFilePath,
|
|
16849
|
-
fileContent:
|
|
17546
|
+
fileContent: applySharedConfigPatch({
|
|
17547
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
17548
|
+
feature: "permissions",
|
|
17549
|
+
existingContent: fileContent ?? "",
|
|
17550
|
+
patch,
|
|
17551
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
17552
|
+
}),
|
|
16850
17553
|
validate: true
|
|
16851
17554
|
});
|
|
16852
17555
|
}
|
|
@@ -17788,11 +18491,13 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
|
|
|
17788
18491
|
...preservedBasicEntries,
|
|
17789
18492
|
...authoredBasics
|
|
17790
18493
|
]);
|
|
17791
|
-
const
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
18494
|
+
const fileContent = applySharedConfigPatch({
|
|
18495
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18496
|
+
feature: "permissions",
|
|
18497
|
+
existingContent,
|
|
18498
|
+
patch: { toolPermissions: [...specialEntries, ...sortedBasic] },
|
|
18499
|
+
filePath
|
|
18500
|
+
});
|
|
17796
18501
|
return new AugmentcodePermissions({
|
|
17797
18502
|
outputRoot,
|
|
17798
18503
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -18384,13 +19089,6 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
18384
19089
|
]);
|
|
18385
19090
|
const CODEX_MINIMAL_KEY = ":minimal";
|
|
18386
19091
|
const GLOBAL_WILDCARD_DOMAIN = "*";
|
|
18387
|
-
const CODEXCLI_OVERRIDE_KEYS = [
|
|
18388
|
-
"approval_policy",
|
|
18389
|
-
"sandbox_mode",
|
|
18390
|
-
"sandbox_workspace_write",
|
|
18391
|
-
"apps",
|
|
18392
|
-
"approvals_reviewer"
|
|
18393
|
-
];
|
|
18394
19092
|
var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
18395
19093
|
static getSettablePaths(_options = {}) {
|
|
18396
19094
|
return {
|
|
@@ -18414,26 +19112,27 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18414
19112
|
}
|
|
18415
19113
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
18416
19114
|
const paths = this.getSettablePaths({ global });
|
|
18417
|
-
const
|
|
18418
|
-
const
|
|
19115
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19116
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
19117
|
+
const existing = toMutableTable(smolToml.parse(existingContent || smolToml.stringify({})));
|
|
18419
19118
|
const newProfile = convertRulesyncToCodexProfile({
|
|
18420
19119
|
config: rulesyncPermissions.getJson(),
|
|
18421
19120
|
logger
|
|
18422
19121
|
});
|
|
18423
|
-
const permissionsTable = toMutableTable(
|
|
19122
|
+
const permissionsTable = toMutableTable(existing.permissions);
|
|
18424
19123
|
const { profile: existingProfile, domainsHadUnknown: existingDomainsHadUnknown } = toCodexProfile(permissionsTable[RULESYNC_PROFILE_NAME]);
|
|
18425
|
-
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
19124
|
+
warnAboutPreservedProfileState({
|
|
19125
|
+
existingProfile,
|
|
19126
|
+
newProfile,
|
|
19127
|
+
existingDomainsHadUnknown,
|
|
19128
|
+
logger
|
|
19129
|
+
});
|
|
18429
19130
|
permissionsTable[RULESYNC_PROFILE_NAME] = mergeWithExistingProfile({
|
|
18430
19131
|
newProfile,
|
|
18431
19132
|
existingProfile
|
|
18432
19133
|
});
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
applyCodexcliOverride({
|
|
18436
|
-
parsed,
|
|
19134
|
+
const overridePatch = computeCodexcliOverridePatch({
|
|
19135
|
+
existing,
|
|
18437
19136
|
override: rulesyncPermissions.getJson().codexcli,
|
|
18438
19137
|
logger
|
|
18439
19138
|
});
|
|
@@ -18441,7 +19140,17 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18441
19140
|
outputRoot,
|
|
18442
19141
|
relativeDirPath: paths.relativeDirPath,
|
|
18443
19142
|
relativeFilePath: paths.relativeFilePath,
|
|
18444
|
-
fileContent:
|
|
19143
|
+
fileContent: applySharedConfigPatch({
|
|
19144
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19145
|
+
feature: "permissions",
|
|
19146
|
+
existingContent,
|
|
19147
|
+
patch: {
|
|
19148
|
+
permissions: permissionsTable,
|
|
19149
|
+
default_permissions: RULESYNC_PROFILE_NAME,
|
|
19150
|
+
...overridePatch
|
|
19151
|
+
},
|
|
19152
|
+
filePath
|
|
19153
|
+
}),
|
|
18445
19154
|
validate
|
|
18446
19155
|
});
|
|
18447
19156
|
}
|
|
@@ -18611,6 +19320,12 @@ function toCodexProfile(value) {
|
|
|
18611
19320
|
domainsHadUnknown
|
|
18612
19321
|
};
|
|
18613
19322
|
}
|
|
19323
|
+
function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
|
|
19324
|
+
if (existingProfile?.extends !== void 0 && existingProfile.extends !== newProfile.extends) logger?.warn(`Existing "extends" value "${existingProfile.extends}" will be replaced by Rulesync-managed "${newProfile.extends ?? "(none)"}".`);
|
|
19325
|
+
if (existingProfile?.network?.unix_sockets !== void 0) logger?.warn(`Preserving existing "network.unix_sockets" from config. Review these entries manually as they may grant broad system access.`);
|
|
19326
|
+
if (existingProfile?.network?.mode !== void 0) logger?.warn(`Preserving existing "network.mode" from config. Review this value manually as it may grant broader network access than the Rulesync-managed domain rules.`);
|
|
19327
|
+
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19328
|
+
}
|
|
18614
19329
|
function mergeWithExistingProfile({ newProfile, existingProfile }) {
|
|
18615
19330
|
if (!existingProfile) return newProfile;
|
|
18616
19331
|
const mergedNetwork = { ...newProfile.network };
|
|
@@ -18661,8 +19376,9 @@ function toMutableTable(value) {
|
|
|
18661
19376
|
function isPlainObject(value) {
|
|
18662
19377
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18663
19378
|
}
|
|
18664
|
-
function
|
|
18665
|
-
|
|
19379
|
+
function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
19380
|
+
const patch = {};
|
|
19381
|
+
if (!override) return patch;
|
|
18666
19382
|
const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
|
|
18667
19383
|
for (const [key, value] of Object.entries(override)) {
|
|
18668
19384
|
if (!allowed.has(key)) {
|
|
@@ -18670,12 +19386,13 @@ function applyCodexcliOverride({ parsed, override, logger }) {
|
|
|
18670
19386
|
continue;
|
|
18671
19387
|
}
|
|
18672
19388
|
if (value === void 0) continue;
|
|
18673
|
-
const
|
|
18674
|
-
|
|
18675
|
-
...
|
|
19389
|
+
const existingValue = existing[key];
|
|
19390
|
+
patch[key] = isPlainObject(existingValue) && isPlainObject(value) ? {
|
|
19391
|
+
...existingValue,
|
|
18676
19392
|
...value
|
|
18677
19393
|
} : value;
|
|
18678
19394
|
}
|
|
19395
|
+
return patch;
|
|
18679
19396
|
}
|
|
18680
19397
|
function extractCodexcliOverride(table) {
|
|
18681
19398
|
const override = {};
|
|
@@ -19236,15 +19953,17 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
19236
19953
|
else delete mergedPermissions.ask;
|
|
19237
19954
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
19238
19955
|
else delete mergedPermissions.deny;
|
|
19239
|
-
const merged = {
|
|
19240
|
-
...settings,
|
|
19241
|
-
permissions: mergedPermissions
|
|
19242
|
-
};
|
|
19243
19956
|
return new DevinPermissions({
|
|
19244
19957
|
outputRoot,
|
|
19245
19958
|
relativeDirPath: paths.relativeDirPath,
|
|
19246
19959
|
relativeFilePath: paths.relativeFilePath,
|
|
19247
|
-
fileContent:
|
|
19960
|
+
fileContent: applySharedConfigPatch({
|
|
19961
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19962
|
+
feature: "permissions",
|
|
19963
|
+
existingContent,
|
|
19964
|
+
patch: { permissions: mergedPermissions },
|
|
19965
|
+
filePath
|
|
19966
|
+
}),
|
|
19248
19967
|
validate
|
|
19249
19968
|
});
|
|
19250
19969
|
}
|
|
@@ -19337,9 +20056,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
19337
20056
|
"networkPolicy",
|
|
19338
20057
|
"sandbox",
|
|
19339
20058
|
"mcpPolicy",
|
|
20059
|
+
"mcpAutonomyOverrides",
|
|
19340
20060
|
"enableDroidShield",
|
|
19341
20061
|
"sessionDefaultSettings",
|
|
19342
20062
|
"maxAutonomyLevel",
|
|
20063
|
+
"subagentAutonomyLevel",
|
|
19343
20064
|
"interactionMode"
|
|
19344
20065
|
];
|
|
19345
20066
|
/**
|
|
@@ -19697,7 +20418,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
19697
20418
|
}
|
|
19698
20419
|
//#endregion
|
|
19699
20420
|
//#region src/features/permissions/grokcli-permissions.ts
|
|
19700
|
-
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
19701
20421
|
const GROKCLI_UI_KEY = "ui";
|
|
19702
20422
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
19703
20423
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
@@ -19799,13 +20519,19 @@ function parseGrokEntry(entry) {
|
|
|
19799
20519
|
* compatibility with older Grok versions: `always-approve` when the config is
|
|
19800
20520
|
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
19801
20521
|
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
19802
|
-
* arrays).
|
|
19803
|
-
*
|
|
19804
|
-
*
|
|
19805
|
-
*
|
|
19806
|
-
*
|
|
20522
|
+
* arrays). It is a user-level UI setting, so it is only written in global scope
|
|
20523
|
+
* (see below).
|
|
20524
|
+
*
|
|
20525
|
+
* Both scopes are supported: the adapter syncs the project-level
|
|
20526
|
+
* `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
|
|
20527
|
+
* (with `--global`). Grok documents that "Project configs are limited to MCP
|
|
20528
|
+
* servers, plugins, and permission rules, not full user configs"
|
|
20529
|
+
* (https://docs.x.ai/build/settings), so the project config carries only the
|
|
20530
|
+
* fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
|
|
20531
|
+
* is written in global scope only. The shared config is merged in
|
|
19807
20532
|
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
19808
|
-
* tools it models and the `[ui] permission_mode` value, while
|
|
20533
|
+
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
20534
|
+
* every other key
|
|
19809
20535
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
19810
20536
|
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
19811
20537
|
* file is never deleted.
|
|
@@ -19827,7 +20553,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19827
20553
|
};
|
|
19828
20554
|
}
|
|
19829
20555
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
19830
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
19831
20556
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
19832
20557
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
19833
20558
|
return new GrokcliPermissions({
|
|
@@ -19836,11 +20561,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19836
20561
|
relativeFilePath: paths.relativeFilePath,
|
|
19837
20562
|
fileContent,
|
|
19838
20563
|
validate,
|
|
19839
|
-
global
|
|
20564
|
+
global
|
|
19840
20565
|
});
|
|
19841
20566
|
}
|
|
19842
20567
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
19843
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
19844
20568
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
19845
20569
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19846
20570
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -19853,25 +20577,32 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19853
20577
|
const config = rulesyncPermissions.getJson();
|
|
19854
20578
|
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
19855
20579
|
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
19856
|
-
|
|
20580
|
+
const permission = {
|
|
19857
20581
|
...existingPermission,
|
|
19858
20582
|
allow: buckets.allow,
|
|
19859
20583
|
deny: buckets.deny,
|
|
19860
20584
|
ask: buckets.ask
|
|
19861
20585
|
};
|
|
19862
|
-
const
|
|
19863
|
-
|
|
19864
|
-
|
|
19865
|
-
|
|
19866
|
-
[GROKCLI_PERMISSION_MODE_KEY]: mode
|
|
19867
|
-
};
|
|
20586
|
+
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
20587
|
+
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
20588
|
+
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
20589
|
+
} } : {};
|
|
19868
20590
|
return new GrokcliPermissions({
|
|
19869
20591
|
outputRoot,
|
|
19870
20592
|
relativeDirPath: paths.relativeDirPath,
|
|
19871
20593
|
relativeFilePath: paths.relativeFilePath,
|
|
19872
|
-
fileContent:
|
|
20594
|
+
fileContent: applySharedConfigPatch({
|
|
20595
|
+
fileKey: sharedConfigFileKey(paths),
|
|
20596
|
+
feature: "permissions",
|
|
20597
|
+
existingContent,
|
|
20598
|
+
patch: {
|
|
20599
|
+
[GROKCLI_PERMISSION_KEY]: permission,
|
|
20600
|
+
...uiPatch
|
|
20601
|
+
},
|
|
20602
|
+
filePath
|
|
20603
|
+
}),
|
|
19873
20604
|
validate: true,
|
|
19874
|
-
global
|
|
20605
|
+
global
|
|
19875
20606
|
});
|
|
19876
20607
|
}
|
|
19877
20608
|
toRulesyncPermissions() {
|
|
@@ -19892,14 +20623,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19892
20623
|
error: null
|
|
19893
20624
|
};
|
|
19894
20625
|
}
|
|
19895
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
20626
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
19896
20627
|
return new GrokcliPermissions({
|
|
19897
20628
|
outputRoot,
|
|
19898
20629
|
relativeDirPath,
|
|
19899
20630
|
relativeFilePath,
|
|
19900
20631
|
fileContent: "",
|
|
19901
20632
|
validate: false,
|
|
19902
|
-
global
|
|
20633
|
+
global
|
|
19903
20634
|
});
|
|
19904
20635
|
}
|
|
19905
20636
|
};
|
|
@@ -20572,7 +21303,16 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
20572
21303
|
outputRoot,
|
|
20573
21304
|
relativeDirPath: paths.relativeDirPath,
|
|
20574
21305
|
relativeFilePath: paths.relativeFilePath,
|
|
20575
|
-
fileContent:
|
|
21306
|
+
fileContent: applySharedConfigPatch({
|
|
21307
|
+
fileKey: sharedConfigFileKey(paths),
|
|
21308
|
+
feature: "permissions",
|
|
21309
|
+
existingContent,
|
|
21310
|
+
patch: {
|
|
21311
|
+
allowedTools: next.allowedTools,
|
|
21312
|
+
toolsSettings: next.toolsSettings
|
|
21313
|
+
},
|
|
21314
|
+
filePath
|
|
21315
|
+
}),
|
|
20576
21316
|
validate
|
|
20577
21317
|
});
|
|
20578
21318
|
}
|
|
@@ -20805,6 +21545,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
20805
21545
|
"notebookedit",
|
|
20806
21546
|
"agent"
|
|
20807
21547
|
]);
|
|
21548
|
+
/**
|
|
21549
|
+
* Translate between rulesync's canonical permission category names and
|
|
21550
|
+
* OpenCode's native permission keys. OpenCode has no `agent` key — subagent
|
|
21551
|
+
* launches are gated by the `task` key (see the documented key list at
|
|
21552
|
+
* https://opencode.ai/docs/permissions/). Without this translation a canonical
|
|
21553
|
+
* `agent: deny` would be written verbatim into `opencode.json` and silently
|
|
21554
|
+
* ignored by OpenCode. Unknown names pass through unchanged.
|
|
21555
|
+
*/
|
|
21556
|
+
const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
|
|
21557
|
+
const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
|
|
21558
|
+
function toOpencodePermissionKey(canonical) {
|
|
21559
|
+
return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
|
|
21560
|
+
}
|
|
21561
|
+
function toCanonicalPermissionKey(opencodeKey) {
|
|
21562
|
+
return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
|
|
21563
|
+
}
|
|
20808
21564
|
function isSharedPermissionCategory(category) {
|
|
20809
21565
|
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
20810
21566
|
}
|
|
@@ -20869,21 +21625,24 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
20869
21625
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
20870
21626
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
20871
21627
|
}
|
|
20872
|
-
const parsed = parse(fileContent ?? "{}");
|
|
20873
21628
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20874
21629
|
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
20875
|
-
const
|
|
20876
|
-
|
|
20877
|
-
permission: {
|
|
20878
|
-
...rulesyncJson.permission,
|
|
20879
|
-
...overridePermission
|
|
20880
|
-
}
|
|
20881
|
-
};
|
|
21630
|
+
const sharedPermission = {};
|
|
21631
|
+
for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
|
|
20882
21632
|
return new OpencodePermissions({
|
|
20883
21633
|
outputRoot,
|
|
20884
21634
|
relativeDirPath: basePaths.relativeDirPath,
|
|
20885
21635
|
relativeFilePath,
|
|
20886
|
-
fileContent:
|
|
21636
|
+
fileContent: applySharedConfigPatch({
|
|
21637
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
21638
|
+
feature: "permissions",
|
|
21639
|
+
existingContent: fileContent ?? "",
|
|
21640
|
+
patch: { permission: {
|
|
21641
|
+
...sharedPermission,
|
|
21642
|
+
...overridePermission
|
|
21643
|
+
} },
|
|
21644
|
+
filePath: join(jsonDir, relativeFilePath)
|
|
21645
|
+
}),
|
|
20887
21646
|
validate: true
|
|
20888
21647
|
});
|
|
20889
21648
|
}
|
|
@@ -20895,8 +21654,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
20895
21654
|
}
|
|
20896
21655
|
const shared = {};
|
|
20897
21656
|
const overrideOnly = {};
|
|
20898
|
-
for (const [category, value] of Object.entries(rawPermission))
|
|
20899
|
-
|
|
21657
|
+
for (const [category, value] of Object.entries(rawPermission)) {
|
|
21658
|
+
const canonicalCategory = toCanonicalPermissionKey(category);
|
|
21659
|
+
if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
|
|
21660
|
+
else overrideOnly[category] = value;
|
|
21661
|
+
}
|
|
20900
21662
|
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20901
21663
|
permission: shared,
|
|
20902
21664
|
opencode: { permission: overrideOnly }
|
|
@@ -21021,6 +21783,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
21021
21783
|
"disabled"
|
|
21022
21784
|
];
|
|
21023
21785
|
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
21786
|
+
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
21024
21787
|
function asPlainRecord(value) {
|
|
21025
21788
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21026
21789
|
}
|
|
@@ -21092,20 +21855,24 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21092
21855
|
else delete mergedPermissions.ask;
|
|
21093
21856
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21094
21857
|
else delete mergedPermissions.deny;
|
|
21095
|
-
const merged = {
|
|
21096
|
-
...settings,
|
|
21097
|
-
permissions: mergedPermissions
|
|
21098
|
-
};
|
|
21099
21858
|
const override = config.qwencode;
|
|
21100
|
-
if (override?.
|
|
21859
|
+
if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
|
|
21860
|
+
const patch = { permissions: mergedPermissions };
|
|
21861
|
+
if (override?.tools !== void 0) patch.tools = {
|
|
21101
21862
|
...asPlainRecord(settings.tools),
|
|
21102
21863
|
...asPlainRecord(override.tools)
|
|
21103
21864
|
};
|
|
21104
|
-
if (override?.security !== void 0)
|
|
21865
|
+
if (override?.security !== void 0) patch.security = {
|
|
21105
21866
|
...asPlainRecord(settings.security),
|
|
21106
21867
|
...asPlainRecord(override.security)
|
|
21107
21868
|
};
|
|
21108
|
-
const fileContent =
|
|
21869
|
+
const fileContent = applySharedConfigPatch({
|
|
21870
|
+
fileKey: sharedConfigFileKey(paths),
|
|
21871
|
+
feature: "permissions",
|
|
21872
|
+
existingContent,
|
|
21873
|
+
patch,
|
|
21874
|
+
filePath
|
|
21875
|
+
});
|
|
21109
21876
|
return new QwencodePermissions({
|
|
21110
21877
|
outputRoot,
|
|
21111
21878
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -21132,9 +21899,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21132
21899
|
});
|
|
21133
21900
|
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
21134
21901
|
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
21902
|
+
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
21135
21903
|
const qwencodeOverride = {};
|
|
21136
21904
|
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
21137
21905
|
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
21906
|
+
if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
|
|
21138
21907
|
const result = { ...config };
|
|
21139
21908
|
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
21140
21909
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -21340,7 +22109,9 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
21340
22109
|
}
|
|
21341
22110
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
21342
22111
|
const paths = this.getSettablePaths({ global });
|
|
21343
|
-
const
|
|
22112
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
22113
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
22114
|
+
const parsed = parseReasonixConfig(existingContent);
|
|
21344
22115
|
const config = rulesyncPermissions.getJson();
|
|
21345
22116
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
21346
22117
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
@@ -21365,25 +22136,27 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
21365
22136
|
else delete mergedPermissions.ask;
|
|
21366
22137
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21367
22138
|
else delete mergedPermissions.deny;
|
|
21368
|
-
const
|
|
21369
|
-
...parsed,
|
|
21370
|
-
permissions: mergedPermissions
|
|
21371
|
-
};
|
|
22139
|
+
const patch = { permissions: mergedPermissions };
|
|
21372
22140
|
const override = config.reasonix;
|
|
21373
|
-
if (override?.sandbox !== void 0)
|
|
22141
|
+
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
21374
22142
|
...asReasonixRecord(parsed.sandbox),
|
|
21375
22143
|
...asReasonixRecord(override.sandbox)
|
|
21376
22144
|
};
|
|
21377
|
-
if (override?.agent !== void 0)
|
|
22145
|
+
if (override?.agent !== void 0) patch.agent = {
|
|
21378
22146
|
...asReasonixRecord(parsed.agent),
|
|
21379
22147
|
...asReasonixRecord(override.agent)
|
|
21380
22148
|
};
|
|
21381
|
-
const fileContent = smolToml.stringify(merged);
|
|
21382
22149
|
return new ReasonixPermissions({
|
|
21383
22150
|
outputRoot,
|
|
21384
22151
|
relativeDirPath: paths.relativeDirPath,
|
|
21385
22152
|
relativeFilePath: paths.relativeFilePath,
|
|
21386
|
-
fileContent
|
|
22153
|
+
fileContent: applySharedConfigPatch({
|
|
22154
|
+
fileKey: sharedConfigFileKey(paths),
|
|
22155
|
+
feature: "permissions",
|
|
22156
|
+
existingContent,
|
|
22157
|
+
patch,
|
|
22158
|
+
filePath
|
|
22159
|
+
}),
|
|
21387
22160
|
validate
|
|
21388
22161
|
});
|
|
21389
22162
|
}
|
|
@@ -21954,7 +22727,9 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
21954
22727
|
}
|
|
21955
22728
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
21956
22729
|
const paths = this.getSettablePaths({ global });
|
|
21957
|
-
const
|
|
22730
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
22731
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
22732
|
+
const config = parseVibeConfig(existingContent);
|
|
21958
22733
|
const permission = rulesyncPermissions.getJson().permission;
|
|
21959
22734
|
const tools = toVibeToolsRecord(config.tools);
|
|
21960
22735
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
@@ -21999,16 +22774,21 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
21999
22774
|
tools[vibeToolName] = nextTool;
|
|
22000
22775
|
}
|
|
22001
22776
|
applyVibeSensitivePatterns(tools, rulesyncPermissions.getJson().vibe);
|
|
22002
|
-
config.tools = tools;
|
|
22003
|
-
if (enabledTools.size > 0) config.enabled_tools = [...enabledTools].toSorted();
|
|
22004
|
-
else delete config.enabled_tools;
|
|
22005
|
-
if (disabledTools.size > 0) config.disabled_tools = [...disabledTools].toSorted();
|
|
22006
|
-
else delete config.disabled_tools;
|
|
22007
22777
|
return new VibePermissions({
|
|
22008
22778
|
outputRoot,
|
|
22009
22779
|
relativeDirPath: paths.relativeDirPath,
|
|
22010
22780
|
relativeFilePath: paths.relativeFilePath,
|
|
22011
|
-
fileContent:
|
|
22781
|
+
fileContent: applySharedConfigPatch({
|
|
22782
|
+
fileKey: sharedConfigFileKey(paths),
|
|
22783
|
+
feature: "permissions",
|
|
22784
|
+
existingContent,
|
|
22785
|
+
patch: {
|
|
22786
|
+
tools,
|
|
22787
|
+
enabled_tools: enabledTools.size > 0 ? [...enabledTools].toSorted() : void 0,
|
|
22788
|
+
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
22789
|
+
},
|
|
22790
|
+
filePath
|
|
22791
|
+
}),
|
|
22012
22792
|
validate,
|
|
22013
22793
|
global
|
|
22014
22794
|
});
|
|
@@ -22471,24 +23251,26 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
22471
23251
|
}
|
|
22472
23252
|
const managedToolNames = new Set(Object.keys(managedTools));
|
|
22473
23253
|
const preservedTools = Object.fromEntries(Object.entries(existingTools).filter(([toolName]) => !managedToolNames.has(toolName)));
|
|
22474
|
-
const mergedSettings = {
|
|
22475
|
-
...settings,
|
|
22476
|
-
agent: {
|
|
22477
|
-
...agent,
|
|
22478
|
-
tool_permissions: {
|
|
22479
|
-
...toolPermissions,
|
|
22480
|
-
tools: {
|
|
22481
|
-
...preservedTools,
|
|
22482
|
-
...managedTools
|
|
22483
|
-
}
|
|
22484
|
-
}
|
|
22485
|
-
}
|
|
22486
|
-
};
|
|
22487
23254
|
return new ZedPermissions({
|
|
22488
23255
|
outputRoot,
|
|
22489
23256
|
relativeDirPath: paths.relativeDirPath,
|
|
22490
23257
|
relativeFilePath: paths.relativeFilePath,
|
|
22491
|
-
fileContent:
|
|
23258
|
+
fileContent: applySharedConfigPatch({
|
|
23259
|
+
fileKey: sharedConfigFileKey(paths),
|
|
23260
|
+
feature: "permissions",
|
|
23261
|
+
existingContent,
|
|
23262
|
+
patch: { agent: {
|
|
23263
|
+
...agent,
|
|
23264
|
+
tool_permissions: {
|
|
23265
|
+
...toolPermissions,
|
|
23266
|
+
tools: {
|
|
23267
|
+
...preservedTools,
|
|
23268
|
+
...managedTools
|
|
23269
|
+
}
|
|
23270
|
+
}
|
|
23271
|
+
} },
|
|
23272
|
+
filePath
|
|
23273
|
+
}),
|
|
22492
23274
|
validate: true
|
|
22493
23275
|
});
|
|
22494
23276
|
}
|
|
@@ -22630,7 +23412,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
22630
23412
|
["grokcli", {
|
|
22631
23413
|
class: GrokcliPermissions,
|
|
22632
23414
|
meta: {
|
|
22633
|
-
supportsProject:
|
|
23415
|
+
supportsProject: true,
|
|
22634
23416
|
supportsGlobal: true,
|
|
22635
23417
|
supportsImport: true
|
|
22636
23418
|
}
|
|
@@ -38178,8 +38960,13 @@ const collectFactoryPaths = (factory) => [...settablePathsForScope({
|
|
|
38178
38960
|
* Derive, from the processor registry, every on-disk file that two or more
|
|
38179
38961
|
* features read-modify-write. Source of truth the generation step graph's
|
|
38180
38962
|
* `writesSharedFile` declarations must match.
|
|
38963
|
+
*
|
|
38964
|
+
* `minWriters: 1` widens the result to single-feature files as well — used to
|
|
38965
|
+
* validate `SHARED_CONFIG_OWNERSHIP` declarations for files that route through
|
|
38966
|
+
* the gateway without being cross-feature shared (e.g. a global-scope twin
|
|
38967
|
+
* only one feature writes).
|
|
38181
38968
|
*/
|
|
38182
|
-
const deriveSharedFileWriters = () => {
|
|
38969
|
+
const deriveSharedFileWriters = ({ minWriters = 2 } = {}) => {
|
|
38183
38970
|
const byKey = /* @__PURE__ */ new Map();
|
|
38184
38971
|
const pathByKey = /* @__PURE__ */ new Map();
|
|
38185
38972
|
for (const entry of PROCESSOR_REGISTRY) {
|
|
@@ -38205,7 +38992,7 @@ const deriveSharedFileWriters = () => {
|
|
|
38205
38992
|
}
|
|
38206
38993
|
const writers = [];
|
|
38207
38994
|
for (const [key, features] of byKey) {
|
|
38208
|
-
if (features.size <
|
|
38995
|
+
if (features.size < minWriters) continue;
|
|
38209
38996
|
const path = pathByKey.get(key);
|
|
38210
38997
|
const toolsByFeature = /* @__PURE__ */ new Map();
|
|
38211
38998
|
for (const [feature, tools] of features) toolsByFeature.set(feature, [...tools].toSorted());
|
|
@@ -39150,4 +39937,4 @@ async function importPermissionsCore(params) {
|
|
|
39150
39937
|
//#endregion
|
|
39151
39938
|
export { removeTempDirectory as $, RulesyncCommand as A, checkPathTraversal as B, RulesyncHooks as C, RULESYNC_RULES_RELATIVE_DIR_PATH as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, formatError as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Et, findControlCharacter as F, findFilesByGlobs as G, directoryExists as H, ConsoleLogger as I, isSymlink as J, getFileSize as K, JsonLogger as L, stringifyFrontmatter as M, loadYaml as N, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES as Ot, ConfigResolver as P, removeFile as Q, CLIError as R, HooksProcessor as S, RULESYNC_RELATIVE_DIR_PATH as St, CLAUDECODE_DIR as T, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Tt, ensureDir as U, createTempDirectory as V, fileExists as W, readFileContent as X, listDirectoryFiles as Y, removeDirectory as Z, RulesyncPermissions as _, RULESYNC_MCP_RELATIVE_FILE_PATH as _t, convertFromTool as a, MAX_FILE_SIZE as at, IgnoreProcessor as b, RULESYNC_PERMISSIONS_FILE_NAME as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dt, toPosixPath as et, SkillsProcessor as f, RULESYNC_HOOKS_FILE_NAME as ft, SKILL_FILE_NAME as g, RULESYNC_MCP_FILE_NAME as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ToolTargetSchema as it, RulesyncCommandFrontmatterSchema as j, CLAUDECODE_SKILLS_DIR_PATH as k, ALL_FEATURES_WITH_WILDCARD as kt, SubagentsProcessor as l, RULESYNC_CONFIG_RELATIVE_FILE_PATH as lt, RulesyncSkill as m, RULESYNC_IGNORE_RELATIVE_FILE_PATH as mt, checkRulesyncDirExists as n, ALL_TOOL_TARGETS as nt, RulesProcessor as o, RULESYNC_AIIGNORE_FILE_NAME as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_RELATIVE_FILE_PATH as pt, getHomeDirectory as q, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rt, RulesyncRule as s, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as st, importFromTool as t, writeFileContent as tt, RulesyncSubagent as u, RULESYNC_CONFIG_SCHEMA_URL as ut, McpProcessor as v, RULESYNC_MCP_SCHEMA_URL as vt, CommandsProcessor as w, RULESYNC_SKILLS_RELATIVE_DIR_PATH as wt, RulesyncIgnore as x, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as xt, RulesyncMcp as y, RULESYNC_OVERVIEW_FILE_NAME as yt, ErrorCodes as z };
|
|
39152
39939
|
|
|
39153
|
-
//# sourceMappingURL=import-
|
|
39940
|
+
//# sourceMappingURL=import-Dioh9ca8.js.map
|