rulesync 43.2.0 → 43.4.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 -1
- package/dist/cli/index.cjs +274 -66
- package/dist/cli/index.js +274 -66
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DAT3ge4z.cjs → import-BU6Goa-J.cjs} +415 -108
- package/dist/{import-DPXjeK8q.js → import-D2mUPFM0.js} +416 -109
- package/dist/{import-DPXjeK8q.js.map → import-D2mUPFM0.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +14 -14
|
@@ -5635,11 +5635,28 @@ const POOL_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_POOL_EVENT_N
|
|
|
5635
5635
|
/**
|
|
5636
5636
|
* Hook events supported by Crush.
|
|
5637
5637
|
*
|
|
5638
|
-
* Crush
|
|
5639
|
-
*
|
|
5640
|
-
*
|
|
5638
|
+
* Crush v0.99.0 fires 13 events under its `hooks` config block (Claude
|
|
5639
|
+
* Code-compatible payload; `matcher` is a regex on the tool name and is only
|
|
5640
|
+
* accepted on tool events). Twelve of them have a canonical counterpart;
|
|
5641
|
+
* `PermissionResult` has none and is authored under the `crush.hooks`
|
|
5642
|
+
* override.
|
|
5643
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
5644
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/hooks/events.go
|
|
5641
5645
|
*/
|
|
5642
|
-
const CRUSH_HOOK_EVENTS = [
|
|
5646
|
+
const CRUSH_HOOK_EVENTS = [
|
|
5647
|
+
"preToolUse",
|
|
5648
|
+
"postToolUse",
|
|
5649
|
+
"postToolUseFailure",
|
|
5650
|
+
"permissionRequest",
|
|
5651
|
+
"sessionStart",
|
|
5652
|
+
"sessionEnd",
|
|
5653
|
+
"beforeSubmitPrompt",
|
|
5654
|
+
"stop",
|
|
5655
|
+
"stopFailure",
|
|
5656
|
+
"stopCancelled",
|
|
5657
|
+
"preCompact",
|
|
5658
|
+
"postCompact"
|
|
5659
|
+
];
|
|
5643
5660
|
/**
|
|
5644
5661
|
* Hook events supported by JetBrains Junie CLI.
|
|
5645
5662
|
*
|
|
@@ -5904,9 +5921,10 @@ const ZCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_ZCODE_EVENT
|
|
|
5904
5921
|
* Bob reads `hooks` from `.bob/settings.json` (project) and
|
|
5905
5922
|
* `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
|
|
5906
5923
|
* seven events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
|
|
5907
|
-
* PreCompact, PostCompact (
|
|
5908
|
-
* are `command` or `
|
|
5909
|
-
*
|
|
5924
|
+
* PreCompact, PostCompact and Stop (Bob IDE and Bob Shell list the same
|
|
5925
|
+
* seven). Handlers are `command` or `http` hooks. `matcher` is a regex matched
|
|
5926
|
+
* against the tool name on PreToolUse / PostToolUse, `source` on SessionStart
|
|
5927
|
+
* and `trigger` on PreCompact / PostCompact; the other events ignore it.
|
|
5910
5928
|
*
|
|
5911
5929
|
* @see https://bob.ibm.com/docs/shell/configuration/lifecycle-hooks
|
|
5912
5930
|
* @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
|
|
@@ -7088,10 +7106,38 @@ const CANONICAL_TO_CODEWHALE_EVENT_NAMES = {
|
|
|
7088
7106
|
const CODEWHALE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CODEWHALE_EVENT_NAMES);
|
|
7089
7107
|
/**
|
|
7090
7108
|
* Canonical -> Crush event names. Crush spells its events the Claude Code
|
|
7091
|
-
* way (`PreToolUse`)
|
|
7092
|
-
*
|
|
7109
|
+
* way (`PreToolUse`), but names the turn events `TurnStart` (Claude Code's
|
|
7110
|
+
* `UserPromptSubmit`) and `TurnEnd` (Claude Code's `Stop`), and the
|
|
7111
|
+
* user-cancelled turn `Interrupt`.
|
|
7112
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
7113
|
+
*/
|
|
7114
|
+
const CANONICAL_TO_CRUSH_EVENT_NAMES = {
|
|
7115
|
+
preToolUse: "PreToolUse",
|
|
7116
|
+
postToolUse: "PostToolUse",
|
|
7117
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7118
|
+
permissionRequest: "PermissionRequest",
|
|
7119
|
+
sessionStart: "SessionStart",
|
|
7120
|
+
sessionEnd: "SessionEnd",
|
|
7121
|
+
beforeSubmitPrompt: "TurnStart",
|
|
7122
|
+
stop: "TurnEnd",
|
|
7123
|
+
stopFailure: "StopFailure",
|
|
7124
|
+
stopCancelled: "Interrupt",
|
|
7125
|
+
preCompact: "PreCompact",
|
|
7126
|
+
postCompact: "PostCompact"
|
|
7127
|
+
};
|
|
7128
|
+
/**
|
|
7129
|
+
* Crush events that carry a tool call, the only ones whose hooks may set a
|
|
7130
|
+
* `matcher` (Crush's `EventHasTool`). A matcher on any other event makes
|
|
7131
|
+
* Crush reject the whole config at load time.
|
|
7132
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
7093
7133
|
*/
|
|
7094
|
-
const
|
|
7134
|
+
const CRUSH_TOOL_EVENTS = [
|
|
7135
|
+
"PreToolUse",
|
|
7136
|
+
"PostToolUse",
|
|
7137
|
+
"PostToolUseFailure",
|
|
7138
|
+
"PermissionRequest",
|
|
7139
|
+
"PermissionResult"
|
|
7140
|
+
];
|
|
7095
7141
|
const CRUSH_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CRUSH_EVENT_NAMES);
|
|
7096
7142
|
/**
|
|
7097
7143
|
* Map canonical camelCase event names to Qwen Code PascalCase.
|
|
@@ -7635,14 +7681,18 @@ const toolScopedMcpSchema = zod_mini.z.looseObject({ mcpServers: zod_mini.z.opti
|
|
|
7635
7681
|
* server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
|
|
7636
7682
|
* in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
|
|
7637
7683
|
* experimental `tool-select` flag is on, and is captured when a server connects
|
|
7638
|
-
* (MoonshotAI/kimi-code#4210).
|
|
7684
|
+
* (MoonshotAI/kimi-code#4210). `reconnectInitialDelayMs` / `reconnectMaxDelayMs`
|
|
7685
|
+
* (Kimi Code 2.4.0) set the first delay and the cap of the automatic reconnect
|
|
7686
|
+
* backoff for workspace-level servers (MoonshotAI/kimi-code#4283).
|
|
7639
7687
|
*
|
|
7640
7688
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
7641
7689
|
*/
|
|
7642
7690
|
const kimiCodeScopedMcpSchema = zod_mini.z.extend(toolScopedMcpSchema, {
|
|
7643
7691
|
startupTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7644
7692
|
toolTimeoutMs: zod_mini.z.optional(zod_mini.z.number()),
|
|
7645
|
-
defaultDeferred: zod_mini.z.optional(zod_mini.z.boolean())
|
|
7693
|
+
defaultDeferred: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7694
|
+
reconnectInitialDelayMs: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative())),
|
|
7695
|
+
reconnectMaxDelayMs: zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative()))
|
|
7646
7696
|
});
|
|
7647
7697
|
/**
|
|
7648
7698
|
* Qwen Code's tool-scoped block also carries the server-level allow/deny lists
|
|
@@ -11468,6 +11518,7 @@ const piSkillSectionSchema = zod_mini.z.looseObject({
|
|
|
11468
11518
|
});
|
|
11469
11519
|
const antigravitySkillSectionSchema = zod_mini.z.looseObject({
|
|
11470
11520
|
"disable-slash-command": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11521
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11471
11522
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
11472
11523
|
});
|
|
11473
11524
|
const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
@@ -11668,7 +11719,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
11668
11719
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
11669
11720
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
11670
11721
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
11671
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
11722
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
11723
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
11672
11724
|
})),
|
|
11673
11725
|
hermesagent: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
11674
11726
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -16226,8 +16278,9 @@ var ToolSkill = class extends AiDir {
|
|
|
16226
16278
|
* {@link AntigravitySharedSkill.getToolTarget}.
|
|
16227
16279
|
*
|
|
16228
16280
|
* Frontmatter beyond `name`/`description` (e.g. `disable-slash-command`,
|
|
16229
|
-
* `metadata`) comes from the rulesync section named
|
|
16230
|
-
* root-level `user-invocable` /
|
|
16281
|
+
* `disable-model-invocation`, `metadata`) comes from the rulesync section named
|
|
16282
|
+
* after that target, with the root-level `user-invocable` /
|
|
16283
|
+
* `disable-model-invocation` / `metadata` as defaults. On the shared project
|
|
16231
16284
|
* `.agents/skills/` tree both targets merge the `antigravity-ide` and
|
|
16232
16285
|
* `antigravity-cli` sections so their output never diverges.
|
|
16233
16286
|
*/
|
|
@@ -16300,6 +16353,8 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16300
16353
|
const { name: _name, description: _description, ...sectionFields } = section;
|
|
16301
16354
|
const sectionDisableSlashCommand = sectionFields["disable-slash-command"];
|
|
16302
16355
|
const disableSlashCommand = typeof sectionDisableSlashCommand === "boolean" ? sectionDisableSlashCommand : rulesyncFrontmatter["user-invocable"] === false ? true : void 0;
|
|
16356
|
+
const sectionDisableModelInvocation = sectionFields["disable-model-invocation"];
|
|
16357
|
+
const disableModelInvocation = typeof sectionDisableModelInvocation === "boolean" ? sectionDisableModelInvocation : rulesyncFrontmatter["disable-model-invocation"];
|
|
16303
16358
|
const metadata = resolveMetadata({
|
|
16304
16359
|
rootFrontmatter: rulesyncFrontmatter,
|
|
16305
16360
|
section: sectionFields
|
|
@@ -16309,6 +16364,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16309
16364
|
name: rulesyncFrontmatter.name,
|
|
16310
16365
|
description: rulesyncFrontmatter.description,
|
|
16311
16366
|
...disableSlashCommand !== void 0 && { "disable-slash-command": disableSlashCommand },
|
|
16367
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
|
|
16312
16368
|
...metadata !== void 0 && { metadata }
|
|
16313
16369
|
};
|
|
16314
16370
|
return new this({
|
|
@@ -19622,7 +19678,8 @@ const AgentsSkillsSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
19622
19678
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
19623
19679
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
19624
19680
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
19625
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
19681
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
19682
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
19626
19683
|
});
|
|
19627
19684
|
const COMPATIBILITY_MAX_LENGTH = 500;
|
|
19628
19685
|
const AGENT_SKILLS_SPEC = "the Agent Skills spec";
|
|
@@ -19763,6 +19820,38 @@ function toSpecConformantAgentSkillFields(section, { coerceMetadata = true } = {
|
|
|
19763
19820
|
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
|
|
19764
19821
|
};
|
|
19765
19822
|
}
|
|
19823
|
+
/**
|
|
19824
|
+
* The opt-in `disable-model-invocation` flag of the `agentsskills` block.
|
|
19825
|
+
*
|
|
19826
|
+
* The key is not part of the Agent Skills specification, so `skills-ref
|
|
19827
|
+
* validate` rejects a `SKILL.md` that carries it, but several clients reading
|
|
19828
|
+
* `.agents/skills/` (e.g. pi) honor it. To keep the default output
|
|
19829
|
+
* spec-conformant, only an explicit `agentsskills.disable-model-invocation`
|
|
19830
|
+
* value is written; the root-level rulesync flag is deliberately not inherited.
|
|
19831
|
+
*
|
|
19832
|
+
* Kept apart from {@link toSpecConformantAgentSkillFields} because that helper
|
|
19833
|
+
* is shared with writers of other tools' own locations, which have their own
|
|
19834
|
+
* sections for tool-specific keys. Only writers of the shared `.agents/skills/`
|
|
19835
|
+
* root (see {@link isSharedAgentSkillsRoot}) — the native target, GitLab Duo's
|
|
19836
|
+
* project scope and the simulated `agentsmd` — spread this, so they all emit
|
|
19837
|
+
* the same frontmatter for that file.
|
|
19838
|
+
*/
|
|
19839
|
+
function toAgentsSkillsInvocationField(section) {
|
|
19840
|
+
const value = section?.["disable-model-invocation"];
|
|
19841
|
+
return value === void 0 ? {} : { "disable-model-invocation": value };
|
|
19842
|
+
}
|
|
19843
|
+
/**
|
|
19844
|
+
* Whether a skill directory lives in the cross-client `.agents/skills/` root
|
|
19845
|
+
* (project, or `~/.agents/skills/` in global scope). The
|
|
19846
|
+
* `agentsskills.disable-model-invocation` opt-in exists for the clients that
|
|
19847
|
+
* read that root, so it is written and imported only there — not by the
|
|
19848
|
+
* subclasses that reuse this class for a tool's own location (Trae, Qoder, Xum,
|
|
19849
|
+
* …), where the key would otherwise leak into the `agentsskills` section on
|
|
19850
|
+
* import and from there into every `.agents/skills/` output.
|
|
19851
|
+
*/
|
|
19852
|
+
function isSharedAgentSkillsRoot(relativeDirPath) {
|
|
19853
|
+
return toPosixPath(relativeDirPath) === toPosixPath(AGENTSMD_SKILLS_DIR_PATH);
|
|
19854
|
+
}
|
|
19766
19855
|
/** The `SKILL.md` a diagnostic should point at, in the scope it is written to. */
|
|
19767
19856
|
function agentSkillFilePath({ outputRoot, relativeDirPath, dirName }) {
|
|
19768
19857
|
return (0, node_path.join)(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
|
|
@@ -19844,7 +19933,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
19844
19933
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
19845
19934
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
19846
19935
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
|
|
19847
|
-
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
|
|
19936
|
+
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools },
|
|
19937
|
+
...isSharedAgentSkillsRoot(this.getRelativeDirPath()) && frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
19848
19938
|
};
|
|
19849
19939
|
const rulesyncFrontmatter = {
|
|
19850
19940
|
name: frontmatter.name,
|
|
@@ -19870,7 +19960,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
19870
19960
|
const agentsSkillsFrontmatter = {
|
|
19871
19961
|
name: rulesyncFrontmatter.name,
|
|
19872
19962
|
description: rulesyncFrontmatter.description,
|
|
19873
|
-
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter))
|
|
19963
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter)),
|
|
19964
|
+
...isSharedAgentSkillsRoot(settablePaths.relativeDirPath) && toAgentsSkillsInvocationField(rulesyncFrontmatter.agentsskills)
|
|
19874
19965
|
};
|
|
19875
19966
|
AgentsSkillsSkill.reportSpecViolations({
|
|
19876
19967
|
outputRoot,
|
|
@@ -24944,7 +25035,8 @@ var AgentsmdSkill = class AgentsmdSkill extends SimulatedSkill {
|
|
|
24944
25035
|
const relativeDirPath = this.getSettablePaths().relativeDirPath;
|
|
24945
25036
|
const frontmatter = {
|
|
24946
25037
|
...defaults.frontmatter,
|
|
24947
|
-
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter()))
|
|
25038
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter())),
|
|
25039
|
+
...toAgentsSkillsInvocationField(params.rulesyncSkill.getFrontmatter().agentsskills)
|
|
24948
25040
|
};
|
|
24949
25041
|
AgentsSkillsSkill.reportSpecViolations({
|
|
24950
25042
|
outputRoot: params.outputRoot ?? process.cwd(),
|
|
@@ -31701,16 +31793,17 @@ const VibeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
31701
31793
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
31702
31794
|
});
|
|
31703
31795
|
/**
|
|
31704
|
-
* Skill names Vibe's built-in skills occupy. `SkillManager`
|
|
31705
|
-
* reserved: a project or user skill whose frontmatter `name`
|
|
31706
|
-
* skipped at load time with only a debug log ("Skipping skill
|
|
31707
|
-
* because builtin skill names are reserved"), so a generated
|
|
31708
|
-
* name is silently never offered.
|
|
31709
|
-
*
|
|
31710
|
-
*
|
|
31711
|
-
*
|
|
31712
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31713
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31796
|
+
* Skill names Vibe's built-in skills occupy. Before v3.0.4, `SkillManager`
|
|
31797
|
+
* treats them as reserved: a project or user skill whose frontmatter `name`
|
|
31798
|
+
* matches one is skipped at load time with only a debug log ("Skipping skill
|
|
31799
|
+
* '<name>' ... because builtin skill names are reserved"), so a generated
|
|
31800
|
+
* skill by that name is silently never offered. v3.0.4 removed
|
|
31801
|
+
* `_reserved_builtins` and that skip ("A user skill named vibe or
|
|
31802
|
+
* skill-creator is no longer silently skipped"), so the warning below is
|
|
31803
|
+
* scoped to older Vibe releases.
|
|
31804
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/skills/manager.py
|
|
31805
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/vibe/core/skills/manager.py
|
|
31806
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
31714
31807
|
*/
|
|
31715
31808
|
const VIBE_RESERVED_SKILL_NAMES = /* @__PURE__ */ new Set(["vibe", "skill-creator"]);
|
|
31716
31809
|
/**
|
|
@@ -31753,11 +31846,12 @@ function buildVibeFrontmatter(rulesyncFrontmatter) {
|
|
|
31753
31846
|
/**
|
|
31754
31847
|
* The skill is still generated under the reserved name — the name is the
|
|
31755
31848
|
* user's to change, and a rename here would make it diverge from the other
|
|
31756
|
-
* targets — but Vibe would drop it without a visible word, so
|
|
31849
|
+
* targets — but Vibe before v3.0.4 would drop it without a visible word, so
|
|
31850
|
+
* say so now.
|
|
31757
31851
|
*/
|
|
31758
31852
|
function warnAboutReservedSkillName({ name, logger }) {
|
|
31759
31853
|
if (!VIBE_RESERVED_SKILL_NAMES.has(name)) return;
|
|
31760
|
-
logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)}
|
|
31854
|
+
logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)} matches a Vibe built-in skill. Vibe before v3.0.4 skips a project or user skill by that name when it loads skills; upgrade Vibe to v3.0.4 or later, or rename the skill, for it to be available.`);
|
|
31761
31855
|
}
|
|
31762
31856
|
var VibeSkill = class VibeSkill extends ToolSkill {
|
|
31763
31857
|
constructor({ outputRoot = process.cwd(), relativeDirPath = VibeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -42725,20 +42819,35 @@ const OPENCODE_KNOWN_SERVER_KEYS = /* @__PURE__ */ new Set([
|
|
|
42725
42819
|
"disabledTools"
|
|
42726
42820
|
]);
|
|
42727
42821
|
function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
42822
|
+
const serverNames = Object.keys(opencodeMcp);
|
|
42728
42823
|
return {
|
|
42729
|
-
...orphanMcpToolFiltersToRulesync(opencodeMcp, tools)
|
|
42824
|
+
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42825
|
+
key,
|
|
42826
|
+
serverNames
|
|
42827
|
+
})))),
|
|
42730
42828
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42731
42829
|
};
|
|
42732
42830
|
}
|
|
42831
|
+
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42832
|
+
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42833
|
+
return findOpencodeMcpToolKeyOwner({
|
|
42834
|
+
key,
|
|
42835
|
+
serverNames,
|
|
42836
|
+
builtinKeys: /* @__PURE__ */ new Set(),
|
|
42837
|
+
acceptRawPrefix: true
|
|
42838
|
+
});
|
|
42839
|
+
}
|
|
42733
42840
|
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
42734
|
-
function splitOpencodeServerTools(serverName, tools) {
|
|
42841
|
+
function splitOpencodeServerTools(serverName, serverNames, tools) {
|
|
42735
42842
|
const enabledTools = [];
|
|
42736
42843
|
const disabledTools = [];
|
|
42737
|
-
const prefix = `${serverName}_`;
|
|
42738
42844
|
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
42739
|
-
|
|
42740
|
-
|
|
42741
|
-
|
|
42845
|
+
const owner = findOpencodeToolOwner({
|
|
42846
|
+
key: toolName,
|
|
42847
|
+
serverNames
|
|
42848
|
+
});
|
|
42849
|
+
if (owner?.serverName !== serverName) continue;
|
|
42850
|
+
(enabled ? enabledTools : disabledTools).push(owner.toolName);
|
|
42742
42851
|
}
|
|
42743
42852
|
return {
|
|
42744
42853
|
enabledTools,
|
|
@@ -42748,7 +42857,7 @@ function splitOpencodeServerTools(serverName, tools) {
|
|
|
42748
42857
|
function convertOpencodeServers(opencodeMcp, tools, remoteType) {
|
|
42749
42858
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
42750
42859
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
42751
|
-
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
42860
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, Object.keys(opencodeMcp), tools);
|
|
42752
42861
|
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
42753
42862
|
...extraFields,
|
|
42754
42863
|
disabled: serverConfig.enabled === false,
|
|
@@ -42884,8 +42993,8 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
42884
42993
|
return {
|
|
42885
42994
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
42886
42995
|
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
|
|
42887
|
-
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[
|
|
42888
|
-
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[
|
|
42996
|
+
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = true;
|
|
42997
|
+
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = false;
|
|
42889
42998
|
return converted === null ? null : [serverName, converted];
|
|
42890
42999
|
}).filter((entry) => entry !== null)),
|
|
42891
43000
|
tools
|
|
@@ -55913,12 +56022,23 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
|
|
|
55913
56022
|
* into separate groups, so a restricting field is never inherited by a hook
|
|
55914
56023
|
* that did not ask for it.
|
|
55915
56024
|
*/
|
|
55916
|
-
function groupDefinitionsByMatcher({ definitions, converterConfig }) {
|
|
56025
|
+
function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, warn }) {
|
|
55917
56026
|
const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
|
|
55918
56027
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
56028
|
+
const translatedMatchers = /* @__PURE__ */ new Map();
|
|
55919
56029
|
for (const def of definitions) {
|
|
55920
56030
|
const rawMatcher = def.matcher ?? "";
|
|
55921
|
-
const
|
|
56031
|
+
const normalizedMatcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
|
|
56032
|
+
const translation = converterConfig.matcherTranslation;
|
|
56033
|
+
let matcher = normalizedMatcher;
|
|
56034
|
+
if (translation && normalizedMatcher && !converterConfig.noMatcherEvents?.has(eventName)) {
|
|
56035
|
+
matcher = translatedMatchers.get(normalizedMatcher) ?? translation.toTool({
|
|
56036
|
+
matcher: normalizedMatcher,
|
|
56037
|
+
eventName,
|
|
56038
|
+
warn
|
|
56039
|
+
});
|
|
56040
|
+
translatedMatchers.set(normalizedMatcher, matcher);
|
|
56041
|
+
}
|
|
55922
56042
|
const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
|
|
55923
56043
|
const value = def[canonical];
|
|
55924
56044
|
return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
@@ -56428,7 +56548,7 @@ function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
|
56428
56548
|
converterConfig,
|
|
56429
56549
|
warn
|
|
56430
56550
|
}),
|
|
56431
|
-
type:
|
|
56551
|
+
type: hookType,
|
|
56432
56552
|
...command !== void 0 && command !== null && { command },
|
|
56433
56553
|
...def.timeout !== void 0 && def.timeout !== null && { timeout: emitTimeout({
|
|
56434
56554
|
timeout: def.timeout,
|
|
@@ -56478,7 +56598,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
56478
56598
|
}) ?? eventName;
|
|
56479
56599
|
const byMatcher = groupDefinitionsByMatcher({
|
|
56480
56600
|
definitions,
|
|
56481
|
-
|
|
56601
|
+
eventName,
|
|
56602
|
+
converterConfig,
|
|
56603
|
+
warn
|
|
56482
56604
|
});
|
|
56483
56605
|
const entries = [];
|
|
56484
56606
|
const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
|
|
@@ -56557,16 +56679,19 @@ function isImportedHookType(value) {
|
|
|
56557
56679
|
return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
|
|
56558
56680
|
}
|
|
56559
56681
|
/**
|
|
56560
|
-
* The canonical hook type a tool-side `type` value denotes: the
|
|
56561
|
-
*
|
|
56562
|
-
* one, otherwise the value itself when it is a canonical type, and
|
|
56563
|
-
* for anything else
|
|
56682
|
+
* The canonical hook type a tool-side `type` value denotes: the canonical type
|
|
56683
|
+
* an {@link ToolHooksConverterConfig.importedHookTypeAliases} entry maps it to
|
|
56684
|
+
* when it has one, otherwise the value itself when it is a canonical type, and
|
|
56685
|
+
* `command` for anything else.
|
|
56564
56686
|
*/
|
|
56565
56687
|
function resolveImportedHookType({ value, converterConfig }) {
|
|
56566
|
-
const
|
|
56567
|
-
if (
|
|
56568
|
-
|
|
56569
|
-
|
|
56688
|
+
const aliases = converterConfig.importedHookTypeAliases;
|
|
56689
|
+
if (aliases !== void 0 && typeof value === "string") {
|
|
56690
|
+
const aliased = lookupOwn({
|
|
56691
|
+
record: aliases,
|
|
56692
|
+
key: value
|
|
56693
|
+
});
|
|
56694
|
+
if (aliased !== void 0) return aliased;
|
|
56570
56695
|
}
|
|
56571
56696
|
return isImportedHookType(value) ? value : "command";
|
|
56572
56697
|
}
|
|
@@ -56794,7 +56919,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
|
|
|
56794
56919
|
canonical: "description",
|
|
56795
56920
|
warn
|
|
56796
56921
|
});
|
|
56797
|
-
const matcher = rawEntry.matcher;
|
|
56922
|
+
const matcher = typeof rawEntry.matcher === "string" && converterConfig.matcherTranslation ? converterConfig.matcherTranslation.toCanonical(rawEntry.matcher) : rawEntry.matcher;
|
|
56798
56923
|
return {
|
|
56799
56924
|
type: hookType,
|
|
56800
56925
|
...command !== void 0 && command !== null && { command },
|
|
@@ -58292,15 +58417,9 @@ const BOB_SPEC = {
|
|
|
58292
58417
|
canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
|
|
58293
58418
|
toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
|
|
58294
58419
|
projectDirVar: "",
|
|
58295
|
-
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
58296
|
-
"sessionStart",
|
|
58297
|
-
"beforeSubmitPrompt",
|
|
58298
|
-
"preCompact",
|
|
58299
|
-
"postCompact",
|
|
58300
|
-
"stop"
|
|
58301
|
-
]),
|
|
58420
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
58302
58421
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
58303
|
-
|
|
58422
|
+
importedHookTypeAliases: { https: "http" },
|
|
58304
58423
|
wildcardMatcherMeansAll: true
|
|
58305
58424
|
}
|
|
58306
58425
|
};
|
|
@@ -58312,7 +58431,7 @@ const BOB_SPEC = {
|
|
|
58312
58431
|
* `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
|
|
58313
58432
|
* `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
|
|
58314
58433
|
* "command": "...", "timeout"?: <seconds> }] }] }`, where a handler may
|
|
58315
|
-
* instead be `{ "type": "
|
|
58434
|
+
* instead be `{ "type": "http", "url": "https://...", "timeout"?: <seconds> }`. The
|
|
58316
58435
|
* file also holds settings rulesync does not own, so generation merges the
|
|
58317
58436
|
* `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting
|
|
58318
58437
|
* the file.
|
|
@@ -60254,6 +60373,32 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
60254
60373
|
}
|
|
60255
60374
|
};
|
|
60256
60375
|
// src/features/hooks/cortexcode-hooks.ts
|
|
60376
|
+
const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
|
|
60377
|
+
const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
60378
|
+
Bash: "bash",
|
|
60379
|
+
Read: "read",
|
|
60380
|
+
Write: "write",
|
|
60381
|
+
Edit: "edit",
|
|
60382
|
+
Grep: "grep",
|
|
60383
|
+
Glob: "glob"
|
|
60384
|
+
};
|
|
60385
|
+
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_TOOL_NAMES).map(([canonical, tool]) => [tool, canonical]));
|
|
60386
|
+
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60387
|
+
function isMatcherName(token) {
|
|
60388
|
+
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60389
|
+
}
|
|
60390
|
+
function replaceMatcherNames(matcher, names) {
|
|
60391
|
+
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60392
|
+
}
|
|
60393
|
+
function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
|
|
60394
|
+
const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60395
|
+
const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
|
|
60396
|
+
if (unmatched.length > 0) warn(`Cortex Code matches hook matchers case-sensitively against lowercase runtime tool names, and ${[...new Set(unmatched)].map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} is not a tool Cortex Code documents a runtime ID for, so it is written as-is and will likely never match. Replace it with the lowercase name Cortex Code uses for that tool, if it has one.`);
|
|
60397
|
+
return translated;
|
|
60398
|
+
}
|
|
60399
|
+
function cortexcodeToCanonicalMatcher(matcher) {
|
|
60400
|
+
return replaceMatcherNames(matcher, CORTEXCODE_TO_CANONICAL_TOOL_NAMES);
|
|
60401
|
+
}
|
|
60257
60402
|
const CORTEXCODE_SPEC = {
|
|
60258
60403
|
displayName: "Cortex Code",
|
|
60259
60404
|
overrideKey: "cortexcode",
|
|
@@ -60263,7 +60408,7 @@ const CORTEXCODE_SPEC = {
|
|
|
60263
60408
|
toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
|
|
60264
60409
|
projectDirVar: "$CORTEX_PROJECT_DIR",
|
|
60265
60410
|
prefixDotRelativeCommandsOnly: true,
|
|
60266
|
-
noMatcherEvents:
|
|
60411
|
+
noMatcherEvents: CORTEXCODE_NO_MATCHER_EVENTS,
|
|
60267
60412
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
|
|
60268
60413
|
booleanPassthroughFields: [{
|
|
60269
60414
|
canonical: "enabled",
|
|
@@ -60278,7 +60423,11 @@ const CORTEXCODE_SPEC = {
|
|
|
60278
60423
|
canonical: "source",
|
|
60279
60424
|
tool: "source",
|
|
60280
60425
|
commandOnly: true
|
|
60281
|
-
}]
|
|
60426
|
+
}],
|
|
60427
|
+
matcherTranslation: {
|
|
60428
|
+
toTool: canonicalToCortexcodeMatcher,
|
|
60429
|
+
toCanonical: cortexcodeToCanonicalMatcher
|
|
60430
|
+
}
|
|
60282
60431
|
}
|
|
60283
60432
|
};
|
|
60284
60433
|
/**
|
|
@@ -60459,35 +60608,80 @@ const NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entr
|
|
|
60459
60608
|
function normalizeCrushEventName(event) {
|
|
60460
60609
|
return event.replaceAll("_", "").toLowerCase();
|
|
60461
60610
|
}
|
|
60611
|
+
/** Crush tool events, by normalized spelling, on which a `matcher` is valid. */
|
|
60612
|
+
const NORMALIZED_CRUSH_TOOL_EVENTS = new Set(CRUSH_TOOL_EVENTS.map((event) => normalizeCrushEventName(event)));
|
|
60613
|
+
/**
|
|
60614
|
+
* Resolve a `crush.hooks` override key to the canonical event it names, or
|
|
60615
|
+
* null when it names no canonical event. The key may be spelled the canonical
|
|
60616
|
+
* way (`stop`) or in any spelling Crush accepts for the event (`TurnEnd`,
|
|
60617
|
+
* `turn_end`), so an override an earlier import filed under a Crush name still
|
|
60618
|
+
* lands on the same event as the shared hooks.
|
|
60619
|
+
*/
|
|
60620
|
+
function resolveOverrideCanonicalEvent(key) {
|
|
60621
|
+
if (lookupOwn({
|
|
60622
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60623
|
+
key
|
|
60624
|
+
}) !== void 0) return key;
|
|
60625
|
+
return lookupOwn({
|
|
60626
|
+
record: NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES,
|
|
60627
|
+
key: normalizeCrushEventName(key)
|
|
60628
|
+
}) ?? null;
|
|
60629
|
+
}
|
|
60462
60630
|
/**
|
|
60463
60631
|
* Build the `hooks` block of `crush.json` from a canonical hooks config.
|
|
60464
60632
|
* Crush keys a flat array of `{name, matcher, command, timeout}` entries by
|
|
60465
60633
|
* event name; `matcher` is a regex tested against the (lower-case) Crush tool
|
|
60466
60634
|
* name and `timeout` is in seconds. Only `type: "command"` canonical hooks are
|
|
60467
60635
|
* emitted, since a Crush hook is a shell command. A shared canonical event
|
|
60468
|
-
* Crush does not fire is skipped (the HooksProcessor reports it)
|
|
60469
|
-
*
|
|
60470
|
-
*
|
|
60636
|
+
* Crush does not fire is skipped (the HooksProcessor reports it).
|
|
60637
|
+
*
|
|
60638
|
+
* Every `crush.hooks` override key is resolved to its event first, whatever
|
|
60639
|
+
* spelling it uses: the override replaces the shared entries of that event
|
|
60640
|
+
* (as for every other tool's override), and override keys that name the same
|
|
60641
|
+
* event are concatenated in order, the way Crush itself merges them. A key
|
|
60642
|
+
* that names no canonical event — such as `PermissionResult`, or one an
|
|
60643
|
+
* import filed there — is written verbatim so it round-trips; keys of that
|
|
60644
|
+
* kind that Crush reads as the same event are concatenated under the first
|
|
60645
|
+
* spelling. A `matcher` is only written on a tool event: Crush rejects the
|
|
60646
|
+
* whole config when one appears anywhere else.
|
|
60471
60647
|
*/
|
|
60472
60648
|
function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
60473
|
-
const
|
|
60474
|
-
for (const [event, defs] of Object.entries(config.hooks))
|
|
60475
|
-
|
|
60476
|
-
...sharedHooks,
|
|
60477
|
-
...toolOverride
|
|
60478
|
-
};
|
|
60479
|
-
const hooks = {};
|
|
60480
|
-
for (const [event, defs] of Object.entries(effective)) {
|
|
60481
|
-
if (isPrototypePollutionKey(event)) continue;
|
|
60482
|
-
const crushEvent = lookupOwn({
|
|
60649
|
+
const shared = /* @__PURE__ */ new Map();
|
|
60650
|
+
for (const [event, defs] of Object.entries(config.hooks)) {
|
|
60651
|
+
const crushEvent = SUPPORTED_CRUSH_EVENTS.has(event) ? lookupOwn({
|
|
60483
60652
|
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60484
60653
|
key: event
|
|
60485
|
-
})
|
|
60654
|
+
}) : void 0;
|
|
60655
|
+
if (crushEvent !== void 0) shared.set(crushEvent, defs);
|
|
60656
|
+
}
|
|
60657
|
+
const override = /* @__PURE__ */ new Map();
|
|
60658
|
+
const verbatimSpellings = /* @__PURE__ */ new Map();
|
|
60659
|
+
for (const [key, defs] of Object.entries(toolOverride ?? {})) {
|
|
60660
|
+
if (isPrototypePollutionKey(key) || !Array.isArray(defs)) continue;
|
|
60661
|
+
const canonical = resolveOverrideCanonicalEvent(key);
|
|
60662
|
+
let crushEvent;
|
|
60663
|
+
if (canonical !== null) crushEvent = lookupOwn({
|
|
60664
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60665
|
+
key: canonical
|
|
60666
|
+
}) ?? key;
|
|
60667
|
+
else {
|
|
60668
|
+
const normalized = normalizeCrushEventName(key);
|
|
60669
|
+
crushEvent = verbatimSpellings.get(normalized) ?? key;
|
|
60670
|
+
verbatimSpellings.set(normalized, crushEvent);
|
|
60671
|
+
}
|
|
60672
|
+
override.set(crushEvent, [...override.get(crushEvent) ?? [], ...defs]);
|
|
60673
|
+
}
|
|
60674
|
+
const effective = new Map(shared);
|
|
60675
|
+
for (const [crushEvent, defs] of override) effective.set(crushEvent, defs);
|
|
60676
|
+
const hooks = {};
|
|
60677
|
+
for (const [crushEvent, defs] of effective) {
|
|
60678
|
+
const allowsMatcher = NORMALIZED_CRUSH_TOOL_EVENTS.has(normalizeCrushEventName(crushEvent));
|
|
60486
60679
|
const entries = [];
|
|
60487
60680
|
for (const def of defs) {
|
|
60488
60681
|
const entry = canonicalDefToCrushEntry({
|
|
60489
60682
|
def,
|
|
60490
|
-
event,
|
|
60683
|
+
event: crushEvent,
|
|
60684
|
+
allowsMatcher,
|
|
60491
60685
|
logger
|
|
60492
60686
|
});
|
|
60493
60687
|
if (entry !== null) entries.push(entry);
|
|
@@ -60497,7 +60691,7 @@ function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
|
60497
60691
|
return hooks;
|
|
60498
60692
|
}
|
|
60499
60693
|
/** Convert one canonical hook definition to a Crush entry, or null to skip. */
|
|
60500
|
-
function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
60694
|
+
function canonicalDefToCrushEntry({ def, event, allowsMatcher, logger }) {
|
|
60501
60695
|
if ((def.type ?? "command") !== "command") return null;
|
|
60502
60696
|
if (typeof def.command !== "string" || def.command === "") {
|
|
60503
60697
|
logger?.warn(`Crush hook ${quoteValueForWarning(def.name ?? "")} under "${event}" has no "command", which Crush would discard, so it was skipped.`);
|
|
@@ -60505,7 +60699,10 @@ function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
|
60505
60699
|
}
|
|
60506
60700
|
const entry = { command: def.command };
|
|
60507
60701
|
if (typeof def.name === "string" && def.name !== "") entry.name = def.name;
|
|
60508
|
-
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*")
|
|
60702
|
+
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*") {
|
|
60703
|
+
if (allowsMatcher) entry.matcher = def.matcher;
|
|
60704
|
+
else logger?.warn(`matcher ${quoteValueForWarning(def.matcher)} on "${event}" hook will be ignored — Crush only accepts a matcher on tool events.`);
|
|
60705
|
+
}
|
|
60509
60706
|
if (typeof def.timeout === "number") entry.timeout = Math.ceil(def.timeout);
|
|
60510
60707
|
return entry;
|
|
60511
60708
|
}
|
|
@@ -60552,12 +60749,13 @@ function crushHooksToCanonical(hooksBlock) {
|
|
|
60552
60749
|
* `<project>/crush.json` (or an existing `.crush.json`; Crush merges the pair,
|
|
60553
60750
|
* lists concatenated) at project scope and `~/.config/crush/crush.json` at
|
|
60554
60751
|
* user scope — as
|
|
60555
|
-
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`.
|
|
60556
|
-
*
|
|
60752
|
+
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. `matcher` is only
|
|
60753
|
+
* valid on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
60754
|
+
* `PermissionRequest`, `PermissionResult`). The `hooks` key is owned outright; every other top-level key
|
|
60557
60755
|
* of the file is preserved and the file is never deleted.
|
|
60558
60756
|
*
|
|
60559
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60560
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60757
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
60758
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
60561
60759
|
*/
|
|
60562
60760
|
var CrushHooks = class CrushHooks extends ToolHooks {
|
|
60563
60761
|
json;
|
|
@@ -63403,8 +63601,11 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
|
|
|
63403
63601
|
* OpenCode 2.x no longer runs V1 plugin modules: a plugin must default-export
|
|
63404
63602
|
* `{ id, setup(ctx) }`. Inside `setup` hooks are registered per domain
|
|
63405
63603
|
* (`await ctx.tool.hook("execute.before", handler)`), and lifecycle events
|
|
63406
|
-
* are read from `ctx.event.subscribe({ signal })`, an async iterable of
|
|
63407
|
-
* public event stream whose payload lives under `event.data`.
|
|
63604
|
+
* are read from `ctx.event.subscribe({ types, signal })`, an async iterable of
|
|
63605
|
+
* the public event stream whose payload lives under `event.data`. `types`
|
|
63606
|
+
* narrows the stream to the event types the plugin handles (a newer 2.x
|
|
63607
|
+
* option; the per-type checks inside the loop stay, so an older 2.x that
|
|
63608
|
+
* ignores it still runs each command only for its own event).
|
|
63408
63609
|
*
|
|
63409
63610
|
* Commands run through a private Bun Shell instance (`new Bun.$.Shell()`, the
|
|
63410
63611
|
* plugin runs inside the Bun-built OpenCode binary) whose working directory is
|
|
@@ -63507,13 +63708,18 @@ function buildHookRegistrationLines(hookPoint, handlers) {
|
|
|
63507
63708
|
}
|
|
63508
63709
|
/**
|
|
63509
63710
|
* Emit the event-stream loop. Each command is isolated in its own try/catch so
|
|
63510
|
-
* a failing hook neither skips its siblings nor ends the subscription.
|
|
63711
|
+
* a failing hook neither skips its siblings nor ends the subscription. The
|
|
63712
|
+
* subscription passes `types` so the server delivers only the handled events.
|
|
63511
63713
|
*/
|
|
63512
63714
|
function buildEventLoopLines(eventHandlers) {
|
|
63513
63715
|
const lines = [];
|
|
63716
|
+
const types = [...eventHandlers.keys()].map((eventType) => JSON.stringify(eventType)).join(", ");
|
|
63514
63717
|
lines.push("const __rulesyncEvents = new AbortController();");
|
|
63515
63718
|
lines.push("void (async () => {");
|
|
63516
|
-
lines.push(" for await (const event of ctx.event.subscribe({
|
|
63719
|
+
lines.push(" for await (const event of ctx.event.subscribe({");
|
|
63720
|
+
lines.push(` types: [${types}],`);
|
|
63721
|
+
lines.push(" signal: __rulesyncEvents.signal,");
|
|
63722
|
+
lines.push(" })) {");
|
|
63517
63723
|
let isFirst = true;
|
|
63518
63724
|
for (const [eventType, handlers] of eventHandlers) {
|
|
63519
63725
|
lines.push(` ${isFirst ? "if" : "} else if"} (event.type === "${eventType}") {`);
|
|
@@ -65647,7 +65853,12 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65647
65853
|
* resolves inside the bundle and is written as is. The file is loaded only
|
|
65648
65854
|
* when `plugin.json` declares the `ai.mistral.vibe` extension.
|
|
65649
65855
|
*
|
|
65856
|
+
* Plugin hooks run on Vibe v2.x and on v3.0.4+. v3.0.0 through v3.0.3 do not
|
|
65857
|
+
* run them (v3.0.4 Fixed: "Hooks shipped in a native Vibe plugin's
|
|
65858
|
+
* `ai.mistral.vibe/hooks.toml` now run"). The output is the same either way.
|
|
65859
|
+
*
|
|
65650
65860
|
* @see https://github.com/mistralai/mistral-vibe/blob/v2.25.8/vibe/core/plugins/_native.py
|
|
65861
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
65651
65862
|
*/
|
|
65652
65863
|
var VibePluginHooks = class extends VibeHooks {
|
|
65653
65864
|
static getSettablePaths() {
|
|
@@ -65731,6 +65942,14 @@ const ZCODE_CONVERTER_CONFIG = {
|
|
|
65731
65942
|
tool: "shell",
|
|
65732
65943
|
commandOnly: true
|
|
65733
65944
|
}],
|
|
65945
|
+
keywordPassthroughFields: [{
|
|
65946
|
+
canonical: "failClosed",
|
|
65947
|
+
tool: "onError",
|
|
65948
|
+
trueValue: "deny",
|
|
65949
|
+
falseValue: "allow",
|
|
65950
|
+
events: /* @__PURE__ */ new Set(["preToolUse"]),
|
|
65951
|
+
ignoredWhenFlags: ["async"]
|
|
65952
|
+
}],
|
|
65734
65953
|
wildcardMatcherMeansAll: true
|
|
65735
65954
|
};
|
|
65736
65955
|
/**
|
|
@@ -73871,9 +74090,21 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
73871
74090
|
...rest
|
|
73872
74091
|
};
|
|
73873
74092
|
}
|
|
74093
|
+
/**
|
|
74094
|
+
* GitLab Duo CLI reserves the MCP server name `duo` for its first-party server
|
|
74095
|
+
* (gitlab-lsp v9.33.0+): a workspace, user or plugin entry named exactly `duo`
|
|
74096
|
+
* (case-sensitive) is dropped with only a log line, so it would never load.
|
|
74097
|
+
*
|
|
74098
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/0572967c915465c1ac94ca38ef57676eb44e16d6
|
|
74099
|
+
*/
|
|
74100
|
+
const GITLABDUO_RESERVED_MCP_SERVER_NAME = "duo";
|
|
73874
74101
|
function toGitlabduoServers(servers, logger) {
|
|
73875
74102
|
const result = {};
|
|
73876
74103
|
for (const [serverName, serverConfig] of Object.entries(servers)) {
|
|
74104
|
+
if (serverName === GITLABDUO_RESERVED_MCP_SERVER_NAME) {
|
|
74105
|
+
logger?.warn(`GitLab Duo CLI MCP: skipping "${serverName}" because GitLab Duo CLI 9.33.0+ reserves that name for its first-party server and never loads a config entry with it. Rename the server to use it with GitLab Duo CLI.`);
|
|
74106
|
+
continue;
|
|
74107
|
+
}
|
|
73877
74108
|
const converted = toGitlabduoServer({
|
|
73878
74109
|
serverName,
|
|
73879
74110
|
serverConfig,
|
|
@@ -75119,24 +75350,47 @@ async function readKimiCodeConfig({ outputRoot }) {
|
|
|
75119
75350
|
}
|
|
75120
75351
|
}
|
|
75121
75352
|
/**
|
|
75353
|
+
* Kimi's reconnect delays are millisecond counts, so only a non-negative safe
|
|
75354
|
+
* integer is passed through. Anything else (a negative, a fraction, a string)
|
|
75355
|
+
* is dropped with a warning rather than written into, or lifted out of, the
|
|
75356
|
+
* shared global `config.toml`.
|
|
75357
|
+
*/
|
|
75358
|
+
function toKimiCodeReconnectDelay({ value, key, logger }) {
|
|
75359
|
+
if (value === void 0) return;
|
|
75360
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
|
|
75361
|
+
warnWithFallback(logger, `Kimi Code MCP: dropping ${key} because it is not a non-negative integer number of milliseconds.`);
|
|
75362
|
+
}
|
|
75363
|
+
/**
|
|
75122
75364
|
* Read the `[mcp]` defaults back out of the shared user `config.toml` so a
|
|
75123
75365
|
* generate → import round trip keeps them. Absent or unparseable content yields
|
|
75124
75366
|
* no defaults rather than failing the import: this file belongs to three
|
|
75125
75367
|
* features plus the user.
|
|
75126
75368
|
*/
|
|
75127
|
-
async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
75369
|
+
async function readKimiCodeMcpDefaults({ outputRoot, logger }) {
|
|
75128
75370
|
const config = await readKimiCodeConfig({ outputRoot });
|
|
75129
75371
|
if (!config.parsed) return {};
|
|
75372
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75373
|
+
value: config.mcp.reconnect_initial_delay_ms,
|
|
75374
|
+
key: "[mcp] reconnect_initial_delay_ms",
|
|
75375
|
+
logger
|
|
75376
|
+
});
|
|
75377
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75378
|
+
value: config.mcp.reconnect_max_delay_ms,
|
|
75379
|
+
key: "[mcp] reconnect_max_delay_ms",
|
|
75380
|
+
logger
|
|
75381
|
+
});
|
|
75130
75382
|
return {
|
|
75131
75383
|
...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
|
|
75132
75384
|
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
|
|
75133
|
-
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
|
|
75385
|
+
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred },
|
|
75386
|
+
...reconnectInitialDelayMs !== void 0 && { reconnectInitialDelayMs },
|
|
75387
|
+
...reconnectMaxDelayMs !== void 0 && { reconnectMaxDelayMs }
|
|
75134
75388
|
};
|
|
75135
75389
|
}
|
|
75136
75390
|
/**
|
|
75137
75391
|
* Kimi's `[mcp]` section in the shared user config sets the default connect and
|
|
75138
|
-
* per-tool-call timeouts,
|
|
75139
|
-
* server, including ones rulesync did not write. Per-server values in
|
|
75392
|
+
* per-tool-call timeouts, whether tools load on demand, and the automatic
|
|
75393
|
+
* reconnect backoff for *every* MCP server, including ones rulesync did not write. Per-server values in
|
|
75140
75394
|
* `mcp.json` still take precedence; this only moves the default.
|
|
75141
75395
|
*
|
|
75142
75396
|
* Written as an auxiliary file so it goes through the normal write phase and
|
|
@@ -75174,7 +75428,9 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
75174
75428
|
...existingSection,
|
|
75175
75429
|
...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
|
|
75176
75430
|
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
|
|
75177
|
-
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
|
|
75431
|
+
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred },
|
|
75432
|
+
...defaults.reconnectInitialDelayMs !== void 0 && { reconnect_initial_delay_ms: defaults.reconnectInitialDelayMs },
|
|
75433
|
+
...defaults.reconnectMaxDelayMs !== void 0 && { reconnect_max_delay_ms: defaults.reconnectMaxDelayMs }
|
|
75178
75434
|
} },
|
|
75179
75435
|
filePath: relativeConfigPath
|
|
75180
75436
|
});
|
|
@@ -75223,7 +75479,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75223
75479
|
fileContent,
|
|
75224
75480
|
validate,
|
|
75225
75481
|
global,
|
|
75226
|
-
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75482
|
+
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75483
|
+
outputRoot,
|
|
75484
|
+
logger
|
|
75485
|
+
}) : {},
|
|
75227
75486
|
logger
|
|
75228
75487
|
});
|
|
75229
75488
|
}
|
|
@@ -75273,7 +75532,17 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75273
75532
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
75274
75533
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
75275
75534
|
const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
|
|
75276
|
-
|
|
75535
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75536
|
+
value: block.reconnectInitialDelayMs,
|
|
75537
|
+
key: "kimi-code.reconnectInitialDelayMs",
|
|
75538
|
+
logger
|
|
75539
|
+
});
|
|
75540
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75541
|
+
value: block.reconnectMaxDelayMs,
|
|
75542
|
+
key: "kimi-code.reconnectMaxDelayMs",
|
|
75543
|
+
logger
|
|
75544
|
+
});
|
|
75545
|
+
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0 && reconnectInitialDelayMs === void 0 && reconnectMaxDelayMs === void 0) return [];
|
|
75277
75546
|
const existing = await readKimiCodeConfig({ outputRoot });
|
|
75278
75547
|
if (!existing.parsed) {
|
|
75279
75548
|
const paths = kimiCodeConfigRelativePath();
|
|
@@ -75285,7 +75554,9 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75285
75554
|
defaults: {
|
|
75286
75555
|
startupTimeoutMs,
|
|
75287
75556
|
toolTimeoutMs,
|
|
75288
|
-
defaultDeferred
|
|
75557
|
+
defaultDeferred,
|
|
75558
|
+
reconnectInitialDelayMs,
|
|
75559
|
+
reconnectMaxDelayMs
|
|
75289
75560
|
},
|
|
75290
75561
|
existing
|
|
75291
75562
|
})];
|
|
@@ -85636,6 +85907,7 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85636
85907
|
"CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT",
|
|
85637
85908
|
"CLAUDE_CODE_DISABLE_POWERSHELL_CMD_RM_DENY",
|
|
85638
85909
|
"CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT",
|
|
85910
|
+
"CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS",
|
|
85639
85911
|
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
85640
85912
|
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
85641
85913
|
"CLAUDE_CODE_PROJECT_DIR_NAME",
|
|
@@ -104881,32 +105153,59 @@ function buildChecksStrategy(ctx) {
|
|
|
104881
105153
|
}
|
|
104882
105154
|
// src/features/shared/antigravity-plugin-manifest.ts
|
|
104883
105155
|
const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
105156
|
+
const ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
105157
|
+
/**
|
|
105158
|
+
* Lower-case a directory name and turn `_` into `-` (collapsing repeated and
|
|
105159
|
+
* trimming edge hyphens), so `My_Plugin` becomes `my-plugin`.
|
|
105160
|
+
*/
|
|
105161
|
+
function toStrictAntigravityPluginName(name) {
|
|
105162
|
+
return name.toLowerCase().replace(/[-_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
105163
|
+
}
|
|
104884
105164
|
/**
|
|
104885
105165
|
* Create the `plugin.json` manifest Antigravity requires before it treats a
|
|
104886
105166
|
* directory as a plugin, so a tree generated from scratch actually loads.
|
|
104887
105167
|
*
|
|
104888
|
-
*
|
|
104889
|
-
*
|
|
104890
|
-
*
|
|
104891
|
-
*
|
|
105168
|
+
* Only the required `name` is written, derived from the plugin directory's
|
|
105169
|
+
* name and normalized to the lowercase kebab-case form `agy plugin validate
|
|
105170
|
+
* --strict` and the Marketplace require; the other manifest fields (such as
|
|
105171
|
+
* `description`, `version` or `author`) have no rulesync source. An existing
|
|
105172
|
+
* manifest is never rewritten: it may carry hand-authored fields. Its `name` is
|
|
105173
|
+
* read only to report a collision with another output root's name. A
|
|
104892
105174
|
* manifest kept in one of the manifest folders counts as present too, so no
|
|
104893
105175
|
* root stub is written next to it.
|
|
104894
105176
|
*/
|
|
104895
105177
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
104896
105178
|
const paths = [];
|
|
105179
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
105180
|
+
const noteName = (name, manifestPath) => {
|
|
105181
|
+
const previousPath = seenNames.get(name);
|
|
105182
|
+
if (previousPath === void 0) {
|
|
105183
|
+
seenNames.set(name, manifestPath);
|
|
105184
|
+
return;
|
|
105185
|
+
}
|
|
105186
|
+
warnOnceWithFallback(logger, `The Antigravity plugin manifests ${quoteForLog(manifestPath)} and ${quoteForLog(previousPath)} both name the plugin ${quoteForLog(name)}, so the plugins collide in Antigravity. Rename one of the directories or set a distinct "name" by hand.`);
|
|
105187
|
+
};
|
|
104897
105188
|
for (const outputRoot of outputRoots) {
|
|
104898
105189
|
const filePath = (0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
104899
|
-
|
|
105190
|
+
const existingManifestPath = await findAntigravityPluginManifest(outputRoot);
|
|
105191
|
+
if (existingManifestPath !== void 0) {
|
|
105192
|
+
const existingName = await readManifestName(existingManifestPath);
|
|
105193
|
+
if (existingName !== void 0) noteName(existingName, existingManifestPath);
|
|
105194
|
+
continue;
|
|
105195
|
+
}
|
|
104900
105196
|
if (await refusesWriteOutsideRoot({
|
|
104901
105197
|
logger,
|
|
104902
105198
|
rootPath: outputRoot,
|
|
104903
105199
|
targetPath: filePath
|
|
104904
105200
|
})) continue;
|
|
104905
|
-
const
|
|
104906
|
-
|
|
104907
|
-
|
|
105201
|
+
const dirName = (0, node_path.basename)((0, node_path.resolve)(outputRoot));
|
|
105202
|
+
const name = toStrictAntigravityPluginName(dirName);
|
|
105203
|
+
if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(dirName) || !ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.test(name)) {
|
|
105204
|
+
warnOnceWithFallback(logger, `Cannot derive an Antigravity plugin name from the directory ${quoteForLog(dirName)}: it must contain only letters, digits, "-" and "_", and at least one letter or digit, so that it normalizes to ${ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.source}. Create ${quoteForLog(filePath)} by hand with a valid "name".`);
|
|
104908
105205
|
continue;
|
|
104909
105206
|
}
|
|
105207
|
+
if (name !== dirName) warnOnceWithFallback(logger, `The Antigravity plugin directory ${quoteForLog(dirName)} is not lowercase kebab-case, which \`agy plugin validate --strict\` and the Marketplace reject, so ${quoteForLog(filePath)} names the plugin ${quoteForLog(name)}.`);
|
|
105208
|
+
noteName(name, filePath);
|
|
104910
105209
|
const content = addTrailingNewline(JSON.stringify({ name }, null, 2));
|
|
104911
105210
|
if (dryRun) logger.info(`[DRY RUN] Would write: ${stripControlCharacters(filePath)}`);
|
|
104912
105211
|
else await writeFileContent(filePath, content);
|
|
@@ -104919,10 +105218,18 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
104919
105218
|
sourceLoadFailed: false
|
|
104920
105219
|
};
|
|
104921
105220
|
}
|
|
104922
|
-
async function
|
|
105221
|
+
async function findAntigravityPluginManifest(outputRoot) {
|
|
104923
105222
|
const candidates = [(0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => (0, node_path.join)(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
104924
|
-
for (const candidate of candidates) if (await fileExists(candidate)) return
|
|
104925
|
-
|
|
105223
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return candidate;
|
|
105224
|
+
}
|
|
105225
|
+
/** The manifest's string `name`, or `undefined` when it is unreadable or absent. */
|
|
105226
|
+
async function readManifestName(manifestPath) {
|
|
105227
|
+
try {
|
|
105228
|
+
const parsed = JSON.parse(await readFileContent(manifestPath));
|
|
105229
|
+
return isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : void 0;
|
|
105230
|
+
} catch {
|
|
105231
|
+
return;
|
|
105232
|
+
}
|
|
104926
105233
|
}
|
|
104927
105234
|
// src/features/shared/hermes-project-plugin-activation.ts
|
|
104928
105235
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|