rulesync 16.33.0 → 16.34.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 +1 -0
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DVRU6cLB.cjs → import-9T9fnjNn.cjs} +1589 -23
- package/dist/{import-C6R-lXFF.js → import-De13gNpF.js} +1573 -7
- package/dist/import-De13gNpF.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-C6R-lXFF.js.map +0 -1
|
@@ -444,36 +444,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
444
444
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
445
445
|
//#endregion
|
|
446
446
|
//#region src/constants/rulesync-paths.ts
|
|
447
|
-
const { join: join$
|
|
447
|
+
const { join: join$350 } = node_path.posix;
|
|
448
448
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
449
449
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
450
450
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
451
451
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
452
452
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
453
453
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
454
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
454
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$350(RULES_FEATURE_SUBDIR, ".curated");
|
|
455
455
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
456
456
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
457
457
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
458
458
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
459
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
460
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
461
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
462
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
463
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
464
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
465
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
466
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
467
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
468
|
-
join$
|
|
469
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
470
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
459
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$350(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
460
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
461
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
462
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
463
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
464
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
465
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
466
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
467
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
468
|
+
join$350(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
469
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
470
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
471
471
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
472
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
473
473
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
474
474
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
475
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
475
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$350(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
477
477
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
478
478
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
479
479
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -503,6 +503,7 @@ const rulesProcessorToolTargetTuple = [
|
|
|
503
503
|
"cline",
|
|
504
504
|
"codebuddy",
|
|
505
505
|
"codexcli",
|
|
506
|
+
"commandcode",
|
|
506
507
|
"continue",
|
|
507
508
|
"copilot",
|
|
508
509
|
"copilotcli",
|
|
@@ -579,6 +580,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
579
580
|
"claudecode-legacy",
|
|
580
581
|
"cline",
|
|
581
582
|
"codexcli",
|
|
583
|
+
"commandcode",
|
|
582
584
|
"continue",
|
|
583
585
|
"copilot",
|
|
584
586
|
"copilotcli",
|
|
@@ -622,6 +624,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
622
624
|
"claudecode-legacy",
|
|
623
625
|
"cline",
|
|
624
626
|
"codexcli",
|
|
627
|
+
"commandcode",
|
|
625
628
|
"continue",
|
|
626
629
|
"copilot",
|
|
627
630
|
"cursor",
|
|
@@ -659,6 +662,7 @@ const subagentsProcessorToolTargetTuple = [
|
|
|
659
662
|
"claudecode-legacy",
|
|
660
663
|
"cline",
|
|
661
664
|
"codexcli",
|
|
665
|
+
"commandcode",
|
|
662
666
|
"copilot",
|
|
663
667
|
"copilotcli",
|
|
664
668
|
"cortexcode",
|
|
@@ -700,6 +704,7 @@ const skillsProcessorToolTargetTuple = [
|
|
|
700
704
|
"claudecode-legacy",
|
|
701
705
|
"cline",
|
|
702
706
|
"codexcli",
|
|
707
|
+
"commandcode",
|
|
703
708
|
"continue",
|
|
704
709
|
"copilot",
|
|
705
710
|
"copilotcli",
|
|
@@ -747,6 +752,7 @@ const hooksProcessorToolTargetTuple = [
|
|
|
747
752
|
"claudecode",
|
|
748
753
|
"claudecode-plugin",
|
|
749
754
|
"codexcli",
|
|
755
|
+
"commandcode",
|
|
750
756
|
"continue",
|
|
751
757
|
"copilot",
|
|
752
758
|
"copilotcli",
|
|
@@ -781,6 +787,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
781
787
|
"claudecode",
|
|
782
788
|
"cline",
|
|
783
789
|
"codexcli",
|
|
790
|
+
"commandcode",
|
|
784
791
|
"continue",
|
|
785
792
|
"copilot",
|
|
786
793
|
"copilotcli",
|
|
@@ -3295,6 +3302,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3295
3302
|
".claude/settings.json",
|
|
3296
3303
|
".claude/settings.local.json",
|
|
3297
3304
|
".codex/config.toml",
|
|
3305
|
+
".commandcode/settings.json",
|
|
3298
3306
|
".continue/settings.json",
|
|
3299
3307
|
".copilot/settings.json",
|
|
3300
3308
|
".github/copilot/settings.json",
|
|
@@ -4952,6 +4960,30 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
|
|
|
4952
4960
|
};
|
|
4953
4961
|
const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4954
4962
|
/**
|
|
4963
|
+
* Hook events supported by Command Code.
|
|
4964
|
+
*
|
|
4965
|
+
* Command Code reads hooks from the `hooks` key of `.commandcode/settings.json`
|
|
4966
|
+
* (project) and `~/.commandcode/settings.json` (user) in the Claude-Code shape.
|
|
4967
|
+
* Four events are documented: PreToolUse, PostToolUse, Stop and SessionStart.
|
|
4968
|
+
* Only `command` hooks exist; `timeout` is in seconds (default 30, max 600)
|
|
4969
|
+
* and `$COMMANDCODE_PROJECT_DIR` resolves to the project root.
|
|
4970
|
+
*
|
|
4971
|
+
* @see https://commandcode.ai/docs/hooks
|
|
4972
|
+
*/
|
|
4973
|
+
const COMMANDCODE_HOOK_EVENTS = [
|
|
4974
|
+
"preToolUse",
|
|
4975
|
+
"postToolUse",
|
|
4976
|
+
"stop",
|
|
4977
|
+
"sessionStart"
|
|
4978
|
+
];
|
|
4979
|
+
const CANONICAL_TO_COMMANDCODE_EVENT_NAMES = {
|
|
4980
|
+
preToolUse: "PreToolUse",
|
|
4981
|
+
postToolUse: "PostToolUse",
|
|
4982
|
+
stop: "Stop",
|
|
4983
|
+
sessionStart: "SessionStart"
|
|
4984
|
+
};
|
|
4985
|
+
const COMMANDCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_COMMANDCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4986
|
+
/**
|
|
4955
4987
|
* Hook events supported by the Continue CLI (`cn`).
|
|
4956
4988
|
*
|
|
4957
4989
|
* Continue reads a Claude-Code-compatible `hooks` key from
|
|
@@ -5160,6 +5192,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
5160
5192
|
crush: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5161
5193
|
bob: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5162
5194
|
cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5195
|
+
commandcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5163
5196
|
continue: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5164
5197
|
tabnine: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5165
5198
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -6179,6 +6212,7 @@ const RulesyncMcpFileSchema = zod_mini.z.looseObject({
|
|
|
6179
6212
|
claudecode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6180
6213
|
cline: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6181
6214
|
codexcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6215
|
+
commandcode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6182
6216
|
continue: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6183
6217
|
copilot: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6184
6218
|
copilotcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
@@ -6574,12 +6608,16 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
6574
6608
|
* Targets that share one output file resolve identically so the shared
|
|
6575
6609
|
* file's content never depends on which of them generates last — see
|
|
6576
6610
|
* `resolveMcpTarget` for the alias groups (kiro trio, claudecode/-legacy,
|
|
6577
|
-
* and the
|
|
6611
|
+
* the Antigravity pair, and the project-scope Claude Code / Command Code
|
|
6612
|
+
* pair, which is why the scope is part of the resolution).
|
|
6578
6613
|
*
|
|
6579
6614
|
* Returns the same instance when neither mechanism is used.
|
|
6580
6615
|
*/
|
|
6581
|
-
forTarget({ toolTarget, logger }) {
|
|
6582
|
-
const { blockKeys, acceptedTargetNames } = resolveMcpTarget({
|
|
6616
|
+
forTarget({ toolTarget, global = false, logger }) {
|
|
6617
|
+
const { blockKeys, acceptedTargetNames } = resolveMcpTarget({
|
|
6618
|
+
toolTarget,
|
|
6619
|
+
global
|
|
6620
|
+
});
|
|
6583
6621
|
const json = this.json;
|
|
6584
6622
|
const sharedServers = this.json.mcpServers ?? {};
|
|
6585
6623
|
const serverNamesWithTargets = Object.entries(sharedServers).filter(([, serverConfig]) => serverConfig.targets !== void 0).map(([serverName]) => serverName);
|
|
@@ -6666,9 +6704,24 @@ const MCP_IGNORED_ALIAS_SOURCE_KEYS = Object.keys(MCP_BLOCK_KEY_ALIASES);
|
|
|
6666
6704
|
* `config`) — so both targets always apply both blocks in a fixed order
|
|
6667
6705
|
* (`antigravity-ide` first, `antigravity-cli` second — the CLI block wins
|
|
6668
6706
|
* per server on conflict).
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6707
|
+
* - `claudecode` (and its legacy alias) and `commandcode` share the root
|
|
6708
|
+
* `.mcp.json` in PROJECT mode only (their global files differ:
|
|
6709
|
+
* `~/.claude.json` vs `~/.commandcode/mcp.json`), so in project mode the
|
|
6710
|
+
* three apply the `claudecode` and `commandcode` blocks in that fixed order
|
|
6711
|
+
* (the `commandcode` block wins per server on conflict); in global mode
|
|
6712
|
+
* each reads its own block.
|
|
6713
|
+
*/
|
|
6714
|
+
function resolveMcpTarget({ toolTarget, global }) {
|
|
6715
|
+
const isClaudecode = toolTarget === "claudecode" || toolTarget === "claudecode-legacy";
|
|
6716
|
+
if (!global && (isClaudecode || toolTarget === "commandcode")) return {
|
|
6717
|
+
blockKeys: ["claudecode", "commandcode"],
|
|
6718
|
+
acceptedTargetNames: /* @__PURE__ */ new Set([
|
|
6719
|
+
"claudecode",
|
|
6720
|
+
"claudecode-legacy",
|
|
6721
|
+
"commandcode"
|
|
6722
|
+
])
|
|
6723
|
+
};
|
|
6724
|
+
if (isClaudecode) return {
|
|
6672
6725
|
blockKeys: ["claudecode"],
|
|
6673
6726
|
acceptedTargetNames: /* @__PURE__ */ new Set(["claudecode", "claudecode-legacy"])
|
|
6674
6727
|
};
|
|
@@ -7833,6 +7886,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
7833
7886
|
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7834
7887
|
continue: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7835
7888
|
copilot: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7889
|
+
commandcode: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7836
7890
|
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7837
7891
|
crush: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7838
7892
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -9555,6 +9609,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9555
9609
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9556
9610
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
9557
9611
|
})),
|
|
9612
|
+
commandcode: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9558
9613
|
cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9559
9614
|
continue: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9560
9615
|
tabnine: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
@@ -13166,6 +13221,20 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13166
13221
|
ownedKeys: ["hooks"]
|
|
13167
13222
|
} }
|
|
13168
13223
|
},
|
|
13224
|
+
".commandcode/settings.json": {
|
|
13225
|
+
format: "json",
|
|
13226
|
+
invalidRootPolicy: "error",
|
|
13227
|
+
features: {
|
|
13228
|
+
hooks: {
|
|
13229
|
+
kind: "replace-owned-keys",
|
|
13230
|
+
ownedKeys: ["hooks"]
|
|
13231
|
+
},
|
|
13232
|
+
permissions: {
|
|
13233
|
+
kind: "replace-owned-keys",
|
|
13234
|
+
ownedKeys: ["permissions"]
|
|
13235
|
+
}
|
|
13236
|
+
}
|
|
13237
|
+
},
|
|
13169
13238
|
".cortex/settings.json": {
|
|
13170
13239
|
format: "json",
|
|
13171
13240
|
invalidRootPolicy: "error",
|
|
@@ -15367,6 +15436,94 @@ var CodexcliCommand = class CodexcliCommand extends ToolCommand {
|
|
|
15367
15436
|
}
|
|
15368
15437
|
};
|
|
15369
15438
|
//#endregion
|
|
15439
|
+
//#region src/constants/commandcode-paths.ts
|
|
15440
|
+
const COMMANDCODE_DIR = ".commandcode";
|
|
15441
|
+
const COMMANDCODE_RULE_FILE_NAME = "AGENTS.md";
|
|
15442
|
+
const COMMANDCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
15443
|
+
const COMMANDCODE_PROJECT_MCP_FILE_NAME = ".mcp.json";
|
|
15444
|
+
const COMMANDCODE_GLOBAL_MCP_FILE_NAME = "mcp.json";
|
|
15445
|
+
const COMMANDCODE_COMMANDS_DIR_PATH = (0, node_path.join)(COMMANDCODE_DIR, "commands");
|
|
15446
|
+
const COMMANDCODE_AGENTS_DIR_PATH = (0, node_path.join)(COMMANDCODE_DIR, "agents");
|
|
15447
|
+
const COMMANDCODE_SKILLS_DIR_PATH = (0, node_path.join)(COMMANDCODE_DIR, "skills");
|
|
15448
|
+
//#endregion
|
|
15449
|
+
//#region src/features/commands/commandcode-command.ts
|
|
15450
|
+
/**
|
|
15451
|
+
* Custom slash command for Command Code.
|
|
15452
|
+
*
|
|
15453
|
+
* Command Code reads Markdown files from `.commandcode/commands/` (project)
|
|
15454
|
+
* and `~/.commandcode/commands/` (user), naming each command after the file's
|
|
15455
|
+
* basename; subdirectories only group the files. The file's full trimmed
|
|
15456
|
+
* body is the prompt that runs — YAML frontmatter is not stripped, only
|
|
15457
|
+
* skipped when the slash menu picks a summary line — so rulesync writes the
|
|
15458
|
+
* bare body. On import a hand-written file's frontmatter block is dropped
|
|
15459
|
+
* (Command Code would send it as prompt text; a rulesync command carries its
|
|
15460
|
+
* own frontmatter) and only the body is kept.
|
|
15461
|
+
*
|
|
15462
|
+
* @see https://commandcode.ai/docs/custom-slash-commands
|
|
15463
|
+
*/
|
|
15464
|
+
var CommandcodeCommand = class CommandcodeCommand extends ToolCommand {
|
|
15465
|
+
static getSettablePaths(_options = {}) {
|
|
15466
|
+
return { relativeDirPath: COMMANDCODE_COMMANDS_DIR_PATH };
|
|
15467
|
+
}
|
|
15468
|
+
toRulesyncCommand() {
|
|
15469
|
+
return new RulesyncCommand({
|
|
15470
|
+
outputRoot: process.cwd(),
|
|
15471
|
+
frontmatter: { targets: ["*"] },
|
|
15472
|
+
body: this.getFileContent(),
|
|
15473
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
15474
|
+
relativeFilePath: this.relativeFilePath,
|
|
15475
|
+
fileContent: this.getFileContent(),
|
|
15476
|
+
validate: true
|
|
15477
|
+
});
|
|
15478
|
+
}
|
|
15479
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true }) {
|
|
15480
|
+
const paths = this.getSettablePaths();
|
|
15481
|
+
return new CommandcodeCommand({
|
|
15482
|
+
outputRoot,
|
|
15483
|
+
fileContent: rulesyncCommand.getBody(),
|
|
15484
|
+
relativeDirPath: paths.relativeDirPath,
|
|
15485
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
15486
|
+
validate
|
|
15487
|
+
});
|
|
15488
|
+
}
|
|
15489
|
+
validate() {
|
|
15490
|
+
return {
|
|
15491
|
+
success: true,
|
|
15492
|
+
error: null
|
|
15493
|
+
};
|
|
15494
|
+
}
|
|
15495
|
+
getBody() {
|
|
15496
|
+
return this.getFileContent();
|
|
15497
|
+
}
|
|
15498
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
15499
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
15500
|
+
rulesyncCommand,
|
|
15501
|
+
toolTarget: "commandcode"
|
|
15502
|
+
});
|
|
15503
|
+
}
|
|
15504
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
15505
|
+
const paths = this.getSettablePaths();
|
|
15506
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
15507
|
+
const { body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
15508
|
+
return new CommandcodeCommand({
|
|
15509
|
+
outputRoot,
|
|
15510
|
+
relativeDirPath: paths.relativeDirPath,
|
|
15511
|
+
relativeFilePath,
|
|
15512
|
+
fileContent: content.trim(),
|
|
15513
|
+
validate
|
|
15514
|
+
});
|
|
15515
|
+
}
|
|
15516
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
15517
|
+
return new CommandcodeCommand({
|
|
15518
|
+
outputRoot,
|
|
15519
|
+
relativeDirPath,
|
|
15520
|
+
relativeFilePath,
|
|
15521
|
+
fileContent: "",
|
|
15522
|
+
validate: false
|
|
15523
|
+
});
|
|
15524
|
+
}
|
|
15525
|
+
};
|
|
15526
|
+
//#endregion
|
|
15370
15527
|
//#region src/constants/continue-paths.ts
|
|
15371
15528
|
const CONTINUE_DIR = ".continue";
|
|
15372
15529
|
const CONTINUE_ROOT_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -19610,6 +19767,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
19610
19767
|
supportsSubdirectory: false
|
|
19611
19768
|
}
|
|
19612
19769
|
}],
|
|
19770
|
+
["commandcode", {
|
|
19771
|
+
class: CommandcodeCommand,
|
|
19772
|
+
meta: {
|
|
19773
|
+
extension: "md",
|
|
19774
|
+
supportsProject: true,
|
|
19775
|
+
supportsGlobal: true,
|
|
19776
|
+
isSimulated: false,
|
|
19777
|
+
supportsSubdirectory: true
|
|
19778
|
+
}
|
|
19779
|
+
}],
|
|
19613
19780
|
["continue", {
|
|
19614
19781
|
class: ContinueCommand,
|
|
19615
19782
|
meta: {
|
|
@@ -22980,6 +23147,132 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
22980
23147
|
}
|
|
22981
23148
|
};
|
|
22982
23149
|
//#endregion
|
|
23150
|
+
//#region src/features/hooks/commandcode-hooks.ts
|
|
23151
|
+
const COMMANDCODE_CONVERTER_CONFIG = {
|
|
23152
|
+
supportedEvents: COMMANDCODE_HOOK_EVENTS,
|
|
23153
|
+
canonicalToToolEventNames: CANONICAL_TO_COMMANDCODE_EVENT_NAMES,
|
|
23154
|
+
toolToCanonicalEventNames: COMMANDCODE_TO_CANONICAL_EVENT_NAMES,
|
|
23155
|
+
projectDirVar: "$COMMANDCODE_PROJECT_DIR",
|
|
23156
|
+
prefixDotRelativeCommandsOnly: true,
|
|
23157
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["stop", "sessionStart"]),
|
|
23158
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"])
|
|
23159
|
+
};
|
|
23160
|
+
/**
|
|
23161
|
+
* Single spelling of the settings codec/policy: fail closed on an unparseable
|
|
23162
|
+
* root rather than replacing the user's Command Code settings with generated
|
|
23163
|
+
* output.
|
|
23164
|
+
*/
|
|
23165
|
+
function parseCommandcodeSettings$1(fileContent, filePath) {
|
|
23166
|
+
return parseSharedConfig({
|
|
23167
|
+
format: "json",
|
|
23168
|
+
fileContent,
|
|
23169
|
+
filePath,
|
|
23170
|
+
invalidRootPolicy: "error"
|
|
23171
|
+
});
|
|
23172
|
+
}
|
|
23173
|
+
/**
|
|
23174
|
+
* Command Code hooks.
|
|
23175
|
+
*
|
|
23176
|
+
* Hooks live under the top-level `hooks` key of `<project>/.commandcode/settings.json`
|
|
23177
|
+
* (project scope) and `~/.commandcode/settings.json` (user scope), in the
|
|
23178
|
+
* Claude-Code shape: `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{
|
|
23179
|
+
* "type": "command", "command": "...", "timeout"?: <seconds> }] }] }`. Both
|
|
23180
|
+
* files also hold settings rulesync does not own (`permissions`, `defaultMode`,
|
|
23181
|
+
* ...), so generation merges the `hooks` key into the existing file (see
|
|
23182
|
+
* `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
|
|
23183
|
+
*
|
|
23184
|
+
* @see https://commandcode.ai/docs/hooks
|
|
23185
|
+
* @see https://commandcode.ai/docs/settings
|
|
23186
|
+
*/
|
|
23187
|
+
var CommandcodeHooks = class CommandcodeHooks extends ToolHooks {
|
|
23188
|
+
constructor(params) {
|
|
23189
|
+
super({
|
|
23190
|
+
...params,
|
|
23191
|
+
fileContent: params.fileContent ?? "{}"
|
|
23192
|
+
});
|
|
23193
|
+
}
|
|
23194
|
+
isDeletable() {
|
|
23195
|
+
return false;
|
|
23196
|
+
}
|
|
23197
|
+
static getSettablePaths(_options = {}) {
|
|
23198
|
+
return {
|
|
23199
|
+
relativeDirPath: COMMANDCODE_DIR,
|
|
23200
|
+
relativeFilePath: COMMANDCODE_SETTINGS_FILE_NAME
|
|
23201
|
+
};
|
|
23202
|
+
}
|
|
23203
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
23204
|
+
const paths = CommandcodeHooks.getSettablePaths({ global });
|
|
23205
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
23206
|
+
return new CommandcodeHooks({
|
|
23207
|
+
outputRoot,
|
|
23208
|
+
relativeDirPath: paths.relativeDirPath,
|
|
23209
|
+
relativeFilePath: paths.relativeFilePath,
|
|
23210
|
+
fileContent,
|
|
23211
|
+
validate
|
|
23212
|
+
});
|
|
23213
|
+
}
|
|
23214
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
23215
|
+
const paths = CommandcodeHooks.getSettablePaths({ global });
|
|
23216
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
23217
|
+
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
23218
|
+
const config = rulesyncHooks.getJson();
|
|
23219
|
+
const hooks = canonicalToToolHooks({
|
|
23220
|
+
config,
|
|
23221
|
+
toolOverrideHooks: config.commandcode?.hooks,
|
|
23222
|
+
converterConfig: COMMANDCODE_CONVERTER_CONFIG,
|
|
23223
|
+
logger
|
|
23224
|
+
});
|
|
23225
|
+
const fileContent = applySharedConfigPatch({
|
|
23226
|
+
fileKey: sharedConfigFileKey(paths),
|
|
23227
|
+
feature: "hooks",
|
|
23228
|
+
existingContent,
|
|
23229
|
+
patch: { hooks },
|
|
23230
|
+
filePath,
|
|
23231
|
+
logger
|
|
23232
|
+
});
|
|
23233
|
+
return new CommandcodeHooks({
|
|
23234
|
+
outputRoot,
|
|
23235
|
+
relativeDirPath: paths.relativeDirPath,
|
|
23236
|
+
relativeFilePath: paths.relativeFilePath,
|
|
23237
|
+
fileContent,
|
|
23238
|
+
validate
|
|
23239
|
+
});
|
|
23240
|
+
}
|
|
23241
|
+
toRulesyncHooks({ logger } = {}) {
|
|
23242
|
+
const configPath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
23243
|
+
let settings;
|
|
23244
|
+
try {
|
|
23245
|
+
settings = parseCommandcodeSettings$1(this.getFileContent(), configPath);
|
|
23246
|
+
} catch (error) {
|
|
23247
|
+
throw new Error(`Failed to parse Command Code hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
|
|
23248
|
+
}
|
|
23249
|
+
const hooks = toolHooksToCanonical({
|
|
23250
|
+
logger,
|
|
23251
|
+
hooks: settings.hooks,
|
|
23252
|
+
converterConfig: COMMANDCODE_CONVERTER_CONFIG
|
|
23253
|
+
});
|
|
23254
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
23255
|
+
hooks,
|
|
23256
|
+
overrideKey: "commandcode"
|
|
23257
|
+
}), null, 2) });
|
|
23258
|
+
}
|
|
23259
|
+
validate() {
|
|
23260
|
+
return {
|
|
23261
|
+
success: true,
|
|
23262
|
+
error: null
|
|
23263
|
+
};
|
|
23264
|
+
}
|
|
23265
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
23266
|
+
return new CommandcodeHooks({
|
|
23267
|
+
outputRoot,
|
|
23268
|
+
relativeDirPath,
|
|
23269
|
+
relativeFilePath,
|
|
23270
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
23271
|
+
validate: false
|
|
23272
|
+
});
|
|
23273
|
+
}
|
|
23274
|
+
};
|
|
23275
|
+
//#endregion
|
|
22983
23276
|
//#region src/features/hooks/continue-hooks.ts
|
|
22984
23277
|
const CONTINUE_SPEC = {
|
|
22985
23278
|
displayName: "Continue",
|
|
@@ -28099,6 +28392,18 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
28099
28392
|
supportedHookTypes: ["command"],
|
|
28100
28393
|
supportsMatcher: true
|
|
28101
28394
|
}],
|
|
28395
|
+
["commandcode", {
|
|
28396
|
+
class: CommandcodeHooks,
|
|
28397
|
+
meta: {
|
|
28398
|
+
supportsProject: true,
|
|
28399
|
+
supportsGlobal: true,
|
|
28400
|
+
supportsImport: true
|
|
28401
|
+
},
|
|
28402
|
+
supportedEvents: COMMANDCODE_HOOK_EVENTS,
|
|
28403
|
+
supportedHookTypes: ["command"],
|
|
28404
|
+
supportsMatcher: true,
|
|
28405
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
28406
|
+
}],
|
|
28102
28407
|
["copilot", {
|
|
28103
28408
|
class: CopilotHooks,
|
|
28104
28409
|
meta: {
|
|
@@ -31956,6 +32261,254 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
31956
32261
|
}
|
|
31957
32262
|
};
|
|
31958
32263
|
//#endregion
|
|
32264
|
+
//#region src/features/mcp/commandcode-mcp.ts
|
|
32265
|
+
/**
|
|
32266
|
+
* Parse a Command Code MCP file. The project file is the `.mcp.json` other
|
|
32267
|
+
* agents (Claude Code among them) share, so a hand-written file is read as
|
|
32268
|
+
* JSONC to tolerate comments and trailing commas; malformed content or a
|
|
32269
|
+
* non-object root (`null`, an array, a scalar) fails closed rather than being
|
|
32270
|
+
* spread into the regenerated file.
|
|
32271
|
+
*/
|
|
32272
|
+
function parseCommandcodeMcpConfig({ fileContent, relativePath }) {
|
|
32273
|
+
let parsed;
|
|
32274
|
+
try {
|
|
32275
|
+
parsed = parseJsonc$6(fileContent);
|
|
32276
|
+
} catch (error) {
|
|
32277
|
+
throw new Error(`Failed to parse Command Code MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
32278
|
+
}
|
|
32279
|
+
if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Command Code MCP config at ${relativePath}: expected a JSON object at the root`);
|
|
32280
|
+
return parsed;
|
|
32281
|
+
}
|
|
32282
|
+
/**
|
|
32283
|
+
* The remote transport Command Code reads a server as. Its loader accepts
|
|
32284
|
+
* `http` (streamable HTTP) and `sse` for a `url` server; a bare `url` defaults
|
|
32285
|
+
* to `http`, and the canonical `streamable-http` spelling is folded into it.
|
|
32286
|
+
* A `ws(s)://` URL or any other stated transport has no Command Code
|
|
32287
|
+
* equivalent, so `undefined` tells the caller to skip the server.
|
|
32288
|
+
* @see https://commandcode.ai/docs/mcp
|
|
32289
|
+
*/
|
|
32290
|
+
function asCommandcodeRemoteTransport(stated, url) {
|
|
32291
|
+
if (stated === "sse") return "sse";
|
|
32292
|
+
if (stated === "http" || stated === "streamable-http") return "http";
|
|
32293
|
+
if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
|
|
32294
|
+
}
|
|
32295
|
+
/**
|
|
32296
|
+
* Convert the canonical server map to the shape Command Code's config schema
|
|
32297
|
+
* documents (used for the global `~/.commandcode/mcp.json` only — the project
|
|
32298
|
+
* `.mcp.json` is shared with Claude Code and written pass-through, see the
|
|
32299
|
+
* class doc): a remote server is `{ transport, url, headers?, env?, oauth? }`
|
|
32300
|
+
* and a stdio server is `{ transport: "stdio", command, args?, env? }`, each
|
|
32301
|
+
* with an optional `enabled` flag. The documented spelling is `transport`
|
|
32302
|
+
* (`type` is only accepted as an alias on read), so that is what is written,
|
|
32303
|
+
* and only the documented keys are emitted — `timeout`, rulesync-only fields
|
|
32304
|
+
* and anything else is left out. The canonical `disabled: true` becomes
|
|
32305
|
+
* Command Code's native `enabled: false` (the rulesync-source-only `enabled`
|
|
32306
|
+
* filter never reaches this function: `getMcpServers()` drops such a server).
|
|
32307
|
+
*
|
|
32308
|
+
* A server Command Code drops at load time — no transport at all, a remote
|
|
32309
|
+
* transport without a URL, a WebSocket URL, or a stdio entry without a
|
|
32310
|
+
* command — is skipped with a warning rather than written in a form the tool
|
|
32311
|
+
* would silently ignore.
|
|
32312
|
+
* @see https://commandcode.ai/docs/mcp
|
|
32313
|
+
*/
|
|
32314
|
+
function convertToCommandcodeFormat(mcpServers, logger) {
|
|
32315
|
+
const result = {};
|
|
32316
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
32317
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
32318
|
+
if (declaresNoTransport(serverConfig)) {
|
|
32319
|
+
warnAndSkipMcpServer({
|
|
32320
|
+
toolName: "Command Code",
|
|
32321
|
+
serverName,
|
|
32322
|
+
reason: "no transport",
|
|
32323
|
+
logger
|
|
32324
|
+
});
|
|
32325
|
+
continue;
|
|
32326
|
+
}
|
|
32327
|
+
const converted = isRemoteMcpServer(serverConfig) ? convertRemoteServer$1({
|
|
32328
|
+
serverName,
|
|
32329
|
+
serverConfig,
|
|
32330
|
+
logger
|
|
32331
|
+
}) : convertStdioServer$1({
|
|
32332
|
+
serverName,
|
|
32333
|
+
serverConfig,
|
|
32334
|
+
logger
|
|
32335
|
+
});
|
|
32336
|
+
if (converted === void 0) continue;
|
|
32337
|
+
if (serverConfig.disabled === true) converted.enabled = false;
|
|
32338
|
+
if (isRecord$1(serverConfig.env)) converted.env = omitPrototypePollutionKeys(serverConfig.env);
|
|
32339
|
+
result[serverName] = converted;
|
|
32340
|
+
}
|
|
32341
|
+
return result;
|
|
32342
|
+
}
|
|
32343
|
+
function convertRemoteServer$1({ serverName, serverConfig, logger }) {
|
|
32344
|
+
const url = resolveRemoteMcpUrl(serverConfig);
|
|
32345
|
+
if (!url) {
|
|
32346
|
+
warnAndSkipMcpServer({
|
|
32347
|
+
toolName: "Command Code",
|
|
32348
|
+
serverName,
|
|
32349
|
+
reason: "a remote transport without a url",
|
|
32350
|
+
logger
|
|
32351
|
+
});
|
|
32352
|
+
return;
|
|
32353
|
+
}
|
|
32354
|
+
const stated = serverConfig.type ?? serverConfig.transport;
|
|
32355
|
+
const transport = asCommandcodeRemoteTransport(typeof stated === "string" ? stated : void 0, url);
|
|
32356
|
+
if (transport === void 0) {
|
|
32357
|
+
warnAndSkipMcpServer({
|
|
32358
|
+
toolName: "Command Code",
|
|
32359
|
+
serverName,
|
|
32360
|
+
reason: stated === void 0 ? "a WebSocket url, which Command Code's remote transports (http and sse) cannot reach" : `the "${String(stated)}" transport, which Command Code does not offer for remote servers (only http and sse)`,
|
|
32361
|
+
logger
|
|
32362
|
+
});
|
|
32363
|
+
return;
|
|
32364
|
+
}
|
|
32365
|
+
const converted = {
|
|
32366
|
+
transport,
|
|
32367
|
+
url
|
|
32368
|
+
};
|
|
32369
|
+
if (isRecord$1(serverConfig.headers)) converted.headers = omitPrototypePollutionKeys(serverConfig.headers);
|
|
32370
|
+
if (isRecord$1(serverConfig.oauth)) converted.oauth = omitPrototypePollutionKeys(serverConfig.oauth);
|
|
32371
|
+
return converted;
|
|
32372
|
+
}
|
|
32373
|
+
function convertStdioServer$1({ serverName, serverConfig, logger }) {
|
|
32374
|
+
const [command, ...args] = resolveLocalMcpCommand(serverConfig);
|
|
32375
|
+
if (!command) {
|
|
32376
|
+
warnAndSkipMcpServer({
|
|
32377
|
+
toolName: "Command Code",
|
|
32378
|
+
serverName,
|
|
32379
|
+
reason: "a stdio transport without a command",
|
|
32380
|
+
logger
|
|
32381
|
+
});
|
|
32382
|
+
return;
|
|
32383
|
+
}
|
|
32384
|
+
const converted = {
|
|
32385
|
+
transport: "stdio",
|
|
32386
|
+
command
|
|
32387
|
+
};
|
|
32388
|
+
if (args.length > 0) converted.args = args;
|
|
32389
|
+
return converted;
|
|
32390
|
+
}
|
|
32391
|
+
/**
|
|
32392
|
+
* Convert Command Code's server map back to the canonical shape. Both
|
|
32393
|
+
* spellings Command Code accepts (`transport` and its `type` alias, with
|
|
32394
|
+
* `command`/`url`) are already canonical, so entries pass through with only
|
|
32395
|
+
* prototype-pollution keys dropped; the native `enabled: false` maps to the
|
|
32396
|
+
* canonical `disabled: true` (a bare `enabled` would otherwise be read back
|
|
32397
|
+
* as rulesync's own generation filter and silently drop the server from every
|
|
32398
|
+
* other target).
|
|
32399
|
+
*/
|
|
32400
|
+
function convertFromCommandcodeFormat(mcpServers) {
|
|
32401
|
+
if (!isMcpServers(mcpServers)) return {};
|
|
32402
|
+
const result = {};
|
|
32403
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
32404
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
32405
|
+
const { enabled, ...rest } = omitPrototypePollutionKeys(serverConfig);
|
|
32406
|
+
result[serverName] = enabled === false ? {
|
|
32407
|
+
...rest,
|
|
32408
|
+
disabled: true
|
|
32409
|
+
} : rest;
|
|
32410
|
+
}
|
|
32411
|
+
return result;
|
|
32412
|
+
}
|
|
32413
|
+
/**
|
|
32414
|
+
* Command Code MCP configuration.
|
|
32415
|
+
*
|
|
32416
|
+
* Command Code reads `.mcp.json` at the project root (project scope, meant to
|
|
32417
|
+
* be committed) and `~/.commandcode/mcp.json` (user scope), both in the
|
|
32418
|
+
* `{ "mcpServers": { ... } }` shape the docs show; the per-machine local
|
|
32419
|
+
* scope under `~/.commandcode/projects/` is left to the tool. Top-level
|
|
32420
|
+
* sibling keys of an existing file are kept. The global file is not deleted
|
|
32421
|
+
* by `--delete` (it lives outside the project), while the project file is
|
|
32422
|
+
* rulesync's own and is.
|
|
32423
|
+
*
|
|
32424
|
+
* The project `.mcp.json` is the very file the `claudecode` target writes, so
|
|
32425
|
+
* at project scope the servers are written in the same pass-through shape
|
|
32426
|
+
* `ClaudecodeMcp` uses: whichever of the two targets generates last leaves
|
|
32427
|
+
* byte-identical content, and Command Code reads that shape natively (`type`
|
|
32428
|
+
* is an alias of `transport`, a bare `url` infers `http`, and unknown keys
|
|
32429
|
+
* are ignored). Only the global file — Command Code's own — gets the
|
|
32430
|
+
* `transport` / `enabled: false` rewrite of `convertToCommandcodeFormat`.
|
|
32431
|
+
*
|
|
32432
|
+
* @see https://commandcode.ai/docs/mcp
|
|
32433
|
+
*/
|
|
32434
|
+
var CommandcodeMcp = class CommandcodeMcp extends ToolMcp {
|
|
32435
|
+
json;
|
|
32436
|
+
constructor(params) {
|
|
32437
|
+
super(params);
|
|
32438
|
+
this.json = this.fileContent === void 0 ? {} : parseCommandcodeMcpConfig({
|
|
32439
|
+
fileContent: this.fileContent,
|
|
32440
|
+
relativePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath)
|
|
32441
|
+
});
|
|
32442
|
+
}
|
|
32443
|
+
getJson() {
|
|
32444
|
+
return this.json;
|
|
32445
|
+
}
|
|
32446
|
+
isDeletable() {
|
|
32447
|
+
return !this.global;
|
|
32448
|
+
}
|
|
32449
|
+
static getSettablePaths({ global = false } = {}) {
|
|
32450
|
+
return global ? {
|
|
32451
|
+
relativeDirPath: COMMANDCODE_DIR,
|
|
32452
|
+
relativeFilePath: COMMANDCODE_GLOBAL_MCP_FILE_NAME
|
|
32453
|
+
} : {
|
|
32454
|
+
relativeDirPath: ".",
|
|
32455
|
+
relativeFilePath: COMMANDCODE_PROJECT_MCP_FILE_NAME
|
|
32456
|
+
};
|
|
32457
|
+
}
|
|
32458
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
32459
|
+
const paths = this.getSettablePaths({ global });
|
|
32460
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
|
|
32461
|
+
return new CommandcodeMcp({
|
|
32462
|
+
outputRoot,
|
|
32463
|
+
relativeDirPath: paths.relativeDirPath,
|
|
32464
|
+
relativeFilePath: paths.relativeFilePath,
|
|
32465
|
+
fileContent,
|
|
32466
|
+
validate,
|
|
32467
|
+
global
|
|
32468
|
+
});
|
|
32469
|
+
}
|
|
32470
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
32471
|
+
const paths = this.getSettablePaths({ global });
|
|
32472
|
+
const json = parseCommandcodeMcpConfig({
|
|
32473
|
+
fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
|
|
32474
|
+
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
32475
|
+
});
|
|
32476
|
+
const mcpServers = global ? convertToCommandcodeFormat(rulesyncMcp.getMcpServers(), logger) : rulesyncMcp.getMcpServers();
|
|
32477
|
+
const commandcodeConfig = {
|
|
32478
|
+
...json,
|
|
32479
|
+
mcpServers
|
|
32480
|
+
};
|
|
32481
|
+
return new CommandcodeMcp({
|
|
32482
|
+
outputRoot,
|
|
32483
|
+
relativeDirPath: paths.relativeDirPath,
|
|
32484
|
+
relativeFilePath: paths.relativeFilePath,
|
|
32485
|
+
fileContent: JSON.stringify(commandcodeConfig, null, 2),
|
|
32486
|
+
validate,
|
|
32487
|
+
global
|
|
32488
|
+
});
|
|
32489
|
+
}
|
|
32490
|
+
toRulesyncMcp() {
|
|
32491
|
+
const mcpServers = convertFromCommandcodeFormat(this.json.mcpServers);
|
|
32492
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
32493
|
+
}
|
|
32494
|
+
validate() {
|
|
32495
|
+
return {
|
|
32496
|
+
success: true,
|
|
32497
|
+
error: null
|
|
32498
|
+
};
|
|
32499
|
+
}
|
|
32500
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
32501
|
+
return new CommandcodeMcp({
|
|
32502
|
+
outputRoot,
|
|
32503
|
+
relativeDirPath,
|
|
32504
|
+
relativeFilePath,
|
|
32505
|
+
fileContent: "{}",
|
|
32506
|
+
validate: false,
|
|
32507
|
+
global
|
|
32508
|
+
});
|
|
32509
|
+
}
|
|
32510
|
+
};
|
|
32511
|
+
//#endregion
|
|
31959
32512
|
//#region src/features/mcp/continue-mcp.ts
|
|
31960
32513
|
/**
|
|
31961
32514
|
* Parse a Continue MCP file. Continue reads the files under `mcpServers/` as
|
|
@@ -36924,6 +37477,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
36924
37477
|
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
36925
37478
|
const servers = rulesyncMcp.forTarget({
|
|
36926
37479
|
toolTarget: "rovodev",
|
|
37480
|
+
global,
|
|
36927
37481
|
logger
|
|
36928
37482
|
}).getMcpServers();
|
|
36929
37483
|
const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
|
|
@@ -38221,6 +38775,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
38221
38775
|
supportsDisabledTools: true
|
|
38222
38776
|
}
|
|
38223
38777
|
}],
|
|
38778
|
+
["commandcode", {
|
|
38779
|
+
class: CommandcodeMcp,
|
|
38780
|
+
meta: {
|
|
38781
|
+
supportsProject: true,
|
|
38782
|
+
supportsGlobal: true,
|
|
38783
|
+
supportsEnabledTools: false,
|
|
38784
|
+
supportsDisabledTools: false
|
|
38785
|
+
}
|
|
38786
|
+
}],
|
|
38224
38787
|
["continue", {
|
|
38225
38788
|
class: ContinueMcp,
|
|
38226
38789
|
meta: {
|
|
@@ -38614,6 +39177,7 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
38614
39177
|
const toolMcps = await Promise.all([rulesyncMcp].map(async (mcp) => {
|
|
38615
39178
|
const targetedRulesyncMcp = mcp.forTarget({
|
|
38616
39179
|
toolTarget: this.toolTarget,
|
|
39180
|
+
global: this.global,
|
|
38617
39181
|
logger: this.logger
|
|
38618
39182
|
});
|
|
38619
39183
|
const fieldsToStrip = [];
|
|
@@ -42608,6 +43172,622 @@ function mapBashActionToDecision(action) {
|
|
|
42608
43172
|
return "forbidden";
|
|
42609
43173
|
}
|
|
42610
43174
|
//#endregion
|
|
43175
|
+
//#region src/features/permissions/commandcode-permissions.ts
|
|
43176
|
+
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
43177
|
+
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
43178
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
43179
|
+
const MCP_CANONICAL_PREFIX$5 = "mcp__";
|
|
43180
|
+
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
43181
|
+
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
43182
|
+
bash: "Shell",
|
|
43183
|
+
read: "Read",
|
|
43184
|
+
edit: "Edit",
|
|
43185
|
+
write: "Write",
|
|
43186
|
+
grep: "Grep",
|
|
43187
|
+
glob: "Glob",
|
|
43188
|
+
webfetch: "WebFetch",
|
|
43189
|
+
websearch: "WebSearch"
|
|
43190
|
+
};
|
|
43191
|
+
const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
43192
|
+
...Object.fromEntries(Object.entries(CATEGORY_TO_COMMANDCODE_TOOL).map(([category, tool]) => [tool.toLowerCase(), category])),
|
|
43193
|
+
bash: "bash",
|
|
43194
|
+
powershell: "bash",
|
|
43195
|
+
shell_command: "bash",
|
|
43196
|
+
monitor_command: "bash",
|
|
43197
|
+
kill_shell: "bash",
|
|
43198
|
+
notebookedit: "edit",
|
|
43199
|
+
write_file: "write",
|
|
43200
|
+
web_fetch: "webfetch",
|
|
43201
|
+
web_search: "websearch"
|
|
43202
|
+
};
|
|
43203
|
+
const ACTION_RANK$1 = {
|
|
43204
|
+
allow: 0,
|
|
43205
|
+
ask: 1,
|
|
43206
|
+
deny: 2
|
|
43207
|
+
};
|
|
43208
|
+
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
43209
|
+
function isStricterAction({ action, existing }) {
|
|
43210
|
+
return existing === void 0 || ACTION_RANK$1[action] > ACTION_RANK$1[existing];
|
|
43211
|
+
}
|
|
43212
|
+
/**
|
|
43213
|
+
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
43214
|
+
* the whitespace makes it a pattern rule, and a shell pattern grants only
|
|
43215
|
+
* commands that pass Command Code's pattern gate.
|
|
43216
|
+
*/
|
|
43217
|
+
const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (the command must parse into words and carry no environment assignment)";
|
|
43218
|
+
/**
|
|
43219
|
+
* Build a Command Code rule (`Shell(git *)`, `Read`, `mcp__github__get_issue`,
|
|
43220
|
+
* `mcp__*`, `*`) from a canonical category + pattern. Returns `null` for
|
|
43221
|
+
* categories Command Code cannot express so the caller can skip them.
|
|
43222
|
+
*
|
|
43223
|
+
* MCP tools are keyed by their canonical `mcp__server__tool` name, so a
|
|
43224
|
+
* scoped `mcp__<remainder>` category is written verbatim and the bare `mcp`
|
|
43225
|
+
* category becomes `mcp__*` (or `mcp__<pattern>` when the pattern names a
|
|
43226
|
+
* server or tool). A pattern on an MCP category is still written as the
|
|
43227
|
+
* `(specifier)` here; `writableCommandcodeRule` decides what to do with it,
|
|
43228
|
+
* because Command Code ignores it. The all-tools `*` category is only
|
|
43229
|
+
* written for its catch-all pattern; narrower `*` patterns are shell
|
|
43230
|
+
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
43231
|
+
*/
|
|
43232
|
+
function buildCommandcodeRule(category, pattern) {
|
|
43233
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$6 || pattern === "";
|
|
43234
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$5)) return catchAll ? category : `${category}(${pattern})`;
|
|
43235
|
+
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$5}${pattern}`;
|
|
43236
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$6 : null;
|
|
43237
|
+
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
43238
|
+
if (tool === void 0) return null;
|
|
43239
|
+
return catchAll ? tool : `${tool}(${pattern})`;
|
|
43240
|
+
}
|
|
43241
|
+
/**
|
|
43242
|
+
* Split `Tool(specifier)` into its two halves the way Command Code's
|
|
43243
|
+
* `splitRule` does (`command-code` 1.54.0): the rule is split at the first
|
|
43244
|
+
* unescaped `(` and must end with `)`, the tool half is trimmed (so
|
|
43245
|
+
* `Shell (rm -rf *)` is a `Shell` rule), and a `\(` / `\)` in the specifier
|
|
43246
|
+
* is an escaped parenthesis. `Tool` alone has an empty specifier. A rule that
|
|
43247
|
+
* opens a parenthesis without closing one is not a rule to Command Code at
|
|
43248
|
+
* all, so it comes back with an empty tool half and stays unmodeled. Nothing
|
|
43249
|
+
* else in the specifier is touched: Command Code does not trim it (the shell
|
|
43250
|
+
* matcher normalizes whitespace on its own, see `parseCommandcodeRule`), so
|
|
43251
|
+
* `Read( )` is a rule for the pattern `" "`, which matches nothing, not for
|
|
43252
|
+
* the whole tool.
|
|
43253
|
+
*/
|
|
43254
|
+
function splitCommandcodeRule(rule) {
|
|
43255
|
+
const trimmed = rule.trim();
|
|
43256
|
+
const parenIndex = trimmed.search(/(?<!\\)\(/);
|
|
43257
|
+
if (parenIndex === -1) return {
|
|
43258
|
+
tool: trimmed,
|
|
43259
|
+
inner: ""
|
|
43260
|
+
};
|
|
43261
|
+
if (!trimmed.endsWith(")")) return {
|
|
43262
|
+
tool: "",
|
|
43263
|
+
inner: ""
|
|
43264
|
+
};
|
|
43265
|
+
return {
|
|
43266
|
+
tool: trimmed.slice(0, parenIndex).trim(),
|
|
43267
|
+
inner: trimmed.slice(parenIndex + 1, -1).replace(/\\([()])/g, "$1")
|
|
43268
|
+
};
|
|
43269
|
+
}
|
|
43270
|
+
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
43271
|
+
function isCatchAllSpecifier(inner) {
|
|
43272
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$6;
|
|
43273
|
+
}
|
|
43274
|
+
/**
|
|
43275
|
+
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
43276
|
+
* `parsePermissionRule` / `parseMcpToken`). The MCP shape is only recognized
|
|
43277
|
+
* by its exact `mcp__` prefix, and such a rule never carries a specifier:
|
|
43278
|
+
* `mcp__<server>__<tool>(owner:foo)` denies, asks or allows the whole tool.
|
|
43279
|
+
* Its names are matched case-sensitively against the registered server and
|
|
43280
|
+
* tool (Command Code never lowercases them), so the remainder keeps its case
|
|
43281
|
+
* as the canonical category. `mcp__<server>`, `mcp__<server>__` and
|
|
43282
|
+
* `mcp__<server>__*` are the whole server (category `mcp__<server>`), and a
|
|
43283
|
+
* `*` server (`mcp__*`, `mcp__*__<tool>`) is every MCP tool, honored in
|
|
43284
|
+
* `deny`/`ask` only. A differently-cased `MCP__...` spelling is read as a
|
|
43285
|
+
* plain tool-name rule instead, matched case-insensitively:
|
|
43286
|
+
* `MCP__<server>__<tool>` matches that tool by name (so it folds onto the
|
|
43287
|
+
* `mcp__` category, remainder as written), `MCP__<server>__<tool>(specifier)`
|
|
43288
|
+
* globs the specifier against the call's `command` / `file_path` / `path` /
|
|
43289
|
+
* `url` / `pattern` argument (in `deny`/`ask` a `param:glob` form is tried
|
|
43290
|
+
* first) — kept as a pattern in `deny`/`ask`, but not imported from `allow`,
|
|
43291
|
+
* where a grant scoped that way cannot be modeled without widening it; the
|
|
43292
|
+
* globs
|
|
43293
|
+
* `MCP__*` (every MCP tool) and `MCP__<server>__*` (the whole server) match
|
|
43294
|
+
* by name in `deny`/`ask` only, and `MCP__<server>` names no tool at all.
|
|
43295
|
+
* Whatever matches nothing anywhere (or a glob rulesync does not model)
|
|
43296
|
+
* returns `null` and is left alone.
|
|
43297
|
+
*/
|
|
43298
|
+
function parseMcpCommandcodeRule({ tool, inner }) {
|
|
43299
|
+
const remainder = tool.slice(5);
|
|
43300
|
+
const separator = remainder.indexOf("__");
|
|
43301
|
+
const server = separator === -1 ? remainder : remainder.slice(0, separator);
|
|
43302
|
+
const rest = separator === -1 ? "" : remainder.slice(separator + 2);
|
|
43303
|
+
if (server.length === 0) return null;
|
|
43304
|
+
const everyServer = {
|
|
43305
|
+
category: "mcp",
|
|
43306
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43307
|
+
notInAllow: true
|
|
43308
|
+
};
|
|
43309
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$5)) {
|
|
43310
|
+
if (server === CATCH_ALL_PATTERN$6) return everyServer;
|
|
43311
|
+
if (server.includes(CATCH_ALL_PATTERN$6)) return null;
|
|
43312
|
+
return {
|
|
43313
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$6 ? `${MCP_CANONICAL_PREFIX$5}${server}` : `${MCP_CANONICAL_PREFIX$5}${remainder}`,
|
|
43314
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43315
|
+
notInAllow: false
|
|
43316
|
+
};
|
|
43317
|
+
}
|
|
43318
|
+
const category = `${MCP_CANONICAL_PREFIX$5}${remainder}`;
|
|
43319
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$6);
|
|
43320
|
+
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
43321
|
+
category,
|
|
43322
|
+
pattern: inner,
|
|
43323
|
+
notInAllow: true
|
|
43324
|
+
} : null;
|
|
43325
|
+
if (remainder === CATCH_ALL_PATTERN$6) return everyServer;
|
|
43326
|
+
if (rest === CATCH_ALL_PATTERN$6 && !server.includes(CATCH_ALL_PATTERN$6)) return {
|
|
43327
|
+
category: `${MCP_CANONICAL_PREFIX$5}${server}`,
|
|
43328
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43329
|
+
notInAllow: true
|
|
43330
|
+
};
|
|
43331
|
+
return namesTool ? {
|
|
43332
|
+
category,
|
|
43333
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43334
|
+
notInAllow: false
|
|
43335
|
+
} : null;
|
|
43336
|
+
}
|
|
43337
|
+
/**
|
|
43338
|
+
* A rule on one of the friendly tools, with its specifier read as Command
|
|
43339
|
+
* Code matches it. The shell matcher trims and collapses whitespace on both
|
|
43340
|
+
* the pattern and the command, so `Shell( git * )` is `Shell(git *)` and a
|
|
43341
|
+
* pattern that is blank once normalized (`Shell( )`), or a bare `:*` whose
|
|
43342
|
+
* prefix is empty (`Shell(:*)`), matches nothing. A padded `Shell( * )` is
|
|
43343
|
+
* not the bare `Shell`: only a specifier of exactly
|
|
43344
|
+
* `` or `*` is dropped by the rule parser, so `( * )` stays a pattern rule —
|
|
43345
|
+
* in `deny`/`ask` it matches every command (the whole tool), while in `allow`
|
|
43346
|
+
* it is narrower than the bare `Shell` (it needs a command that parses into
|
|
43347
|
+
* words and has no environment-assignment prefix), so it is the whole tool
|
|
43348
|
+
* marked `notInAllow`. The path, web and tool-name matchers use the
|
|
43349
|
+
* specifier as written, so one that is blank or `*` only once trimmed
|
|
43350
|
+
* (`Read( * )`) matches nothing. What matches nothing comes back as `null`:
|
|
43351
|
+
* the rule is unmodeled and left alone.
|
|
43352
|
+
*/
|
|
43353
|
+
function friendlyToolRule({ category, inner }) {
|
|
43354
|
+
if (category === "bash") {
|
|
43355
|
+
if (isCatchAllSpecifier(inner)) return {
|
|
43356
|
+
category,
|
|
43357
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43358
|
+
notInAllow: false
|
|
43359
|
+
};
|
|
43360
|
+
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
43361
|
+
if (normalized === "" || normalized === ":*") return null;
|
|
43362
|
+
return isCatchAllSpecifier(normalized) ? {
|
|
43363
|
+
category,
|
|
43364
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43365
|
+
notInAllow: true
|
|
43366
|
+
} : {
|
|
43367
|
+
category,
|
|
43368
|
+
pattern: normalized,
|
|
43369
|
+
notInAllow: false
|
|
43370
|
+
};
|
|
43371
|
+
}
|
|
43372
|
+
if (isCatchAllSpecifier(inner)) return {
|
|
43373
|
+
category,
|
|
43374
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43375
|
+
notInAllow: false
|
|
43376
|
+
};
|
|
43377
|
+
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
43378
|
+
category,
|
|
43379
|
+
pattern: inner,
|
|
43380
|
+
notInAllow: false
|
|
43381
|
+
};
|
|
43382
|
+
}
|
|
43383
|
+
/**
|
|
43384
|
+
* Parse a Command Code rule back into canonical terms. Tool names fold case;
|
|
43385
|
+
* `Tool`, `Tool()` and `Tool(*)` all mean the whole tool; MCP-shaped rules
|
|
43386
|
+
* go through `parseMcpCommandcodeRule`. Returns `null` for a rule rulesync
|
|
43387
|
+
* cannot model (an exact internal tool name such as `edit_file`, a
|
|
43388
|
+
* name-wildcard like `edit_*`, a specifier on a rule that takes none, or a
|
|
43389
|
+
* rule Command Code enforces nothing for, such as `Agent`).
|
|
43390
|
+
*/
|
|
43391
|
+
function parseCommandcodeRule(rule) {
|
|
43392
|
+
const { tool, inner } = splitCommandcodeRule(rule);
|
|
43393
|
+
if (tool === CATCH_ALL_PATTERN$6) return isCatchAllSpecifier(inner) ? {
|
|
43394
|
+
category: "*",
|
|
43395
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
43396
|
+
notInAllow: true
|
|
43397
|
+
} : null;
|
|
43398
|
+
const lowered = tool.toLowerCase();
|
|
43399
|
+
if (lowered.startsWith(MCP_CANONICAL_PREFIX$5)) return parseMcpCommandcodeRule({
|
|
43400
|
+
tool,
|
|
43401
|
+
inner
|
|
43402
|
+
});
|
|
43403
|
+
const category = Object.hasOwn(COMMANDCODE_TOOL_TO_CATEGORY, lowered) ? COMMANDCODE_TOOL_TO_CATEGORY[lowered] : void 0;
|
|
43404
|
+
if (category === void 0) return null;
|
|
43405
|
+
return friendlyToolRule({
|
|
43406
|
+
category,
|
|
43407
|
+
inner
|
|
43408
|
+
});
|
|
43409
|
+
}
|
|
43410
|
+
/**
|
|
43411
|
+
* Whether an imported rule is a differently-cased `MCP__<server>__<tool>`
|
|
43412
|
+
* name that folded onto an exact-prefix `mcp__` category (which Command Code
|
|
43413
|
+
* matches case-sensitively, unlike the name rule it came from).
|
|
43414
|
+
*/
|
|
43415
|
+
function isFoldedMcpToolNameRule({ rule, category }) {
|
|
43416
|
+
const { tool } = splitCommandcodeRule(rule);
|
|
43417
|
+
return category.startsWith(MCP_CANONICAL_PREFIX$5) && !tool.startsWith(MCP_CANONICAL_PREFIX$5);
|
|
43418
|
+
}
|
|
43419
|
+
/** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
|
|
43420
|
+
function scopedMcpRuleTool(rule) {
|
|
43421
|
+
const { tool, inner } = splitCommandcodeRule(rule);
|
|
43422
|
+
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$5) ? tool : null;
|
|
43423
|
+
}
|
|
43424
|
+
/**
|
|
43425
|
+
* The string entries of a Command Code permission list. Command Code reads
|
|
43426
|
+
* each entry on its own and skips one that is not a string, so a stray
|
|
43427
|
+
* `null` beside `Shell(rm -rf *)` must not throw the deny away with it; the
|
|
43428
|
+
* skipped entries are counted in a warning because they cannot be kept.
|
|
43429
|
+
*/
|
|
43430
|
+
function stringRules({ list, action, outcome, warn }) {
|
|
43431
|
+
if (!Array.isArray(list)) return [];
|
|
43432
|
+
const rules = list.filter((entry) => typeof entry === "string");
|
|
43433
|
+
const skipped = list.length - rules.length;
|
|
43434
|
+
if (skipped > 0) warn(`Command Code permission list "${action}" holds ${skipped} ${skipped === 1 ? "entry" : "entries"} that ${skipped === 1 ? "is" : "are"} not a string, which Command Code skips; ${skipped === 1 ? "that entry was" : "those entries were"} ${outcome}.`);
|
|
43435
|
+
return rules;
|
|
43436
|
+
}
|
|
43437
|
+
/**
|
|
43438
|
+
* The canonical categories this run rebuilds — every category the canonical
|
|
43439
|
+
* config names (an empty `bash: {}` still reclaims the previous `Shell(...)`
|
|
43440
|
+
* entries, as in the Claude Code adapter) plus every rule it emits, keyed the
|
|
43441
|
+
* way an existing entry parses back (so `mcp: { github: "deny" }`, written as
|
|
43442
|
+
* `mcp__github`, claims the `mcp__github` entries of the previous run; case
|
|
43443
|
+
* variants of the friendly tool names fold together, while MCP names are
|
|
43444
|
+
* matched exactly, as Command Code does). An existing entry whose tool folds onto one of them is
|
|
43445
|
+
* rulesync's to replace, whatever list it sits in — otherwise flipping a rule
|
|
43446
|
+
* from deny to allow would leave the old deny behind and win. Every other
|
|
43447
|
+
* entry — an internal tool name rulesync cannot model, or a modeled tool the
|
|
43448
|
+
* canonical config does not mention — is the user's (Command Code writes
|
|
43449
|
+
* interactive approvals into the same lists) and is preserved verbatim.
|
|
43450
|
+
* A `deny`/`ask` entry that is reclaimed without an equally strict rule
|
|
43451
|
+
* taking its place (say a hand-written `mcp__*` deny next to a canonical
|
|
43452
|
+
* `mcp: { github: "deny" }`) is reported, so regenerating never silently
|
|
43453
|
+
* loosens what the user wrote. Mirrors `managedClaudeToolNames` in the
|
|
43454
|
+
* Claude Code adapter.
|
|
43455
|
+
*/
|
|
43456
|
+
function preservedRules({ existingPermissions, key, managedCategories, written, logger }) {
|
|
43457
|
+
return stringRules({
|
|
43458
|
+
list: existingPermissions[key],
|
|
43459
|
+
action: key,
|
|
43460
|
+
outcome: "dropped from the regenerated list",
|
|
43461
|
+
warn: (message) => logger?.warn(message)
|
|
43462
|
+
}).filter((rule) => {
|
|
43463
|
+
const parsed = parseCommandcodeRule(rule);
|
|
43464
|
+
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
43465
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$6})`);
|
|
43466
|
+
if (key !== "allow" && isStricterAction({
|
|
43467
|
+
action: key,
|
|
43468
|
+
existing: replacement
|
|
43469
|
+
})) logger?.warn(`Command Code permission rule '${rule}' in "${key}" belongs to the '${parsed.category}' category, which the rulesync config now manages, and was replaced by its rules.`);
|
|
43470
|
+
return false;
|
|
43471
|
+
});
|
|
43472
|
+
}
|
|
43473
|
+
/**
|
|
43474
|
+
* The rule to write for a canonical rule, or `null` when Command Code would
|
|
43475
|
+
* ignore it in the `action` list. A server-less wildcard (`*`, `mcp__*`) in
|
|
43476
|
+
* `allow` is warned about and left unwritten rather than written dead. A
|
|
43477
|
+
* specifier on an MCP rule is ignored by Command Code in every list, so a
|
|
43478
|
+
* scoped `deny`/`ask` is written as the bare tool (which is what Command
|
|
43479
|
+
* Code would enforce anyway, only now visibly) with a warning, and a scoped
|
|
43480
|
+
* `allow` — which would grant the whole tool — is refused. Judged on the
|
|
43481
|
+
* rule as written, so the bare `mcp` category cannot smuggle a specifier in
|
|
43482
|
+
* through a `<server>__<tool>(specifier)` pattern.
|
|
43483
|
+
*/
|
|
43484
|
+
function writableCommandcodeRule({ rule, emitted, action, logger }) {
|
|
43485
|
+
if (action === "allow" && emitted.notInAllow) {
|
|
43486
|
+
logger?.warn(emitted.category === "bash" ? `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, so the '${emitted.category}' allow rule was not written; use the '*' pattern for the whole tool.` : `Command Code does not honor '${rule}' in "allow" as written (an allow rule must name what it grants), so the '${emitted.category}' allow rule was not written.`);
|
|
43487
|
+
return null;
|
|
43488
|
+
}
|
|
43489
|
+
const tool = scopedMcpRuleTool(rule);
|
|
43490
|
+
if (tool === null) return rule;
|
|
43491
|
+
if (action === "allow") {
|
|
43492
|
+
logger?.warn(`Command Code ignores the specifier of an MCP rule and would allow the whole '${tool}' tool, so the '${rule}' allow rule was not written.`);
|
|
43493
|
+
return null;
|
|
43494
|
+
}
|
|
43495
|
+
logger?.warn(`Command Code ignores the specifier of an MCP rule, so '${rule}' was written as '${tool}' and applies "${action}" to the whole tool.`);
|
|
43496
|
+
return tool;
|
|
43497
|
+
}
|
|
43498
|
+
/**
|
|
43499
|
+
* Record `action` for `rule`, keeping the stricter one (deny > ask > allow)
|
|
43500
|
+
* when two canonical rules collapse onto the same Command Code entry.
|
|
43501
|
+
*/
|
|
43502
|
+
function rankCommandcodeRule({ ranked, rule, action, logger }) {
|
|
43503
|
+
const existing = ranked.get(rule);
|
|
43504
|
+
if (existing !== void 0 && existing !== action) logger?.warn(`Command Code permission rule '${rule}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
|
|
43505
|
+
if (isStricterAction({
|
|
43506
|
+
action,
|
|
43507
|
+
existing
|
|
43508
|
+
})) ranked.set(rule, action);
|
|
43509
|
+
}
|
|
43510
|
+
/**
|
|
43511
|
+
* Bucket the canonical rules into Command Code's `allow`/`ask`/`deny` lists.
|
|
43512
|
+
* Collisions resolve to the strictest action (deny > ask > allow); categories
|
|
43513
|
+
* Command Code cannot express are skipped with a warning when they carry a
|
|
43514
|
+
* `deny`; rules Command Code would ignore in the target list are dropped or
|
|
43515
|
+
* rewritten by `writableCommandcodeRule`.
|
|
43516
|
+
*/
|
|
43517
|
+
function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
43518
|
+
const permission = honorAllToolsOnBash(config.permission);
|
|
43519
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
43520
|
+
const managedCategories = /* @__PURE__ */ new Set();
|
|
43521
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
43522
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$6);
|
|
43523
|
+
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
43524
|
+
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
43525
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
43526
|
+
const rule = buildCommandcodeRule(category, pattern);
|
|
43527
|
+
const emitted = rule === null ? null : parseCommandcodeRule(rule);
|
|
43528
|
+
if (rule === null || emitted === null) {
|
|
43529
|
+
const honoredOnShell = category === "*" && permission.bash !== void 0;
|
|
43530
|
+
if (action === "deny" && !honoredOnShell) logger?.warn(`Command Code has no permission rule for the '${category}' category with pattern '${pattern}'; its 'deny' rule could not be represented and was skipped.`);
|
|
43531
|
+
continue;
|
|
43532
|
+
}
|
|
43533
|
+
managedCategories.add(emitted.category);
|
|
43534
|
+
const written = writableCommandcodeRule({
|
|
43535
|
+
rule,
|
|
43536
|
+
emitted,
|
|
43537
|
+
action,
|
|
43538
|
+
logger
|
|
43539
|
+
});
|
|
43540
|
+
if (written !== null) rankCommandcodeRule({
|
|
43541
|
+
ranked,
|
|
43542
|
+
rule: written,
|
|
43543
|
+
action,
|
|
43544
|
+
logger
|
|
43545
|
+
});
|
|
43546
|
+
}
|
|
43547
|
+
}
|
|
43548
|
+
const written = /* @__PURE__ */ new Map();
|
|
43549
|
+
for (const [rule, action] of ranked) {
|
|
43550
|
+
const parsed = parseCommandcodeRule(rule);
|
|
43551
|
+
if (parsed !== null) written.set(`${parsed.category}(${parsed.pattern})`, action);
|
|
43552
|
+
}
|
|
43553
|
+
const preserved = {
|
|
43554
|
+
existingPermissions,
|
|
43555
|
+
managedCategories,
|
|
43556
|
+
written,
|
|
43557
|
+
logger
|
|
43558
|
+
};
|
|
43559
|
+
const allow = preservedRules({
|
|
43560
|
+
...preserved,
|
|
43561
|
+
key: "allow"
|
|
43562
|
+
});
|
|
43563
|
+
const ask = preservedRules({
|
|
43564
|
+
...preserved,
|
|
43565
|
+
key: "ask"
|
|
43566
|
+
});
|
|
43567
|
+
const deny = preservedRules({
|
|
43568
|
+
...preserved,
|
|
43569
|
+
key: "deny"
|
|
43570
|
+
});
|
|
43571
|
+
for (const [rule, action] of ranked) if (action === "allow") allow.push(rule);
|
|
43572
|
+
else if (action === "ask") ask.push(rule);
|
|
43573
|
+
else deny.push(rule);
|
|
43574
|
+
return {
|
|
43575
|
+
allow: (0, es_toolkit.uniq)(allow.toSorted()),
|
|
43576
|
+
ask: (0, es_toolkit.uniq)(ask.toSorted()),
|
|
43577
|
+
deny: (0, es_toolkit.uniq)(deny.toSorted())
|
|
43578
|
+
};
|
|
43579
|
+
}
|
|
43580
|
+
/**
|
|
43581
|
+
* Why a rule marked `notInAllow` is not imported from `allow`: Command Code
|
|
43582
|
+
* ignores a server-less wildcard or a tool-name glob there, while it does
|
|
43583
|
+
* enforce a scoped `MCP__<server>__<tool>(specifier)` — as a glob over the
|
|
43584
|
+
* call's arguments — and a padded `Shell( * )` — as a pattern narrower than
|
|
43585
|
+
* the bare `Shell` — which rulesync cannot model in `allow` without widening
|
|
43586
|
+
* the grant.
|
|
43587
|
+
*/
|
|
43588
|
+
function skippedAllowImportMessage({ rule, category }) {
|
|
43589
|
+
const { tool, inner } = splitCommandcodeRule(rule);
|
|
43590
|
+
if (category === "bash") return `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, which rulesync cannot import without widening the grant to every command, so it was not imported.`;
|
|
43591
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$5) || isCatchAllSpecifier(inner)) return `Command Code ignores '${rule}' in "allow" (an allow rule must name what it grants), so it was not imported.`;
|
|
43592
|
+
return `Command Code globs the specifier of '${rule}' in "allow" against the call's arguments, which rulesync cannot import without widening the grant to the whole tool, so it was not imported.`;
|
|
43593
|
+
}
|
|
43594
|
+
/**
|
|
43595
|
+
* Parse Command Code's `permissions` lists back into a canonical permission
|
|
43596
|
+
* map with `deny > ask > allow` precedence, so a tool described more than
|
|
43597
|
+
* once resolves to the strictest action. Each rule is imported as what
|
|
43598
|
+
* Command Code enforces for it, never as what it looks like: a rule Command
|
|
43599
|
+
* Code ignores in `allow` (`*`, `mcp__*`, an `MCP__...` glob) is skipped
|
|
43600
|
+
* there with a warning, since importing it would turn a dead line into a
|
|
43601
|
+
* live grant for every other target, and so is a scoped
|
|
43602
|
+
* `MCP__<server>__<tool>(specifier)` allow, which Command Code enforces as a
|
|
43603
|
+
* glob over the call's arguments but rulesync cannot import without widening
|
|
43604
|
+
* it to the whole tool; the specifier of an exact-prefix `mcp__...` rule,
|
|
43605
|
+
* which Command Code drops, is folded to the whole tool in every list — with
|
|
43606
|
+
* a warning in `allow`, where the fold widens the grant the user wrote. A
|
|
43607
|
+
* tool-half glob in that exact-prefix spelling (`mcp__<server>__get_*`), which
|
|
43608
|
+
* Command Code matches against tool names in every list, is imported as the
|
|
43609
|
+
* category spelled that way, the same one the generator writes back; other
|
|
43610
|
+
* targets read it as a literal tool name.
|
|
43611
|
+
*/
|
|
43612
|
+
function parseCommandcodeRuleLists(permissions) {
|
|
43613
|
+
const permission = {};
|
|
43614
|
+
const lists = [
|
|
43615
|
+
["allow", permissions.allow],
|
|
43616
|
+
["ask", permissions.ask],
|
|
43617
|
+
["deny", permissions.deny]
|
|
43618
|
+
];
|
|
43619
|
+
for (const [action, rawList] of lists) {
|
|
43620
|
+
const list = stringRules({
|
|
43621
|
+
list: rawList,
|
|
43622
|
+
action,
|
|
43623
|
+
outcome: "not imported",
|
|
43624
|
+
warn: (message) => fallbackLogger.warn(message)
|
|
43625
|
+
});
|
|
43626
|
+
for (const rule of list) {
|
|
43627
|
+
const parsed = parseCommandcodeRule(rule);
|
|
43628
|
+
if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) {
|
|
43629
|
+
if (action !== "allow") fallbackLogger.warn(`Command Code permission rule '${rule}' in "${action}" is not one rulesync can model, so it could not be imported; it stays in the Command Code settings, where a regenerate keeps it unless the canonical config manages its category.`);
|
|
43630
|
+
continue;
|
|
43631
|
+
}
|
|
43632
|
+
const { category, pattern } = parsed;
|
|
43633
|
+
if (action === "allow" && parsed.notInAllow) {
|
|
43634
|
+
fallbackLogger.warn(skippedAllowImportMessage({
|
|
43635
|
+
rule,
|
|
43636
|
+
category
|
|
43637
|
+
}));
|
|
43638
|
+
continue;
|
|
43639
|
+
}
|
|
43640
|
+
if (action === "allow" && scopedMcpRuleTool(rule) !== null) fallbackLogger.warn(`Command Code ignores the specifier of an MCP rule, so '${rule}' in "allow" was imported as the whole '${category}' tool, which is what Command Code grants for it.`);
|
|
43641
|
+
if (isFoldedMcpToolNameRule({
|
|
43642
|
+
rule,
|
|
43643
|
+
category
|
|
43644
|
+
})) fallbackLogger.warn(`Command Code matches '${rule}' in "${action}" against MCP tool names case-insensitively, but the '${category}' rule generated from it matches the server and tool names exactly as written; spell the rule the way the tool is registered if they differ.`);
|
|
43645
|
+
const rules = permission[category] ??= {};
|
|
43646
|
+
if (isStricterAction({
|
|
43647
|
+
action,
|
|
43648
|
+
existing: rules[pattern]
|
|
43649
|
+
})) rules[pattern] = action;
|
|
43650
|
+
}
|
|
43651
|
+
}
|
|
43652
|
+
return permission;
|
|
43653
|
+
}
|
|
43654
|
+
/**
|
|
43655
|
+
* Permissions generator for Command Code.
|
|
43656
|
+
*
|
|
43657
|
+
* Rules live under the `permissions` key of `.commandcode/settings.json`
|
|
43658
|
+
* (project) and `~/.commandcode/settings.json` (user) as three lists of
|
|
43659
|
+
* Claude-style entries: `deny` (always wins), `ask`, `allow`. The same key
|
|
43660
|
+
* also carries `defaultMode`, `additionalDirectories` and `disableBypass`,
|
|
43661
|
+
* and the file holds `hooks` and other settings, so writes go through the
|
|
43662
|
+
* shared-config gateway: rulesync replaces the three lists, keeps every
|
|
43663
|
+
* sibling key, and never deletes the file.
|
|
43664
|
+
*
|
|
43665
|
+
* Generate: `permission.<category>.<pattern>` becomes `Tool(pattern)` with
|
|
43666
|
+
* the friendly names (`Shell`, `Read`, `Edit`, `Write`, `WebFetch`,
|
|
43667
|
+
* `WebSearch`), `mcp__<server>__<tool>` names pass through, and the all-tools
|
|
43668
|
+
* `*` category becomes the bare `*` rule Command Code accepts in `deny`/`ask`.
|
|
43669
|
+
* Only the entries of the categories the canonical config names are rebuilt;
|
|
43670
|
+
* every other existing entry is preserved verbatim.
|
|
43671
|
+
* Import: the lists are parsed back as what Command Code enforces for each
|
|
43672
|
+
* entry — friendly tool names case-insensitively (with the internal aliases
|
|
43673
|
+
* such as `Bash`, `PowerShell` and `write_file` folding onto their
|
|
43674
|
+
* category), MCP names exactly; rules for internal tool names rulesync does
|
|
43675
|
+
* not model are skipped.
|
|
43676
|
+
*
|
|
43677
|
+
* @see https://commandcode.ai/docs/permissions
|
|
43678
|
+
* @see https://commandcode.ai/docs/settings
|
|
43679
|
+
*/
|
|
43680
|
+
var CommandcodePermissions = class CommandcodePermissions extends ToolPermissions {
|
|
43681
|
+
constructor(params) {
|
|
43682
|
+
super({
|
|
43683
|
+
...params,
|
|
43684
|
+
fileContent: params.fileContent ?? "{}"
|
|
43685
|
+
});
|
|
43686
|
+
}
|
|
43687
|
+
/**
|
|
43688
|
+
* `settings.json` holds hooks and other user settings, so it is never
|
|
43689
|
+
* deleted; clearing permissions happens via an in-place merge.
|
|
43690
|
+
*/
|
|
43691
|
+
isDeletable() {
|
|
43692
|
+
return false;
|
|
43693
|
+
}
|
|
43694
|
+
static getSettablePaths(_options = {}) {
|
|
43695
|
+
return {
|
|
43696
|
+
relativeDirPath: COMMANDCODE_DIR,
|
|
43697
|
+
relativeFilePath: COMMANDCODE_SETTINGS_FILE_NAME
|
|
43698
|
+
};
|
|
43699
|
+
}
|
|
43700
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
43701
|
+
const paths = CommandcodePermissions.getSettablePaths({ global });
|
|
43702
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
43703
|
+
return new CommandcodePermissions({
|
|
43704
|
+
outputRoot,
|
|
43705
|
+
relativeDirPath: paths.relativeDirPath,
|
|
43706
|
+
relativeFilePath: paths.relativeFilePath,
|
|
43707
|
+
fileContent,
|
|
43708
|
+
validate,
|
|
43709
|
+
global
|
|
43710
|
+
});
|
|
43711
|
+
}
|
|
43712
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
43713
|
+
const paths = CommandcodePermissions.getSettablePaths({ global });
|
|
43714
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
43715
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
43716
|
+
const existing = parseCommandcodeSettings({
|
|
43717
|
+
fileContent: existingContent,
|
|
43718
|
+
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
43719
|
+
});
|
|
43720
|
+
const existingPermissions = isRecord$1(existing[COMMANDCODE_PERMISSIONS_KEY]) ? existing[COMMANDCODE_PERMISSIONS_KEY] : {};
|
|
43721
|
+
const lists = buildCommandcodeRuleLists({
|
|
43722
|
+
config: rulesyncPermissions.getJson(),
|
|
43723
|
+
existingPermissions,
|
|
43724
|
+
logger
|
|
43725
|
+
});
|
|
43726
|
+
const permissions = { ...existingPermissions };
|
|
43727
|
+
for (const key of [
|
|
43728
|
+
"allow",
|
|
43729
|
+
"ask",
|
|
43730
|
+
"deny"
|
|
43731
|
+
]) if (lists[key].length > 0) permissions[key] = lists[key];
|
|
43732
|
+
else delete permissions[key];
|
|
43733
|
+
return new CommandcodePermissions({
|
|
43734
|
+
outputRoot,
|
|
43735
|
+
relativeDirPath: paths.relativeDirPath,
|
|
43736
|
+
relativeFilePath: paths.relativeFilePath,
|
|
43737
|
+
fileContent: applySharedConfigPatch({
|
|
43738
|
+
fileKey: sharedConfigFileKey(paths),
|
|
43739
|
+
feature: "permissions",
|
|
43740
|
+
existingContent,
|
|
43741
|
+
patch: { [COMMANDCODE_PERMISSIONS_KEY]: permissions },
|
|
43742
|
+
filePath
|
|
43743
|
+
}),
|
|
43744
|
+
validate: true,
|
|
43745
|
+
global
|
|
43746
|
+
});
|
|
43747
|
+
}
|
|
43748
|
+
toRulesyncPermissions() {
|
|
43749
|
+
const settings = parseCommandcodeSettings({
|
|
43750
|
+
fileContent: this.getFileContent() || "{}",
|
|
43751
|
+
relativePath: (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())
|
|
43752
|
+
});
|
|
43753
|
+
const permissions = isRecord$1(settings[COMMANDCODE_PERMISSIONS_KEY]) ? settings[COMMANDCODE_PERMISSIONS_KEY] : {};
|
|
43754
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission: parseCommandcodeRuleLists(permissions) }, null, 2) });
|
|
43755
|
+
}
|
|
43756
|
+
validate() {
|
|
43757
|
+
return {
|
|
43758
|
+
success: true,
|
|
43759
|
+
error: null
|
|
43760
|
+
};
|
|
43761
|
+
}
|
|
43762
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
43763
|
+
return new CommandcodePermissions({
|
|
43764
|
+
outputRoot,
|
|
43765
|
+
relativeDirPath,
|
|
43766
|
+
relativeFilePath,
|
|
43767
|
+
fileContent: "{}",
|
|
43768
|
+
validate: false,
|
|
43769
|
+
global
|
|
43770
|
+
});
|
|
43771
|
+
}
|
|
43772
|
+
};
|
|
43773
|
+
/**
|
|
43774
|
+
* Fail closed on a syntax error or non-object root, matching the write path's
|
|
43775
|
+
* shared-config declaration, so a broken file is surfaced rather than
|
|
43776
|
+
* partially imported or overwritten.
|
|
43777
|
+
*/
|
|
43778
|
+
function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
43779
|
+
try {
|
|
43780
|
+
return parseSharedConfig({
|
|
43781
|
+
format: "json",
|
|
43782
|
+
fileContent,
|
|
43783
|
+
filePath: relativePath,
|
|
43784
|
+
invalidRootPolicy: "error"
|
|
43785
|
+
});
|
|
43786
|
+
} catch (error) {
|
|
43787
|
+
throw new Error(`Failed to parse Command Code settings in ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
43788
|
+
}
|
|
43789
|
+
}
|
|
43790
|
+
//#endregion
|
|
42611
43791
|
//#region src/features/permissions/continue-permissions.ts
|
|
42612
43792
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
42613
43793
|
const CATCH_ALL_PATTERN$5 = "*";
|
|
@@ -53369,6 +54549,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
53369
54549
|
supportsImport: true
|
|
53370
54550
|
}
|
|
53371
54551
|
}],
|
|
54552
|
+
["commandcode", {
|
|
54553
|
+
class: CommandcodePermissions,
|
|
54554
|
+
meta: {
|
|
54555
|
+
supportsProject: true,
|
|
54556
|
+
supportsGlobal: true,
|
|
54557
|
+
supportsImport: true
|
|
54558
|
+
}
|
|
54559
|
+
}],
|
|
53372
54560
|
["continue", {
|
|
53373
54561
|
class: ContinuePermissions,
|
|
53374
54562
|
meta: {
|
|
@@ -56135,6 +57323,154 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
|
|
|
56135
57323
|
}
|
|
56136
57324
|
};
|
|
56137
57325
|
//#endregion
|
|
57326
|
+
//#region src/features/skills/commandcode-skill.ts
|
|
57327
|
+
const CommandcodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
57328
|
+
name: zod_mini.z.string(),
|
|
57329
|
+
description: zod_mini.z.string()
|
|
57330
|
+
});
|
|
57331
|
+
/**
|
|
57332
|
+
* Represents a Command Code skill directory.
|
|
57333
|
+
*
|
|
57334
|
+
* Command Code discovers Anthropic-style `<name>/SKILL.md` directories under
|
|
57335
|
+
* `<project>/.commandcode/skills/` (project scope) and
|
|
57336
|
+
* `~/.commandcode/skills/` (user scope). The frontmatter requires `name`
|
|
57337
|
+
* (matching the directory name) and `description`; supporting files next to
|
|
57338
|
+
* `SKILL.md` are carried along.
|
|
57339
|
+
*
|
|
57340
|
+
* @see https://commandcode.ai/docs/skills
|
|
57341
|
+
*/
|
|
57342
|
+
var CommandcodeSkill = class CommandcodeSkill extends ToolSkill {
|
|
57343
|
+
constructor({ outputRoot = process.cwd(), relativeDirPath = COMMANDCODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
57344
|
+
super({
|
|
57345
|
+
outputRoot,
|
|
57346
|
+
relativeDirPath,
|
|
57347
|
+
dirName,
|
|
57348
|
+
mainFile: {
|
|
57349
|
+
name: SKILL_FILE_NAME,
|
|
57350
|
+
body,
|
|
57351
|
+
frontmatter: { ...frontmatter }
|
|
57352
|
+
},
|
|
57353
|
+
otherFiles,
|
|
57354
|
+
global
|
|
57355
|
+
});
|
|
57356
|
+
if (validate) {
|
|
57357
|
+
const result = this.validate();
|
|
57358
|
+
if (!result.success) throw result.error;
|
|
57359
|
+
}
|
|
57360
|
+
}
|
|
57361
|
+
static getSettablePaths(_options = {}) {
|
|
57362
|
+
return { relativeDirPath: COMMANDCODE_SKILLS_DIR_PATH };
|
|
57363
|
+
}
|
|
57364
|
+
getFrontmatter() {
|
|
57365
|
+
return CommandcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
57366
|
+
}
|
|
57367
|
+
getBody() {
|
|
57368
|
+
return this.mainFile?.body ?? "";
|
|
57369
|
+
}
|
|
57370
|
+
validate() {
|
|
57371
|
+
if (!this.mainFile) return {
|
|
57372
|
+
success: false,
|
|
57373
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
|
|
57374
|
+
};
|
|
57375
|
+
const result = CommandcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
|
|
57376
|
+
if (!result.success) return {
|
|
57377
|
+
success: false,
|
|
57378
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
|
|
57379
|
+
};
|
|
57380
|
+
if (result.data.name !== this.getDirName()) return {
|
|
57381
|
+
success: false,
|
|
57382
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
|
|
57383
|
+
};
|
|
57384
|
+
return {
|
|
57385
|
+
success: true,
|
|
57386
|
+
error: null
|
|
57387
|
+
};
|
|
57388
|
+
}
|
|
57389
|
+
toRulesyncSkill() {
|
|
57390
|
+
const { name, description, ...commandcodeSection } = this.getFrontmatter();
|
|
57391
|
+
const rulesyncFrontmatter = {
|
|
57392
|
+
name,
|
|
57393
|
+
description,
|
|
57394
|
+
targets: ["*"],
|
|
57395
|
+
...Object.keys(commandcodeSection).length > 0 && { commandcode: commandcodeSection }
|
|
57396
|
+
};
|
|
57397
|
+
return new RulesyncSkill({
|
|
57398
|
+
outputRoot: this.outputRoot,
|
|
57399
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
57400
|
+
dirName: this.getDirName(),
|
|
57401
|
+
frontmatter: rulesyncFrontmatter,
|
|
57402
|
+
body: this.getBody(),
|
|
57403
|
+
otherFiles: this.getOtherFiles(),
|
|
57404
|
+
validate: true,
|
|
57405
|
+
global: this.global
|
|
57406
|
+
});
|
|
57407
|
+
}
|
|
57408
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
57409
|
+
const settablePaths = CommandcodeSkill.getSettablePaths({ global });
|
|
57410
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
57411
|
+
const { name: _sectionName, description: _sectionDescription, ...commandcodeSection } = rulesyncFrontmatter.commandcode ?? {};
|
|
57412
|
+
const commandcodeFrontmatter = {
|
|
57413
|
+
...commandcodeSection,
|
|
57414
|
+
name: rulesyncFrontmatter.name,
|
|
57415
|
+
description: rulesyncFrontmatter.description
|
|
57416
|
+
};
|
|
57417
|
+
return new CommandcodeSkill({
|
|
57418
|
+
outputRoot,
|
|
57419
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
57420
|
+
dirName: commandcodeFrontmatter.name,
|
|
57421
|
+
frontmatter: commandcodeFrontmatter,
|
|
57422
|
+
body: rulesyncSkill.getBody(),
|
|
57423
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
57424
|
+
validate,
|
|
57425
|
+
global
|
|
57426
|
+
});
|
|
57427
|
+
}
|
|
57428
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
57429
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
57430
|
+
return targets.includes("*") || targets.includes("commandcode");
|
|
57431
|
+
}
|
|
57432
|
+
static async fromDir(params) {
|
|
57433
|
+
const loaded = await this.loadSkillDirContent({
|
|
57434
|
+
...params,
|
|
57435
|
+
getSettablePaths: CommandcodeSkill.getSettablePaths
|
|
57436
|
+
});
|
|
57437
|
+
const result = CommandcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
|
|
57438
|
+
if (!result.success) {
|
|
57439
|
+
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
57440
|
+
throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
57441
|
+
}
|
|
57442
|
+
if (result.data.name !== loaded.dirName) {
|
|
57443
|
+
const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
|
|
57444
|
+
throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
|
|
57445
|
+
}
|
|
57446
|
+
return new CommandcodeSkill({
|
|
57447
|
+
outputRoot: loaded.outputRoot,
|
|
57448
|
+
relativeDirPath: loaded.relativeDirPath,
|
|
57449
|
+
dirName: loaded.dirName,
|
|
57450
|
+
frontmatter: result.data,
|
|
57451
|
+
body: loaded.body,
|
|
57452
|
+
otherFiles: loaded.otherFiles,
|
|
57453
|
+
validate: true,
|
|
57454
|
+
global: loaded.global
|
|
57455
|
+
});
|
|
57456
|
+
}
|
|
57457
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
|
|
57458
|
+
return new CommandcodeSkill({
|
|
57459
|
+
outputRoot,
|
|
57460
|
+
relativeDirPath,
|
|
57461
|
+
dirName,
|
|
57462
|
+
frontmatter: {
|
|
57463
|
+
name: "",
|
|
57464
|
+
description: ""
|
|
57465
|
+
},
|
|
57466
|
+
body: "",
|
|
57467
|
+
otherFiles: [],
|
|
57468
|
+
validate: false,
|
|
57469
|
+
global
|
|
57470
|
+
});
|
|
57471
|
+
}
|
|
57472
|
+
};
|
|
57473
|
+
//#endregion
|
|
56138
57474
|
//#region src/features/skills/continue-skill.ts
|
|
56139
57475
|
const ContinueSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
56140
57476
|
name: zod_mini.z.string(),
|
|
@@ -61324,6 +62660,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
61324
62660
|
supportsGlobal: true
|
|
61325
62661
|
}
|
|
61326
62662
|
}],
|
|
62663
|
+
["commandcode", {
|
|
62664
|
+
class: CommandcodeSkill,
|
|
62665
|
+
meta: {
|
|
62666
|
+
supportsProject: true,
|
|
62667
|
+
supportsSimulated: false,
|
|
62668
|
+
supportsGlobal: true
|
|
62669
|
+
}
|
|
62670
|
+
}],
|
|
61327
62671
|
["continue", {
|
|
61328
62672
|
class: ContinueSkill,
|
|
61329
62673
|
meta: {
|
|
@@ -63427,6 +64771,143 @@ var CodexCliSubagent = class CodexCliSubagent extends ToolSubagent {
|
|
|
63427
64771
|
}
|
|
63428
64772
|
};
|
|
63429
64773
|
//#endregion
|
|
64774
|
+
//#region src/features/subagents/commandcode-subagent.ts
|
|
64775
|
+
const CommandcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
64776
|
+
name: zod_mini.z.string(),
|
|
64777
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
64778
|
+
tools: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
64779
|
+
disallowedTools: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
64780
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
64781
|
+
reasoningEffort: zod_mini.z.optional(zod_mini.z.string()),
|
|
64782
|
+
maxTurns: zod_mini.z.optional(zod_mini.z.number()),
|
|
64783
|
+
permissionMode: zod_mini.z.optional(zod_mini.z.string()),
|
|
64784
|
+
background: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
64785
|
+
showOutput: zod_mini.z.optional(zod_mini.z.boolean())
|
|
64786
|
+
});
|
|
64787
|
+
/**
|
|
64788
|
+
* Command Code custom agent: a Markdown file with YAML frontmatter under
|
|
64789
|
+
* `.commandcode/agents/` (project) or `~/.commandcode/agents/` (user); the
|
|
64790
|
+
* body is the agent's system prompt.
|
|
64791
|
+
*
|
|
64792
|
+
* @see https://commandcode.ai/docs/custom-agents
|
|
64793
|
+
*/
|
|
64794
|
+
var CommandcodeSubagent = class CommandcodeSubagent extends ToolSubagent {
|
|
64795
|
+
frontmatter;
|
|
64796
|
+
body;
|
|
64797
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
64798
|
+
if (rest.validate !== false) {
|
|
64799
|
+
const result = CommandcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
64800
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
64801
|
+
}
|
|
64802
|
+
super({
|
|
64803
|
+
...rest,
|
|
64804
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
|
|
64805
|
+
});
|
|
64806
|
+
this.frontmatter = frontmatter;
|
|
64807
|
+
this.body = body;
|
|
64808
|
+
}
|
|
64809
|
+
static getSettablePaths(_options = {}) {
|
|
64810
|
+
return { relativeDirPath: COMMANDCODE_AGENTS_DIR_PATH };
|
|
64811
|
+
}
|
|
64812
|
+
getFrontmatter() {
|
|
64813
|
+
return this.frontmatter;
|
|
64814
|
+
}
|
|
64815
|
+
getBody() {
|
|
64816
|
+
return this.body;
|
|
64817
|
+
}
|
|
64818
|
+
toRulesyncSubagent() {
|
|
64819
|
+
const { name, description, ...rest } = this.frontmatter;
|
|
64820
|
+
return new RulesyncSubagent({
|
|
64821
|
+
outputRoot: ".",
|
|
64822
|
+
frontmatter: {
|
|
64823
|
+
targets: ["*"],
|
|
64824
|
+
name,
|
|
64825
|
+
description,
|
|
64826
|
+
commandcode: { ...rest }
|
|
64827
|
+
},
|
|
64828
|
+
body: this.body,
|
|
64829
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
64830
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
64831
|
+
validate: true
|
|
64832
|
+
});
|
|
64833
|
+
}
|
|
64834
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
64835
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
64836
|
+
const commandcodeSection = rulesyncFrontmatter.commandcode ?? {};
|
|
64837
|
+
const commandcodeSubagentFrontmatter = {
|
|
64838
|
+
name: rulesyncFrontmatter.name,
|
|
64839
|
+
description: rulesyncFrontmatter.description,
|
|
64840
|
+
...commandcodeSection
|
|
64841
|
+
};
|
|
64842
|
+
const body = rulesyncSubagent.getBody();
|
|
64843
|
+
const fileContent = stringifyFrontmatter(body, commandcodeSubagentFrontmatter, { avoidBlockScalars: true });
|
|
64844
|
+
const paths = this.getSettablePaths({ global });
|
|
64845
|
+
return new CommandcodeSubagent({
|
|
64846
|
+
outputRoot,
|
|
64847
|
+
frontmatter: commandcodeSubagentFrontmatter,
|
|
64848
|
+
body,
|
|
64849
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64850
|
+
relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
|
|
64851
|
+
fileContent,
|
|
64852
|
+
validate,
|
|
64853
|
+
global
|
|
64854
|
+
});
|
|
64855
|
+
}
|
|
64856
|
+
validate() {
|
|
64857
|
+
if (!this.frontmatter) return {
|
|
64858
|
+
success: true,
|
|
64859
|
+
error: null
|
|
64860
|
+
};
|
|
64861
|
+
const result = CommandcodeSubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
64862
|
+
if (result.success) return {
|
|
64863
|
+
success: true,
|
|
64864
|
+
error: null
|
|
64865
|
+
};
|
|
64866
|
+
else return {
|
|
64867
|
+
success: false,
|
|
64868
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
64869
|
+
};
|
|
64870
|
+
}
|
|
64871
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
64872
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
64873
|
+
rulesyncSubagent,
|
|
64874
|
+
toolTarget: "commandcode"
|
|
64875
|
+
});
|
|
64876
|
+
}
|
|
64877
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
64878
|
+
const paths = this.getSettablePaths({ global });
|
|
64879
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
64880
|
+
const fileContent = await readFileContent(filePath);
|
|
64881
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
64882
|
+
const result = CommandcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
64883
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
64884
|
+
return new CommandcodeSubagent({
|
|
64885
|
+
outputRoot,
|
|
64886
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64887
|
+
relativeFilePath,
|
|
64888
|
+
frontmatter: result.data,
|
|
64889
|
+
body: content.trim(),
|
|
64890
|
+
fileContent,
|
|
64891
|
+
validate,
|
|
64892
|
+
global
|
|
64893
|
+
});
|
|
64894
|
+
}
|
|
64895
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
64896
|
+
return new CommandcodeSubagent({
|
|
64897
|
+
outputRoot,
|
|
64898
|
+
relativeDirPath,
|
|
64899
|
+
relativeFilePath,
|
|
64900
|
+
frontmatter: {
|
|
64901
|
+
name: "",
|
|
64902
|
+
description: ""
|
|
64903
|
+
},
|
|
64904
|
+
body: "",
|
|
64905
|
+
fileContent: "",
|
|
64906
|
+
validate: false
|
|
64907
|
+
});
|
|
64908
|
+
}
|
|
64909
|
+
};
|
|
64910
|
+
//#endregion
|
|
63430
64911
|
//#region src/features/subagents/copilot-subagent.ts
|
|
63431
64912
|
const CopilotSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
63432
64913
|
name: zod_mini.z.string(),
|
|
@@ -67085,6 +68566,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
67085
68566
|
filePattern: "*.toml"
|
|
67086
68567
|
}
|
|
67087
68568
|
}],
|
|
68569
|
+
["commandcode", {
|
|
68570
|
+
class: CommandcodeSubagent,
|
|
68571
|
+
meta: {
|
|
68572
|
+
supportsProject: true,
|
|
68573
|
+
supportsSimulated: false,
|
|
68574
|
+
supportsGlobal: true,
|
|
68575
|
+
filePattern: "*.md"
|
|
68576
|
+
}
|
|
68577
|
+
}],
|
|
67088
68578
|
["copilot", {
|
|
67089
68579
|
class: CopilotSubagent,
|
|
67090
68580
|
meta: {
|
|
@@ -69917,6 +71407,73 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
69917
71407
|
}
|
|
69918
71408
|
};
|
|
69919
71409
|
//#endregion
|
|
71410
|
+
//#region src/features/rules/commandcode-rule.ts
|
|
71411
|
+
var CommandcodeRule = class CommandcodeRule extends ToolRule {
|
|
71412
|
+
constructor({ fileContent, root, ...rest }) {
|
|
71413
|
+
super({
|
|
71414
|
+
...rest,
|
|
71415
|
+
fileContent,
|
|
71416
|
+
root: root ?? false
|
|
71417
|
+
});
|
|
71418
|
+
}
|
|
71419
|
+
static getSettablePaths({ global = false } = {}) {
|
|
71420
|
+
return { root: {
|
|
71421
|
+
relativeDirPath: global ? COMMANDCODE_DIR : ".",
|
|
71422
|
+
relativeFilePath: COMMANDCODE_RULE_FILE_NAME
|
|
71423
|
+
} };
|
|
71424
|
+
}
|
|
71425
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
71426
|
+
const { root } = this.getSettablePaths({ global });
|
|
71427
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
71428
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
71429
|
+
return new CommandcodeRule({
|
|
71430
|
+
outputRoot,
|
|
71431
|
+
relativeDirPath: root.relativeDirPath,
|
|
71432
|
+
relativeFilePath: root.relativeFilePath,
|
|
71433
|
+
fileContent,
|
|
71434
|
+
validate,
|
|
71435
|
+
root: true
|
|
71436
|
+
});
|
|
71437
|
+
}
|
|
71438
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
71439
|
+
const { root } = this.getSettablePaths({ global });
|
|
71440
|
+
const isRoot = rulesyncRule.getFrontmatter().root ?? false;
|
|
71441
|
+
return new CommandcodeRule({
|
|
71442
|
+
outputRoot,
|
|
71443
|
+
relativeDirPath: root.relativeDirPath,
|
|
71444
|
+
relativeFilePath: root.relativeFilePath,
|
|
71445
|
+
fileContent: rulesyncRule.getBody(),
|
|
71446
|
+
validate,
|
|
71447
|
+
root: isRoot
|
|
71448
|
+
});
|
|
71449
|
+
}
|
|
71450
|
+
toRulesyncRule() {
|
|
71451
|
+
return this.toRulesyncRuleDefault();
|
|
71452
|
+
}
|
|
71453
|
+
validate() {
|
|
71454
|
+
return {
|
|
71455
|
+
success: true,
|
|
71456
|
+
error: null
|
|
71457
|
+
};
|
|
71458
|
+
}
|
|
71459
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
71460
|
+
return new CommandcodeRule({
|
|
71461
|
+
outputRoot,
|
|
71462
|
+
relativeDirPath,
|
|
71463
|
+
relativeFilePath,
|
|
71464
|
+
fileContent: "",
|
|
71465
|
+
validate: false,
|
|
71466
|
+
root: relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === ".commandcode")
|
|
71467
|
+
});
|
|
71468
|
+
}
|
|
71469
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
71470
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
71471
|
+
rulesyncRule,
|
|
71472
|
+
toolTarget: "commandcode"
|
|
71473
|
+
});
|
|
71474
|
+
}
|
|
71475
|
+
};
|
|
71476
|
+
//#endregion
|
|
69920
71477
|
//#region src/features/rules/continue-rule.ts
|
|
69921
71478
|
/**
|
|
69922
71479
|
* Frontmatter schema for Continue rule files (`.continue/rules/*.md`).
|
|
@@ -74237,6 +75794,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
74237
75794
|
collisionPolicy: "fold"
|
|
74238
75795
|
}
|
|
74239
75796
|
}],
|
|
75797
|
+
["commandcode", {
|
|
75798
|
+
class: CommandcodeRule,
|
|
75799
|
+
meta: {
|
|
75800
|
+
extension: "md",
|
|
75801
|
+
supportsGlobal: true,
|
|
75802
|
+
ruleDiscoveryMode: "auto",
|
|
75803
|
+
collisionPolicy: "fold"
|
|
75804
|
+
}
|
|
75805
|
+
}],
|
|
74240
75806
|
["continue", {
|
|
74241
75807
|
class: ContinueRule,
|
|
74242
75808
|
meta: {
|