mirror of
https://github.com/mksglu/context-mode.git
synced 2026-10-03 04:38:25 +08:00
fix: use absolute node path in hook commands instead of CLI
The `context-mode hook <platform> <event>` CLI command requires the binary to be in PATH, which fails on homebrew/nvm/volta setups where the global bin dir is not in the hook subprocess PATH. All three adapters now use `node "<pluginRoot>/hooks/<script>.mjs"` format via buildHookCommand(hookType, pluginRoot), ensuring hooks work regardless of PATH configuration. Affected adapters: Claude Code, VS Code Copilot, Gemini CLI. 727 tests passing.
This commit is contained in:
@@ -104,9 +104,13 @@ export function isContextModeHook(
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type.
|
||||
* Uses the CLI dispatcher: `context-mode hook claude-code <event>`
|
||||
* Requires global install: `npm install -g context-mode`
|
||||
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
||||
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
||||
*/
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
export function buildHookCommand(hookType: HookType, pluginRoot?: string): string {
|
||||
if (pluginRoot) {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
||||
}
|
||||
return `context-mode hook claude-code ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ export class ClaudeCodeAdapter implements HookAdapter {
|
||||
];
|
||||
|
||||
for (const hookType of hookTypes) {
|
||||
const command = buildHookCommand(hookType);
|
||||
const command = buildHookCommand(hookType, pluginRoot);
|
||||
|
||||
if (hookType === HOOK_TYPES.PRE_TOOL_USE) {
|
||||
const entry = {
|
||||
|
||||
@@ -77,9 +77,13 @@ export function isContextModeHook(
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type.
|
||||
* Uses the CLI dispatcher: `context-mode hook gemini-cli <event>`
|
||||
* Requires global install: `npm install -g context-mode`
|
||||
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
||||
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
||||
*/
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
export function buildHookCommand(hookType: HookType, pluginRoot?: string): string {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
if (pluginRoot && scriptName) {
|
||||
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
||||
}
|
||||
return `context-mode hook gemini-cli ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ interface GeminiCLIHookInput {
|
||||
import {
|
||||
HOOK_TYPES as GEMINI_HOOK_NAMES,
|
||||
HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS,
|
||||
buildHookCommand as buildGeminiHookCommand,
|
||||
type HookType as GeminiHookType,
|
||||
} from "./hooks.js";
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
@@ -241,7 +243,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
return join(this.getSessionDir(), `${hash}-events.md`);
|
||||
}
|
||||
|
||||
generateHookConfig(_pluginRoot: string): HookRegistration {
|
||||
generateHookConfig(pluginRoot: string): HookRegistration {
|
||||
return {
|
||||
[GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
|
||||
{
|
||||
@@ -249,7 +251,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.BEFORE_TOOL.toLowerCase()}`,
|
||||
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.BEFORE_TOOL, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -260,7 +262,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.AFTER_TOOL.toLowerCase()}`,
|
||||
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.AFTER_TOOL, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -271,7 +273,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.PRE_COMPRESS.toLowerCase()}`,
|
||||
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.PRE_COMPRESS, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -282,7 +284,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.SESSION_START.toLowerCase()}`,
|
||||
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.SESSION_START, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -440,7 +442,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
|
||||
// ── Upgrade ────────────────────────────────────────────
|
||||
|
||||
configureAllHooks(_pluginRoot: string): string[] {
|
||||
configureAllHooks(pluginRoot: string): string[] {
|
||||
const settings = this.readSettings() ?? {};
|
||||
const hooks = (settings.hooks ?? {}) as Record<string, unknown>;
|
||||
const changes: string[] = [];
|
||||
@@ -453,7 +455,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
];
|
||||
|
||||
for (const config of hookConfigs) {
|
||||
const command = `context-mode hook gemini-cli ${config.name.toLowerCase()}`;
|
||||
const command = buildGeminiHookCommand(config.name as GeminiHookType, pluginRoot);
|
||||
const entry = {
|
||||
matcher: "",
|
||||
hooks: [{ type: "command", command }],
|
||||
|
||||
@@ -84,13 +84,16 @@ export function isContextModeHook(
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type.
|
||||
* Uses the CLI dispatcher: `context-mode hook vscode-copilot <event>`
|
||||
* Requires global install: `npm install -g context-mode`
|
||||
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
||||
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
||||
*/
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
export function buildHookCommand(hookType: HookType, pluginRoot?: string): string {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
if (!scriptName) {
|
||||
throw new Error(`No script defined for hook type: ${hookType}`);
|
||||
}
|
||||
if (pluginRoot) {
|
||||
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
||||
}
|
||||
return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ interface VSCodeCopilotHookInput {
|
||||
import {
|
||||
HOOK_TYPES as VSCODE_HOOK_NAMES,
|
||||
HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS,
|
||||
buildHookCommand as buildVSCodeHookCommand,
|
||||
type HookType as VSCodeHookType,
|
||||
} from "./hooks.js";
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
@@ -266,7 +268,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
return join(this.getSessionDir(), `${hash}-events.md`);
|
||||
}
|
||||
|
||||
generateHookConfig(_pluginRoot: string): HookRegistration {
|
||||
generateHookConfig(pluginRoot: string): HookRegistration {
|
||||
return {
|
||||
[VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
|
||||
{
|
||||
@@ -274,7 +276,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_TOOL_USE.toLowerCase()}`,
|
||||
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_TOOL_USE, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -285,7 +287,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.POST_TOOL_USE.toLowerCase()}`,
|
||||
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.POST_TOOL_USE, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -296,7 +298,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_COMPACT.toLowerCase()}`,
|
||||
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_COMPACT, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -307,7 +309,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.SESSION_START.toLowerCase()}`,
|
||||
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.SESSION_START, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -498,7 +500,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
|
||||
// ── Upgrade ────────────────────────────────────────────
|
||||
|
||||
configureAllHooks(_pluginRoot: string): string[] {
|
||||
configureAllHooks(pluginRoot: string): string[] {
|
||||
const changes: string[] = [];
|
||||
const hookConfig: Record<string, unknown> = { hooks: {} };
|
||||
const hooks = hookConfig.hooks as Record<string, unknown>;
|
||||
@@ -520,7 +522,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `context-mode hook vscode-copilot ${hookType.toLowerCase()}`,
|
||||
command: buildVSCodeHookCommand(hookType, pluginRoot),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user