rulesync 43.3.0 → 43.4.1
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 +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CedO1sFP.cjs → import-B3x-pkWH.cjs} +376 -103
- package/dist/{import-BQNLyW5s.js → import-BKyt4qe5.js} +377 -104
- package/dist/{import-BQNLyW5s.js.map → import-BKyt4qe5.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +14 -14
|
@@ -5613,11 +5613,28 @@ const POOL_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_POOL_EVENT_N
|
|
|
5613
5613
|
/**
|
|
5614
5614
|
* Hook events supported by Crush.
|
|
5615
5615
|
*
|
|
5616
|
-
* Crush
|
|
5617
|
-
*
|
|
5618
|
-
*
|
|
5616
|
+
* Crush v0.99.0 fires 13 events under its `hooks` config block (Claude
|
|
5617
|
+
* Code-compatible payload; `matcher` is a regex on the tool name and is only
|
|
5618
|
+
* accepted on tool events). Twelve of them have a canonical counterpart;
|
|
5619
|
+
* `PermissionResult` has none and is authored under the `crush.hooks`
|
|
5620
|
+
* override.
|
|
5621
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
5622
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/hooks/events.go
|
|
5619
5623
|
*/
|
|
5620
|
-
const CRUSH_HOOK_EVENTS = [
|
|
5624
|
+
const CRUSH_HOOK_EVENTS = [
|
|
5625
|
+
"preToolUse",
|
|
5626
|
+
"postToolUse",
|
|
5627
|
+
"postToolUseFailure",
|
|
5628
|
+
"permissionRequest",
|
|
5629
|
+
"sessionStart",
|
|
5630
|
+
"sessionEnd",
|
|
5631
|
+
"beforeSubmitPrompt",
|
|
5632
|
+
"stop",
|
|
5633
|
+
"stopFailure",
|
|
5634
|
+
"stopCancelled",
|
|
5635
|
+
"preCompact",
|
|
5636
|
+
"postCompact"
|
|
5637
|
+
];
|
|
5621
5638
|
/**
|
|
5622
5639
|
* Hook events supported by JetBrains Junie CLI.
|
|
5623
5640
|
*
|
|
@@ -5882,9 +5899,10 @@ const ZCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_ZCODE_EVENT
|
|
|
5882
5899
|
* Bob reads `hooks` from `.bob/settings.json` (project) and
|
|
5883
5900
|
* `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
|
|
5884
5901
|
* seven events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
|
|
5885
|
-
* PreCompact, PostCompact (
|
|
5886
|
-
* are `command` or `
|
|
5887
|
-
*
|
|
5902
|
+
* PreCompact, PostCompact and Stop (Bob IDE and Bob Shell list the same
|
|
5903
|
+
* seven). Handlers are `command` or `http` hooks. `matcher` is a regex matched
|
|
5904
|
+
* against the tool name on PreToolUse / PostToolUse, `source` on SessionStart
|
|
5905
|
+
* and `trigger` on PreCompact / PostCompact; the other events ignore it.
|
|
5888
5906
|
*
|
|
5889
5907
|
* @see https://bob.ibm.com/docs/shell/configuration/lifecycle-hooks
|
|
5890
5908
|
* @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
|
|
@@ -7066,10 +7084,38 @@ const CANONICAL_TO_CODEWHALE_EVENT_NAMES = {
|
|
|
7066
7084
|
const CODEWHALE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CODEWHALE_EVENT_NAMES);
|
|
7067
7085
|
/**
|
|
7068
7086
|
* Canonical -> Crush event names. Crush spells its events the Claude Code
|
|
7069
|
-
* way (`PreToolUse`)
|
|
7070
|
-
*
|
|
7087
|
+
* way (`PreToolUse`), but names the turn events `TurnStart` (Claude Code's
|
|
7088
|
+
* `UserPromptSubmit`) and `TurnEnd` (Claude Code's `Stop`), and the
|
|
7089
|
+
* user-cancelled turn `Interrupt`.
|
|
7090
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
7071
7091
|
*/
|
|
7072
|
-
const CANONICAL_TO_CRUSH_EVENT_NAMES = {
|
|
7092
|
+
const CANONICAL_TO_CRUSH_EVENT_NAMES = {
|
|
7093
|
+
preToolUse: "PreToolUse",
|
|
7094
|
+
postToolUse: "PostToolUse",
|
|
7095
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7096
|
+
permissionRequest: "PermissionRequest",
|
|
7097
|
+
sessionStart: "SessionStart",
|
|
7098
|
+
sessionEnd: "SessionEnd",
|
|
7099
|
+
beforeSubmitPrompt: "TurnStart",
|
|
7100
|
+
stop: "TurnEnd",
|
|
7101
|
+
stopFailure: "StopFailure",
|
|
7102
|
+
stopCancelled: "Interrupt",
|
|
7103
|
+
preCompact: "PreCompact",
|
|
7104
|
+
postCompact: "PostCompact"
|
|
7105
|
+
};
|
|
7106
|
+
/**
|
|
7107
|
+
* Crush events that carry a tool call, the only ones whose hooks may set a
|
|
7108
|
+
* `matcher` (Crush's `EventHasTool`). A matcher on any other event makes
|
|
7109
|
+
* Crush reject the whole config at load time.
|
|
7110
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
7111
|
+
*/
|
|
7112
|
+
const CRUSH_TOOL_EVENTS = [
|
|
7113
|
+
"PreToolUse",
|
|
7114
|
+
"PostToolUse",
|
|
7115
|
+
"PostToolUseFailure",
|
|
7116
|
+
"PermissionRequest",
|
|
7117
|
+
"PermissionResult"
|
|
7118
|
+
];
|
|
7073
7119
|
const CRUSH_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CRUSH_EVENT_NAMES);
|
|
7074
7120
|
/**
|
|
7075
7121
|
* Map canonical camelCase event names to Qwen Code PascalCase.
|
|
@@ -7613,14 +7659,18 @@ const toolScopedMcpSchema = z.looseObject({ mcpServers: z.optional(z.record(z.st
|
|
|
7613
7659
|
* server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
|
|
7614
7660
|
* in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
|
|
7615
7661
|
* experimental `tool-select` flag is on, and is captured when a server connects
|
|
7616
|
-
* (MoonshotAI/kimi-code#4210).
|
|
7662
|
+
* (MoonshotAI/kimi-code#4210). `reconnectInitialDelayMs` / `reconnectMaxDelayMs`
|
|
7663
|
+
* (Kimi Code 2.4.0) set the first delay and the cap of the automatic reconnect
|
|
7664
|
+
* backoff for workspace-level servers (MoonshotAI/kimi-code#4283).
|
|
7617
7665
|
*
|
|
7618
7666
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
|
|
7619
7667
|
*/
|
|
7620
7668
|
const kimiCodeScopedMcpSchema = z.extend(toolScopedMcpSchema, {
|
|
7621
7669
|
startupTimeoutMs: z.optional(z.number()),
|
|
7622
7670
|
toolTimeoutMs: z.optional(z.number()),
|
|
7623
|
-
defaultDeferred: z.optional(z.boolean())
|
|
7671
|
+
defaultDeferred: z.optional(z.boolean()),
|
|
7672
|
+
reconnectInitialDelayMs: z.optional(z.int().check(z.nonnegative())),
|
|
7673
|
+
reconnectMaxDelayMs: z.optional(z.int().check(z.nonnegative()))
|
|
7624
7674
|
});
|
|
7625
7675
|
/**
|
|
7626
7676
|
* Qwen Code's tool-scoped block also carries the server-level allow/deny lists
|
|
@@ -11446,6 +11496,7 @@ const piSkillSectionSchema = z.looseObject({
|
|
|
11446
11496
|
});
|
|
11447
11497
|
const antigravitySkillSectionSchema = z.looseObject({
|
|
11448
11498
|
"disable-slash-command": z.optional(z.boolean()),
|
|
11499
|
+
"disable-model-invocation": z.optional(z.boolean()),
|
|
11449
11500
|
metadata: z.optional(z.looseObject({}))
|
|
11450
11501
|
});
|
|
11451
11502
|
const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
@@ -16205,8 +16256,9 @@ var ToolSkill = class extends AiDir {
|
|
|
16205
16256
|
* {@link AntigravitySharedSkill.getToolTarget}.
|
|
16206
16257
|
*
|
|
16207
16258
|
* Frontmatter beyond `name`/`description` (e.g. `disable-slash-command`,
|
|
16208
|
-
* `metadata`) comes from the rulesync section named
|
|
16209
|
-
* root-level `user-invocable` /
|
|
16259
|
+
* `disable-model-invocation`, `metadata`) comes from the rulesync section named
|
|
16260
|
+
* after that target, with the root-level `user-invocable` /
|
|
16261
|
+
* `disable-model-invocation` / `metadata` as defaults. On the shared project
|
|
16210
16262
|
* `.agents/skills/` tree both targets merge the `antigravity-ide` and
|
|
16211
16263
|
* `antigravity-cli` sections so their output never diverges.
|
|
16212
16264
|
*/
|
|
@@ -16279,6 +16331,8 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16279
16331
|
const { name: _name, description: _description, ...sectionFields } = section;
|
|
16280
16332
|
const sectionDisableSlashCommand = sectionFields["disable-slash-command"];
|
|
16281
16333
|
const disableSlashCommand = typeof sectionDisableSlashCommand === "boolean" ? sectionDisableSlashCommand : rulesyncFrontmatter["user-invocable"] === false ? true : void 0;
|
|
16334
|
+
const sectionDisableModelInvocation = sectionFields["disable-model-invocation"];
|
|
16335
|
+
const disableModelInvocation = typeof sectionDisableModelInvocation === "boolean" ? sectionDisableModelInvocation : rulesyncFrontmatter["disable-model-invocation"];
|
|
16282
16336
|
const metadata = resolveMetadata({
|
|
16283
16337
|
rootFrontmatter: rulesyncFrontmatter,
|
|
16284
16338
|
section: sectionFields
|
|
@@ -16288,6 +16342,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
|
|
|
16288
16342
|
name: rulesyncFrontmatter.name,
|
|
16289
16343
|
description: rulesyncFrontmatter.description,
|
|
16290
16344
|
...disableSlashCommand !== void 0 && { "disable-slash-command": disableSlashCommand },
|
|
16345
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
|
|
16291
16346
|
...metadata !== void 0 && { metadata }
|
|
16292
16347
|
};
|
|
16293
16348
|
return new this({
|
|
@@ -31716,16 +31771,17 @@ const VibeSkillFrontmatterSchema = z.looseObject({
|
|
|
31716
31771
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
|
|
31717
31772
|
});
|
|
31718
31773
|
/**
|
|
31719
|
-
* Skill names Vibe's built-in skills occupy. `SkillManager`
|
|
31720
|
-
* reserved: a project or user skill whose frontmatter `name`
|
|
31721
|
-
* skipped at load time with only a debug log ("Skipping skill
|
|
31722
|
-
* because builtin skill names are reserved"), so a generated
|
|
31723
|
-
* name is silently never offered.
|
|
31724
|
-
*
|
|
31725
|
-
*
|
|
31726
|
-
*
|
|
31727
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31728
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
31774
|
+
* Skill names Vibe's built-in skills occupy. Before v3.0.4, `SkillManager`
|
|
31775
|
+
* treats them as reserved: a project or user skill whose frontmatter `name`
|
|
31776
|
+
* matches one is skipped at load time with only a debug log ("Skipping skill
|
|
31777
|
+
* '<name>' ... because builtin skill names are reserved"), so a generated
|
|
31778
|
+
* skill by that name is silently never offered. v3.0.4 removed
|
|
31779
|
+
* `_reserved_builtins` and that skip ("A user skill named vibe or
|
|
31780
|
+
* skill-creator is no longer silently skipped"), so the warning below is
|
|
31781
|
+
* scoped to older Vibe releases.
|
|
31782
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/skills/manager.py
|
|
31783
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/vibe/core/skills/manager.py
|
|
31784
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
31729
31785
|
*/
|
|
31730
31786
|
const VIBE_RESERVED_SKILL_NAMES = /* @__PURE__ */ new Set(["vibe", "skill-creator"]);
|
|
31731
31787
|
/**
|
|
@@ -31768,11 +31824,12 @@ function buildVibeFrontmatter(rulesyncFrontmatter) {
|
|
|
31768
31824
|
/**
|
|
31769
31825
|
* The skill is still generated under the reserved name — the name is the
|
|
31770
31826
|
* user's to change, and a rename here would make it diverge from the other
|
|
31771
|
-
* targets — but Vibe would drop it without a visible word, so
|
|
31827
|
+
* targets — but Vibe before v3.0.4 would drop it without a visible word, so
|
|
31828
|
+
* say so now.
|
|
31772
31829
|
*/
|
|
31773
31830
|
function warnAboutReservedSkillName({ name, logger }) {
|
|
31774
31831
|
if (!VIBE_RESERVED_SKILL_NAMES.has(name)) return;
|
|
31775
|
-
logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)}
|
|
31832
|
+
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.`);
|
|
31776
31833
|
}
|
|
31777
31834
|
var VibeSkill = class VibeSkill extends ToolSkill {
|
|
31778
31835
|
constructor({ outputRoot = process.cwd(), relativeDirPath = VibeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -42740,20 +42797,35 @@ const OPENCODE_KNOWN_SERVER_KEYS = /* @__PURE__ */ new Set([
|
|
|
42740
42797
|
"disabledTools"
|
|
42741
42798
|
]);
|
|
42742
42799
|
function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
42800
|
+
const serverNames = Object.keys(opencodeMcp);
|
|
42743
42801
|
return {
|
|
42744
|
-
...orphanMcpToolFiltersToRulesync(opencodeMcp, tools)
|
|
42802
|
+
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42803
|
+
key,
|
|
42804
|
+
serverNames
|
|
42805
|
+
})))),
|
|
42745
42806
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42746
42807
|
};
|
|
42747
42808
|
}
|
|
42809
|
+
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42810
|
+
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42811
|
+
return findOpencodeMcpToolKeyOwner({
|
|
42812
|
+
key,
|
|
42813
|
+
serverNames,
|
|
42814
|
+
builtinKeys: /* @__PURE__ */ new Set(),
|
|
42815
|
+
acceptRawPrefix: true
|
|
42816
|
+
});
|
|
42817
|
+
}
|
|
42748
42818
|
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
42749
|
-
function splitOpencodeServerTools(serverName, tools) {
|
|
42819
|
+
function splitOpencodeServerTools(serverName, serverNames, tools) {
|
|
42750
42820
|
const enabledTools = [];
|
|
42751
42821
|
const disabledTools = [];
|
|
42752
|
-
const prefix = `${serverName}_`;
|
|
42753
42822
|
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
42754
|
-
|
|
42755
|
-
|
|
42756
|
-
|
|
42823
|
+
const owner = findOpencodeToolOwner({
|
|
42824
|
+
key: toolName,
|
|
42825
|
+
serverNames
|
|
42826
|
+
});
|
|
42827
|
+
if (owner?.serverName !== serverName) continue;
|
|
42828
|
+
(enabled ? enabledTools : disabledTools).push(owner.toolName);
|
|
42757
42829
|
}
|
|
42758
42830
|
return {
|
|
42759
42831
|
enabledTools,
|
|
@@ -42763,7 +42835,7 @@ function splitOpencodeServerTools(serverName, tools) {
|
|
|
42763
42835
|
function convertOpencodeServers(opencodeMcp, tools, remoteType) {
|
|
42764
42836
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
42765
42837
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
42766
|
-
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
42838
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, Object.keys(opencodeMcp), tools);
|
|
42767
42839
|
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
42768
42840
|
...extraFields,
|
|
42769
42841
|
disabled: serverConfig.enabled === false,
|
|
@@ -42899,8 +42971,11 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
42899
42971
|
return {
|
|
42900
42972
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
42901
42973
|
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
|
|
42902
|
-
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools)
|
|
42903
|
-
|
|
42974
|
+
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) {
|
|
42975
|
+
const key = opencodeMcpToolPermissionKey(serverName, tool);
|
|
42976
|
+
tools[key] = tools[key] !== false;
|
|
42977
|
+
}
|
|
42978
|
+
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = false;
|
|
42904
42979
|
return converted === null ? null : [serverName, converted];
|
|
42905
42980
|
}).filter((entry) => entry !== null)),
|
|
42906
42981
|
tools
|
|
@@ -55928,12 +56003,23 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
|
|
|
55928
56003
|
* into separate groups, so a restricting field is never inherited by a hook
|
|
55929
56004
|
* that did not ask for it.
|
|
55930
56005
|
*/
|
|
55931
|
-
function groupDefinitionsByMatcher({ definitions, converterConfig }) {
|
|
56006
|
+
function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, warn }) {
|
|
55932
56007
|
const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
|
|
55933
56008
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
56009
|
+
const translatedMatchers = /* @__PURE__ */ new Map();
|
|
55934
56010
|
for (const def of definitions) {
|
|
55935
56011
|
const rawMatcher = def.matcher ?? "";
|
|
55936
|
-
const
|
|
56012
|
+
const normalizedMatcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
|
|
56013
|
+
const translation = converterConfig.matcherTranslation;
|
|
56014
|
+
let matcher = normalizedMatcher;
|
|
56015
|
+
if (translation && normalizedMatcher && !converterConfig.noMatcherEvents?.has(eventName)) {
|
|
56016
|
+
matcher = translatedMatchers.get(normalizedMatcher) ?? translation.toTool({
|
|
56017
|
+
matcher: normalizedMatcher,
|
|
56018
|
+
eventName,
|
|
56019
|
+
warn
|
|
56020
|
+
});
|
|
56021
|
+
translatedMatchers.set(normalizedMatcher, matcher);
|
|
56022
|
+
}
|
|
55937
56023
|
const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
|
|
55938
56024
|
const value = def[canonical];
|
|
55939
56025
|
return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
@@ -56443,7 +56529,7 @@ function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
|
56443
56529
|
converterConfig,
|
|
56444
56530
|
warn
|
|
56445
56531
|
}),
|
|
56446
|
-
type:
|
|
56532
|
+
type: hookType,
|
|
56447
56533
|
...command !== void 0 && command !== null && { command },
|
|
56448
56534
|
...def.timeout !== void 0 && def.timeout !== null && { timeout: emitTimeout({
|
|
56449
56535
|
timeout: def.timeout,
|
|
@@ -56493,7 +56579,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
56493
56579
|
}) ?? eventName;
|
|
56494
56580
|
const byMatcher = groupDefinitionsByMatcher({
|
|
56495
56581
|
definitions,
|
|
56496
|
-
|
|
56582
|
+
eventName,
|
|
56583
|
+
converterConfig,
|
|
56584
|
+
warn
|
|
56497
56585
|
});
|
|
56498
56586
|
const entries = [];
|
|
56499
56587
|
const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
|
|
@@ -56572,16 +56660,19 @@ function isImportedHookType(value) {
|
|
|
56572
56660
|
return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
|
|
56573
56661
|
}
|
|
56574
56662
|
/**
|
|
56575
|
-
* The canonical hook type a tool-side `type` value denotes: the
|
|
56576
|
-
*
|
|
56577
|
-
* one, otherwise the value itself when it is a canonical type, and
|
|
56578
|
-
* for anything else
|
|
56663
|
+
* The canonical hook type a tool-side `type` value denotes: the canonical type
|
|
56664
|
+
* an {@link ToolHooksConverterConfig.importedHookTypeAliases} entry maps it to
|
|
56665
|
+
* when it has one, otherwise the value itself when it is a canonical type, and
|
|
56666
|
+
* `command` for anything else.
|
|
56579
56667
|
*/
|
|
56580
56668
|
function resolveImportedHookType({ value, converterConfig }) {
|
|
56581
|
-
const
|
|
56582
|
-
if (
|
|
56583
|
-
|
|
56584
|
-
|
|
56669
|
+
const aliases = converterConfig.importedHookTypeAliases;
|
|
56670
|
+
if (aliases !== void 0 && typeof value === "string") {
|
|
56671
|
+
const aliased = lookupOwn({
|
|
56672
|
+
record: aliases,
|
|
56673
|
+
key: value
|
|
56674
|
+
});
|
|
56675
|
+
if (aliased !== void 0) return aliased;
|
|
56585
56676
|
}
|
|
56586
56677
|
return isImportedHookType(value) ? value : "command";
|
|
56587
56678
|
}
|
|
@@ -56809,7 +56900,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
|
|
|
56809
56900
|
canonical: "description",
|
|
56810
56901
|
warn
|
|
56811
56902
|
});
|
|
56812
|
-
const matcher = rawEntry.matcher;
|
|
56903
|
+
const matcher = typeof rawEntry.matcher === "string" && converterConfig.matcherTranslation ? converterConfig.matcherTranslation.toCanonical(rawEntry.matcher) : rawEntry.matcher;
|
|
56813
56904
|
return {
|
|
56814
56905
|
type: hookType,
|
|
56815
56906
|
...command !== void 0 && command !== null && { command },
|
|
@@ -58307,15 +58398,9 @@ const BOB_SPEC = {
|
|
|
58307
58398
|
canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
|
|
58308
58399
|
toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
|
|
58309
58400
|
projectDirVar: "",
|
|
58310
|
-
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
58311
|
-
"sessionStart",
|
|
58312
|
-
"beforeSubmitPrompt",
|
|
58313
|
-
"preCompact",
|
|
58314
|
-
"postCompact",
|
|
58315
|
-
"stop"
|
|
58316
|
-
]),
|
|
58401
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
58317
58402
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
58318
|
-
|
|
58403
|
+
importedHookTypeAliases: { https: "http" },
|
|
58319
58404
|
wildcardMatcherMeansAll: true
|
|
58320
58405
|
}
|
|
58321
58406
|
};
|
|
@@ -58327,7 +58412,7 @@ const BOB_SPEC = {
|
|
|
58327
58412
|
* `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
|
|
58328
58413
|
* `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
|
|
58329
58414
|
* "command": "...", "timeout"?: <seconds> }] }] }`, where a handler may
|
|
58330
|
-
* instead be `{ "type": "
|
|
58415
|
+
* instead be `{ "type": "http", "url": "https://...", "timeout"?: <seconds> }`. The
|
|
58331
58416
|
* file also holds settings rulesync does not own, so generation merges the
|
|
58332
58417
|
* `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting
|
|
58333
58418
|
* the file.
|
|
@@ -60269,6 +60354,32 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
60269
60354
|
}
|
|
60270
60355
|
};
|
|
60271
60356
|
// src/features/hooks/cortexcode-hooks.ts
|
|
60357
|
+
const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
|
|
60358
|
+
const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
60359
|
+
Bash: "bash",
|
|
60360
|
+
Read: "read",
|
|
60361
|
+
Write: "write",
|
|
60362
|
+
Edit: "edit",
|
|
60363
|
+
Grep: "grep",
|
|
60364
|
+
Glob: "glob"
|
|
60365
|
+
};
|
|
60366
|
+
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_TOOL_NAMES).map(([canonical, tool]) => [tool, canonical]));
|
|
60367
|
+
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60368
|
+
function isMatcherName(token) {
|
|
60369
|
+
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60370
|
+
}
|
|
60371
|
+
function replaceMatcherNames(matcher, names) {
|
|
60372
|
+
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60373
|
+
}
|
|
60374
|
+
function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
|
|
60375
|
+
const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60376
|
+
const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
|
|
60377
|
+
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.`);
|
|
60378
|
+
return translated;
|
|
60379
|
+
}
|
|
60380
|
+
function cortexcodeToCanonicalMatcher(matcher) {
|
|
60381
|
+
return replaceMatcherNames(matcher, CORTEXCODE_TO_CANONICAL_TOOL_NAMES);
|
|
60382
|
+
}
|
|
60272
60383
|
const CORTEXCODE_SPEC = {
|
|
60273
60384
|
displayName: "Cortex Code",
|
|
60274
60385
|
overrideKey: "cortexcode",
|
|
@@ -60278,7 +60389,7 @@ const CORTEXCODE_SPEC = {
|
|
|
60278
60389
|
toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
|
|
60279
60390
|
projectDirVar: "$CORTEX_PROJECT_DIR",
|
|
60280
60391
|
prefixDotRelativeCommandsOnly: true,
|
|
60281
|
-
noMatcherEvents:
|
|
60392
|
+
noMatcherEvents: CORTEXCODE_NO_MATCHER_EVENTS,
|
|
60282
60393
|
supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
|
|
60283
60394
|
booleanPassthroughFields: [{
|
|
60284
60395
|
canonical: "enabled",
|
|
@@ -60293,7 +60404,11 @@ const CORTEXCODE_SPEC = {
|
|
|
60293
60404
|
canonical: "source",
|
|
60294
60405
|
tool: "source",
|
|
60295
60406
|
commandOnly: true
|
|
60296
|
-
}]
|
|
60407
|
+
}],
|
|
60408
|
+
matcherTranslation: {
|
|
60409
|
+
toTool: canonicalToCortexcodeMatcher,
|
|
60410
|
+
toCanonical: cortexcodeToCanonicalMatcher
|
|
60411
|
+
}
|
|
60297
60412
|
}
|
|
60298
60413
|
};
|
|
60299
60414
|
/**
|
|
@@ -60474,35 +60589,80 @@ const NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entr
|
|
|
60474
60589
|
function normalizeCrushEventName(event) {
|
|
60475
60590
|
return event.replaceAll("_", "").toLowerCase();
|
|
60476
60591
|
}
|
|
60592
|
+
/** Crush tool events, by normalized spelling, on which a `matcher` is valid. */
|
|
60593
|
+
const NORMALIZED_CRUSH_TOOL_EVENTS = new Set(CRUSH_TOOL_EVENTS.map((event) => normalizeCrushEventName(event)));
|
|
60594
|
+
/**
|
|
60595
|
+
* Resolve a `crush.hooks` override key to the canonical event it names, or
|
|
60596
|
+
* null when it names no canonical event. The key may be spelled the canonical
|
|
60597
|
+
* way (`stop`) or in any spelling Crush accepts for the event (`TurnEnd`,
|
|
60598
|
+
* `turn_end`), so an override an earlier import filed under a Crush name still
|
|
60599
|
+
* lands on the same event as the shared hooks.
|
|
60600
|
+
*/
|
|
60601
|
+
function resolveOverrideCanonicalEvent(key) {
|
|
60602
|
+
if (lookupOwn({
|
|
60603
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60604
|
+
key
|
|
60605
|
+
}) !== void 0) return key;
|
|
60606
|
+
return lookupOwn({
|
|
60607
|
+
record: NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES,
|
|
60608
|
+
key: normalizeCrushEventName(key)
|
|
60609
|
+
}) ?? null;
|
|
60610
|
+
}
|
|
60477
60611
|
/**
|
|
60478
60612
|
* Build the `hooks` block of `crush.json` from a canonical hooks config.
|
|
60479
60613
|
* Crush keys a flat array of `{name, matcher, command, timeout}` entries by
|
|
60480
60614
|
* event name; `matcher` is a regex tested against the (lower-case) Crush tool
|
|
60481
60615
|
* name and `timeout` is in seconds. Only `type: "command"` canonical hooks are
|
|
60482
60616
|
* emitted, since a Crush hook is a shell command. A shared canonical event
|
|
60483
|
-
* Crush does not fire is skipped (the HooksProcessor reports it)
|
|
60484
|
-
*
|
|
60485
|
-
*
|
|
60617
|
+
* Crush does not fire is skipped (the HooksProcessor reports it).
|
|
60618
|
+
*
|
|
60619
|
+
* Every `crush.hooks` override key is resolved to its event first, whatever
|
|
60620
|
+
* spelling it uses: the override replaces the shared entries of that event
|
|
60621
|
+
* (as for every other tool's override), and override keys that name the same
|
|
60622
|
+
* event are concatenated in order, the way Crush itself merges them. A key
|
|
60623
|
+
* that names no canonical event — such as `PermissionResult`, or one an
|
|
60624
|
+
* import filed there — is written verbatim so it round-trips; keys of that
|
|
60625
|
+
* kind that Crush reads as the same event are concatenated under the first
|
|
60626
|
+
* spelling. A `matcher` is only written on a tool event: Crush rejects the
|
|
60627
|
+
* whole config when one appears anywhere else.
|
|
60486
60628
|
*/
|
|
60487
60629
|
function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
60488
|
-
const
|
|
60489
|
-
for (const [event, defs] of Object.entries(config.hooks))
|
|
60490
|
-
|
|
60491
|
-
...sharedHooks,
|
|
60492
|
-
...toolOverride
|
|
60493
|
-
};
|
|
60494
|
-
const hooks = {};
|
|
60495
|
-
for (const [event, defs] of Object.entries(effective)) {
|
|
60496
|
-
if (isPrototypePollutionKey(event)) continue;
|
|
60497
|
-
const crushEvent = lookupOwn({
|
|
60630
|
+
const shared = /* @__PURE__ */ new Map();
|
|
60631
|
+
for (const [event, defs] of Object.entries(config.hooks)) {
|
|
60632
|
+
const crushEvent = SUPPORTED_CRUSH_EVENTS.has(event) ? lookupOwn({
|
|
60498
60633
|
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60499
60634
|
key: event
|
|
60500
|
-
})
|
|
60635
|
+
}) : void 0;
|
|
60636
|
+
if (crushEvent !== void 0) shared.set(crushEvent, defs);
|
|
60637
|
+
}
|
|
60638
|
+
const override = /* @__PURE__ */ new Map();
|
|
60639
|
+
const verbatimSpellings = /* @__PURE__ */ new Map();
|
|
60640
|
+
for (const [key, defs] of Object.entries(toolOverride ?? {})) {
|
|
60641
|
+
if (isPrototypePollutionKey(key) || !Array.isArray(defs)) continue;
|
|
60642
|
+
const canonical = resolveOverrideCanonicalEvent(key);
|
|
60643
|
+
let crushEvent;
|
|
60644
|
+
if (canonical !== null) crushEvent = lookupOwn({
|
|
60645
|
+
record: CANONICAL_TO_CRUSH_EVENT_NAMES,
|
|
60646
|
+
key: canonical
|
|
60647
|
+
}) ?? key;
|
|
60648
|
+
else {
|
|
60649
|
+
const normalized = normalizeCrushEventName(key);
|
|
60650
|
+
crushEvent = verbatimSpellings.get(normalized) ?? key;
|
|
60651
|
+
verbatimSpellings.set(normalized, crushEvent);
|
|
60652
|
+
}
|
|
60653
|
+
override.set(crushEvent, [...override.get(crushEvent) ?? [], ...defs]);
|
|
60654
|
+
}
|
|
60655
|
+
const effective = new Map(shared);
|
|
60656
|
+
for (const [crushEvent, defs] of override) effective.set(crushEvent, defs);
|
|
60657
|
+
const hooks = {};
|
|
60658
|
+
for (const [crushEvent, defs] of effective) {
|
|
60659
|
+
const allowsMatcher = NORMALIZED_CRUSH_TOOL_EVENTS.has(normalizeCrushEventName(crushEvent));
|
|
60501
60660
|
const entries = [];
|
|
60502
60661
|
for (const def of defs) {
|
|
60503
60662
|
const entry = canonicalDefToCrushEntry({
|
|
60504
60663
|
def,
|
|
60505
|
-
event,
|
|
60664
|
+
event: crushEvent,
|
|
60665
|
+
allowsMatcher,
|
|
60506
60666
|
logger
|
|
60507
60667
|
});
|
|
60508
60668
|
if (entry !== null) entries.push(entry);
|
|
@@ -60512,7 +60672,7 @@ function canonicalToCrushHooks({ config, toolOverride, logger }) {
|
|
|
60512
60672
|
return hooks;
|
|
60513
60673
|
}
|
|
60514
60674
|
/** Convert one canonical hook definition to a Crush entry, or null to skip. */
|
|
60515
|
-
function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
60675
|
+
function canonicalDefToCrushEntry({ def, event, allowsMatcher, logger }) {
|
|
60516
60676
|
if ((def.type ?? "command") !== "command") return null;
|
|
60517
60677
|
if (typeof def.command !== "string" || def.command === "") {
|
|
60518
60678
|
logger?.warn(`Crush hook ${quoteValueForWarning(def.name ?? "")} under "${event}" has no "command", which Crush would discard, so it was skipped.`);
|
|
@@ -60520,7 +60680,10 @@ function canonicalDefToCrushEntry({ def, event, logger }) {
|
|
|
60520
60680
|
}
|
|
60521
60681
|
const entry = { command: def.command };
|
|
60522
60682
|
if (typeof def.name === "string" && def.name !== "") entry.name = def.name;
|
|
60523
|
-
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*")
|
|
60683
|
+
if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*") {
|
|
60684
|
+
if (allowsMatcher) entry.matcher = def.matcher;
|
|
60685
|
+
else logger?.warn(`matcher ${quoteValueForWarning(def.matcher)} on "${event}" hook will be ignored — Crush only accepts a matcher on tool events.`);
|
|
60686
|
+
}
|
|
60524
60687
|
if (typeof def.timeout === "number") entry.timeout = Math.ceil(def.timeout);
|
|
60525
60688
|
return entry;
|
|
60526
60689
|
}
|
|
@@ -60567,12 +60730,13 @@ function crushHooksToCanonical(hooksBlock) {
|
|
|
60567
60730
|
* `<project>/crush.json` (or an existing `.crush.json`; Crush merges the pair,
|
|
60568
60731
|
* lists concatenated) at project scope and `~/.config/crush/crush.json` at
|
|
60569
60732
|
* user scope — as
|
|
60570
|
-
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`.
|
|
60571
|
-
*
|
|
60733
|
+
* `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. `matcher` is only
|
|
60734
|
+
* valid on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
60735
|
+
* `PermissionRequest`, `PermissionResult`). The `hooks` key is owned outright; every other top-level key
|
|
60572
60736
|
* of the file is preserved and the file is never deleted.
|
|
60573
60737
|
*
|
|
60574
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60575
|
-
* @see https://github.com/charmbracelet/crush/blob/
|
|
60738
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
|
|
60739
|
+
* @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
|
|
60576
60740
|
*/
|
|
60577
60741
|
var CrushHooks = class CrushHooks extends ToolHooks {
|
|
60578
60742
|
json;
|
|
@@ -63418,8 +63582,11 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
|
|
|
63418
63582
|
* OpenCode 2.x no longer runs V1 plugin modules: a plugin must default-export
|
|
63419
63583
|
* `{ id, setup(ctx) }`. Inside `setup` hooks are registered per domain
|
|
63420
63584
|
* (`await ctx.tool.hook("execute.before", handler)`), and lifecycle events
|
|
63421
|
-
* are read from `ctx.event.subscribe({ signal })`, an async iterable of
|
|
63422
|
-
* public event stream whose payload lives under `event.data`.
|
|
63585
|
+
* are read from `ctx.event.subscribe({ types, signal })`, an async iterable of
|
|
63586
|
+
* the public event stream whose payload lives under `event.data`. `types`
|
|
63587
|
+
* narrows the stream to the event types the plugin handles (a newer 2.x
|
|
63588
|
+
* option; the per-type checks inside the loop stay, so an older 2.x that
|
|
63589
|
+
* ignores it still runs each command only for its own event).
|
|
63423
63590
|
*
|
|
63424
63591
|
* Commands run through a private Bun Shell instance (`new Bun.$.Shell()`, the
|
|
63425
63592
|
* plugin runs inside the Bun-built OpenCode binary) whose working directory is
|
|
@@ -63522,13 +63689,18 @@ function buildHookRegistrationLines(hookPoint, handlers) {
|
|
|
63522
63689
|
}
|
|
63523
63690
|
/**
|
|
63524
63691
|
* Emit the event-stream loop. Each command is isolated in its own try/catch so
|
|
63525
|
-
* a failing hook neither skips its siblings nor ends the subscription.
|
|
63692
|
+
* a failing hook neither skips its siblings nor ends the subscription. The
|
|
63693
|
+
* subscription passes `types` so the server delivers only the handled events.
|
|
63526
63694
|
*/
|
|
63527
63695
|
function buildEventLoopLines(eventHandlers) {
|
|
63528
63696
|
const lines = [];
|
|
63697
|
+
const types = [...eventHandlers.keys()].map((eventType) => JSON.stringify(eventType)).join(", ");
|
|
63529
63698
|
lines.push("const __rulesyncEvents = new AbortController();");
|
|
63530
63699
|
lines.push("void (async () => {");
|
|
63531
|
-
lines.push(" for await (const event of ctx.event.subscribe({
|
|
63700
|
+
lines.push(" for await (const event of ctx.event.subscribe({");
|
|
63701
|
+
lines.push(` types: [${types}],`);
|
|
63702
|
+
lines.push(" signal: __rulesyncEvents.signal,");
|
|
63703
|
+
lines.push(" })) {");
|
|
63532
63704
|
let isFirst = true;
|
|
63533
63705
|
for (const [eventType, handlers] of eventHandlers) {
|
|
63534
63706
|
lines.push(` ${isFirst ? "if" : "} else if"} (event.type === "${eventType}") {`);
|
|
@@ -65662,7 +65834,12 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65662
65834
|
* resolves inside the bundle and is written as is. The file is loaded only
|
|
65663
65835
|
* when `plugin.json` declares the `ai.mistral.vibe` extension.
|
|
65664
65836
|
*
|
|
65837
|
+
* Plugin hooks run on Vibe v2.x and on v3.0.4+. v3.0.0 through v3.0.3 do not
|
|
65838
|
+
* run them (v3.0.4 Fixed: "Hooks shipped in a native Vibe plugin's
|
|
65839
|
+
* `ai.mistral.vibe/hooks.toml` now run"). The output is the same either way.
|
|
65840
|
+
*
|
|
65665
65841
|
* @see https://github.com/mistralai/mistral-vibe/blob/v2.25.8/vibe/core/plugins/_native.py
|
|
65842
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
|
|
65666
65843
|
*/
|
|
65667
65844
|
var VibePluginHooks = class extends VibeHooks {
|
|
65668
65845
|
static getSettablePaths() {
|
|
@@ -65746,6 +65923,14 @@ const ZCODE_CONVERTER_CONFIG = {
|
|
|
65746
65923
|
tool: "shell",
|
|
65747
65924
|
commandOnly: true
|
|
65748
65925
|
}],
|
|
65926
|
+
keywordPassthroughFields: [{
|
|
65927
|
+
canonical: "failClosed",
|
|
65928
|
+
tool: "onError",
|
|
65929
|
+
trueValue: "deny",
|
|
65930
|
+
falseValue: "allow",
|
|
65931
|
+
events: /* @__PURE__ */ new Set(["preToolUse"]),
|
|
65932
|
+
ignoredWhenFlags: ["async"]
|
|
65933
|
+
}],
|
|
65749
65934
|
wildcardMatcherMeansAll: true
|
|
65750
65935
|
};
|
|
65751
65936
|
/**
|
|
@@ -73886,9 +74071,21 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
73886
74071
|
...rest
|
|
73887
74072
|
};
|
|
73888
74073
|
}
|
|
74074
|
+
/**
|
|
74075
|
+
* GitLab Duo CLI reserves the MCP server name `duo` for its first-party server
|
|
74076
|
+
* (gitlab-lsp v9.33.0+): a workspace, user or plugin entry named exactly `duo`
|
|
74077
|
+
* (case-sensitive) is dropped with only a log line, so it would never load.
|
|
74078
|
+
*
|
|
74079
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/0572967c915465c1ac94ca38ef57676eb44e16d6
|
|
74080
|
+
*/
|
|
74081
|
+
const GITLABDUO_RESERVED_MCP_SERVER_NAME = "duo";
|
|
73889
74082
|
function toGitlabduoServers(servers, logger) {
|
|
73890
74083
|
const result = {};
|
|
73891
74084
|
for (const [serverName, serverConfig] of Object.entries(servers)) {
|
|
74085
|
+
if (serverName === GITLABDUO_RESERVED_MCP_SERVER_NAME) {
|
|
74086
|
+
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.`);
|
|
74087
|
+
continue;
|
|
74088
|
+
}
|
|
73892
74089
|
const converted = toGitlabduoServer({
|
|
73893
74090
|
serverName,
|
|
73894
74091
|
serverConfig,
|
|
@@ -75134,24 +75331,47 @@ async function readKimiCodeConfig({ outputRoot }) {
|
|
|
75134
75331
|
}
|
|
75135
75332
|
}
|
|
75136
75333
|
/**
|
|
75334
|
+
* Kimi's reconnect delays are millisecond counts, so only a non-negative safe
|
|
75335
|
+
* integer is passed through. Anything else (a negative, a fraction, a string)
|
|
75336
|
+
* is dropped with a warning rather than written into, or lifted out of, the
|
|
75337
|
+
* shared global `config.toml`.
|
|
75338
|
+
*/
|
|
75339
|
+
function toKimiCodeReconnectDelay({ value, key, logger }) {
|
|
75340
|
+
if (value === void 0) return;
|
|
75341
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
|
|
75342
|
+
warnWithFallback(logger, `Kimi Code MCP: dropping ${key} because it is not a non-negative integer number of milliseconds.`);
|
|
75343
|
+
}
|
|
75344
|
+
/**
|
|
75137
75345
|
* Read the `[mcp]` defaults back out of the shared user `config.toml` so a
|
|
75138
75346
|
* generate → import round trip keeps them. Absent or unparseable content yields
|
|
75139
75347
|
* no defaults rather than failing the import: this file belongs to three
|
|
75140
75348
|
* features plus the user.
|
|
75141
75349
|
*/
|
|
75142
|
-
async function readKimiCodeMcpDefaults({ outputRoot }) {
|
|
75350
|
+
async function readKimiCodeMcpDefaults({ outputRoot, logger }) {
|
|
75143
75351
|
const config = await readKimiCodeConfig({ outputRoot });
|
|
75144
75352
|
if (!config.parsed) return {};
|
|
75353
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75354
|
+
value: config.mcp.reconnect_initial_delay_ms,
|
|
75355
|
+
key: "[mcp] reconnect_initial_delay_ms",
|
|
75356
|
+
logger
|
|
75357
|
+
});
|
|
75358
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75359
|
+
value: config.mcp.reconnect_max_delay_ms,
|
|
75360
|
+
key: "[mcp] reconnect_max_delay_ms",
|
|
75361
|
+
logger
|
|
75362
|
+
});
|
|
75145
75363
|
return {
|
|
75146
75364
|
...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
|
|
75147
75365
|
...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
|
|
75148
|
-
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
|
|
75366
|
+
...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred },
|
|
75367
|
+
...reconnectInitialDelayMs !== void 0 && { reconnectInitialDelayMs },
|
|
75368
|
+
...reconnectMaxDelayMs !== void 0 && { reconnectMaxDelayMs }
|
|
75149
75369
|
};
|
|
75150
75370
|
}
|
|
75151
75371
|
/**
|
|
75152
75372
|
* Kimi's `[mcp]` section in the shared user config sets the default connect and
|
|
75153
|
-
* per-tool-call timeouts,
|
|
75154
|
-
* server, including ones rulesync did not write. Per-server values in
|
|
75373
|
+
* per-tool-call timeouts, whether tools load on demand, and the automatic
|
|
75374
|
+
* reconnect backoff for *every* MCP server, including ones rulesync did not write. Per-server values in
|
|
75155
75375
|
* `mcp.json` still take precedence; this only moves the default.
|
|
75156
75376
|
*
|
|
75157
75377
|
* Written as an auxiliary file so it goes through the normal write phase and
|
|
@@ -75189,7 +75409,9 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
75189
75409
|
...existingSection,
|
|
75190
75410
|
...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
|
|
75191
75411
|
...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
|
|
75192
|
-
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
|
|
75412
|
+
...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred },
|
|
75413
|
+
...defaults.reconnectInitialDelayMs !== void 0 && { reconnect_initial_delay_ms: defaults.reconnectInitialDelayMs },
|
|
75414
|
+
...defaults.reconnectMaxDelayMs !== void 0 && { reconnect_max_delay_ms: defaults.reconnectMaxDelayMs }
|
|
75193
75415
|
} },
|
|
75194
75416
|
filePath: relativeConfigPath
|
|
75195
75417
|
});
|
|
@@ -75238,7 +75460,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75238
75460
|
fileContent,
|
|
75239
75461
|
validate,
|
|
75240
75462
|
global,
|
|
75241
|
-
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75463
|
+
configDefaults: global ? await readKimiCodeMcpDefaults({
|
|
75464
|
+
outputRoot,
|
|
75465
|
+
logger
|
|
75466
|
+
}) : {},
|
|
75242
75467
|
logger
|
|
75243
75468
|
});
|
|
75244
75469
|
}
|
|
@@ -75288,7 +75513,17 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75288
75513
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
75289
75514
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
75290
75515
|
const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
|
|
75291
|
-
|
|
75516
|
+
const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
|
|
75517
|
+
value: block.reconnectInitialDelayMs,
|
|
75518
|
+
key: "kimi-code.reconnectInitialDelayMs",
|
|
75519
|
+
logger
|
|
75520
|
+
});
|
|
75521
|
+
const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
|
|
75522
|
+
value: block.reconnectMaxDelayMs,
|
|
75523
|
+
key: "kimi-code.reconnectMaxDelayMs",
|
|
75524
|
+
logger
|
|
75525
|
+
});
|
|
75526
|
+
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0 && reconnectInitialDelayMs === void 0 && reconnectMaxDelayMs === void 0) return [];
|
|
75292
75527
|
const existing = await readKimiCodeConfig({ outputRoot });
|
|
75293
75528
|
if (!existing.parsed) {
|
|
75294
75529
|
const paths = kimiCodeConfigRelativePath();
|
|
@@ -75300,7 +75535,9 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
75300
75535
|
defaults: {
|
|
75301
75536
|
startupTimeoutMs,
|
|
75302
75537
|
toolTimeoutMs,
|
|
75303
|
-
defaultDeferred
|
|
75538
|
+
defaultDeferred,
|
|
75539
|
+
reconnectInitialDelayMs,
|
|
75540
|
+
reconnectMaxDelayMs
|
|
75304
75541
|
},
|
|
75305
75542
|
existing
|
|
75306
75543
|
})];
|
|
@@ -85651,6 +85888,7 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85651
85888
|
"CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT",
|
|
85652
85889
|
"CLAUDE_CODE_DISABLE_POWERSHELL_CMD_RM_DENY",
|
|
85653
85890
|
"CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT",
|
|
85891
|
+
"CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS",
|
|
85654
85892
|
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
85655
85893
|
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
85656
85894
|
"CLAUDE_CODE_PROJECT_DIR_NAME",
|
|
@@ -104896,32 +105134,59 @@ function buildChecksStrategy(ctx) {
|
|
|
104896
105134
|
}
|
|
104897
105135
|
// src/features/shared/antigravity-plugin-manifest.ts
|
|
104898
105136
|
const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
105137
|
+
const ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
105138
|
+
/**
|
|
105139
|
+
* Lower-case a directory name and turn `_` into `-` (collapsing repeated and
|
|
105140
|
+
* trimming edge hyphens), so `My_Plugin` becomes `my-plugin`.
|
|
105141
|
+
*/
|
|
105142
|
+
function toStrictAntigravityPluginName(name) {
|
|
105143
|
+
return name.toLowerCase().replace(/[-_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
105144
|
+
}
|
|
104899
105145
|
/**
|
|
104900
105146
|
* Create the `plugin.json` manifest Antigravity requires before it treats a
|
|
104901
105147
|
* directory as a plugin, so a tree generated from scratch actually loads.
|
|
104902
105148
|
*
|
|
104903
|
-
*
|
|
104904
|
-
*
|
|
104905
|
-
*
|
|
104906
|
-
*
|
|
105149
|
+
* Only the required `name` is written, derived from the plugin directory's
|
|
105150
|
+
* name and normalized to the lowercase kebab-case form `agy plugin validate
|
|
105151
|
+
* --strict` and the Marketplace require; the other manifest fields (such as
|
|
105152
|
+
* `description`, `version` or `author`) have no rulesync source. An existing
|
|
105153
|
+
* manifest is never rewritten: it may carry hand-authored fields. Its `name` is
|
|
105154
|
+
* read only to report a collision with another output root's name. A
|
|
104907
105155
|
* manifest kept in one of the manifest folders counts as present too, so no
|
|
104908
105156
|
* root stub is written next to it.
|
|
104909
105157
|
*/
|
|
104910
105158
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
104911
105159
|
const paths = [];
|
|
105160
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
105161
|
+
const noteName = (name, manifestPath) => {
|
|
105162
|
+
const previousPath = seenNames.get(name);
|
|
105163
|
+
if (previousPath === void 0) {
|
|
105164
|
+
seenNames.set(name, manifestPath);
|
|
105165
|
+
return;
|
|
105166
|
+
}
|
|
105167
|
+
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.`);
|
|
105168
|
+
};
|
|
104912
105169
|
for (const outputRoot of outputRoots) {
|
|
104913
105170
|
const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
104914
|
-
|
|
105171
|
+
const existingManifestPath = await findAntigravityPluginManifest(outputRoot);
|
|
105172
|
+
if (existingManifestPath !== void 0) {
|
|
105173
|
+
const existingName = await readManifestName(existingManifestPath);
|
|
105174
|
+
if (existingName !== void 0) noteName(existingName, existingManifestPath);
|
|
105175
|
+
continue;
|
|
105176
|
+
}
|
|
104915
105177
|
if (await refusesWriteOutsideRoot({
|
|
104916
105178
|
logger,
|
|
104917
105179
|
rootPath: outputRoot,
|
|
104918
105180
|
targetPath: filePath
|
|
104919
105181
|
})) continue;
|
|
104920
|
-
const
|
|
104921
|
-
|
|
104922
|
-
|
|
105182
|
+
const dirName = basename(resolve(outputRoot));
|
|
105183
|
+
const name = toStrictAntigravityPluginName(dirName);
|
|
105184
|
+
if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(dirName) || !ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.test(name)) {
|
|
105185
|
+
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".`);
|
|
104923
105186
|
continue;
|
|
104924
105187
|
}
|
|
105188
|
+
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)}.`);
|
|
105189
|
+
noteName(name, filePath);
|
|
104925
105190
|
const content = addTrailingNewline(JSON.stringify({ name }, null, 2));
|
|
104926
105191
|
if (dryRun) logger.info(`[DRY RUN] Would write: ${stripControlCharacters(filePath)}`);
|
|
104927
105192
|
else await writeFileContent(filePath, content);
|
|
@@ -104934,10 +105199,18 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
104934
105199
|
sourceLoadFailed: false
|
|
104935
105200
|
};
|
|
104936
105201
|
}
|
|
104937
|
-
async function
|
|
105202
|
+
async function findAntigravityPluginManifest(outputRoot) {
|
|
104938
105203
|
const candidates = [join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => join(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
104939
|
-
for (const candidate of candidates) if (await fileExists(candidate)) return
|
|
104940
|
-
|
|
105204
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return candidate;
|
|
105205
|
+
}
|
|
105206
|
+
/** The manifest's string `name`, or `undefined` when it is unreadable or absent. */
|
|
105207
|
+
async function readManifestName(manifestPath) {
|
|
105208
|
+
try {
|
|
105209
|
+
const parsed = JSON.parse(await readFileContent(manifestPath));
|
|
105210
|
+
return isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : void 0;
|
|
105211
|
+
} catch {
|
|
105212
|
+
return;
|
|
105213
|
+
}
|
|
104941
105214
|
}
|
|
104942
105215
|
// src/features/shared/hermes-project-plugin-activation.ts
|
|
104943
105216
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|
|
@@ -107040,4 +107313,4 @@ async function importChecksCore(params) {
|
|
|
107040
107313
|
}
|
|
107041
107314
|
export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
|
|
107042
107315
|
|
|
107043
|
-
//# sourceMappingURL=import-
|
|
107316
|
+
//# sourceMappingURL=import-BKyt4qe5.js.map
|