mirror of
https://github.com/mksglu/context-mode.git
synced 2026-09-19 03:27:16 +08:00
feat: global install + CLI hook dispatcher
Replace `node ./node_modules/context-mode/hooks/...` with `context-mode hook <platform> <event>`. All platforms now use `npm install -g context-mode` as prerequisite. MCP server configs use `context-mode` directly instead of `npx -y context-mode`. This ensures ctx-upgrade persists across sessions (no ephemeral npx cache). - Add `hook` subcommand to CLI with HOOK_MAP dispatcher - Update all adapter buildHookCommand() to use CLI dispatcher - Update all adapter configureAllHooks() and generateHookConfig() - Update all config templates (gemini-cli, vscode-copilot, opencode, codex) - Update README install instructions for all platforms Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -92,20 +92,25 @@ This gives you the 6 sandbox tools but without automatic routing. The model can
|
||||
<details>
|
||||
<summary><strong>Gemini CLI</strong></summary>
|
||||
|
||||
**Step 1 — Register the MCP server.** Add to `~/.gemini/settings.json`:
|
||||
**Step 1 — Install globally:**
|
||||
|
||||
```bash
|
||||
npm install -g context-mode
|
||||
```
|
||||
|
||||
**Step 2 — Register the MCP server.** Add to `~/.gemini/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"context-mode": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "context-mode"]
|
||||
"command": "context-mode"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2 — Add hooks.** Without hooks, the model can ignore routing instructions and dump raw output into your context window. Hooks intercept every tool call and enforce sandbox routing programmatically — blocking `curl`, `wget`, and other data-heavy commands before they execute. Add to the same `~/.gemini/settings.json`:
|
||||
**Step 3 — Add hooks.** Without hooks, the model can ignore routing instructions and dump raw output into your context window. Hooks intercept every tool call and enforce sandbox routing programmatically — blocking `curl`, `wget`, and other data-heavy commands before they execute. Add to the same `~/.gemini/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -113,26 +118,26 @@ This gives you the 6 sandbox tools but without automatic routing. The model can
|
||||
"BeforeTool": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [{ "type": "command", "command": "node ./node_modules/context-mode/hooks/gemini-cli/beforetool.mjs" }]
|
||||
"hooks": [{ "type": "command", "command": "context-mode hook gemini-cli beforetool" }]
|
||||
}
|
||||
],
|
||||
"AfterTool": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [{ "type": "command", "command": "node ./node_modules/context-mode/hooks/gemini-cli/aftertool.mjs" }]
|
||||
"hooks": [{ "type": "command", "command": "context-mode hook gemini-cli aftertool" }]
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [{ "type": "command", "command": "node ./node_modules/context-mode/hooks/gemini-cli/sessionstart.mjs" }]
|
||||
"hooks": [{ "type": "command", "command": "context-mode hook gemini-cli sessionstart" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3 — Restart Gemini CLI.** On first run, a `GEMINI.md` routing instructions file is auto-created in your project root. This works alongside hooks as a parallel enforcement layer — hooks block dangerous commands programmatically, while `GEMINI.md` teaches the model to prefer sandbox tools from the start.
|
||||
**Step 4 — Restart Gemini CLI.** On first run, a `GEMINI.md` routing instructions file is auto-created in your project root. This works alongside hooks as a parallel enforcement layer — hooks block dangerous commands programmatically, while `GEMINI.md` teaches the model to prefer sandbox tools from the start.
|
||||
|
||||
> **Why hooks matter:** Without hooks, context-mode relies on `GEMINI.md` instructions alone (~60% compliance). The model sometimes follows them, but regularly runs raw `curl`, reads large files directly, or dumps unprocessed output into context — a single unrouted Playwright snapshot (56 KB) wipes out an entire session's savings. With hooks, every tool call is intercepted before execution — dangerous commands are blocked, and routing guidance is injected in real-time. This is the difference between ~60% and ~98% context savings.
|
||||
|
||||
@@ -143,38 +148,43 @@ Full hook config including PreCompress: [`configs/gemini-cli/settings.json`](con
|
||||
<details>
|
||||
<summary><strong>VS Code Copilot</strong></summary>
|
||||
|
||||
**Step 1 — Register the MCP server.** Create `.vscode/mcp.json` in your project root:
|
||||
**Step 1 — Install globally:**
|
||||
|
||||
```bash
|
||||
npm install -g context-mode
|
||||
```
|
||||
|
||||
**Step 2 — Register the MCP server.** Create `.vscode/mcp.json` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"context-mode": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "context-mode"]
|
||||
"command": "context-mode"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2 — Add hooks.** Without hooks, the model can bypass routing and dump raw output into your context. Hooks intercept every tool call and enforce sandbox routing programmatically. Create `.github/hooks/context-mode.json`:
|
||||
**Step 3 — Add hooks.** Without hooks, the model can bypass routing and dump raw output into your context. Hooks intercept every tool call and enforce sandbox routing programmatically. Create `.github/hooks/context-mode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/pretooluse.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot pretooluse" }
|
||||
],
|
||||
"PostToolUse": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/posttooluse.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot posttooluse" }
|
||||
],
|
||||
"SessionStart": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/sessionstart.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot sessionstart" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3 — Restart VS Code.** On first run, a `.github/copilot-instructions.md` routing instructions file is auto-created in your project. This works alongside hooks as a parallel enforcement layer — hooks intercept tool calls programmatically, while `copilot-instructions.md` guides the model's tool selection from session start.
|
||||
**Step 4 — Restart VS Code.** On first run, a `.github/copilot-instructions.md` routing instructions file is auto-created in your project. This works alongside hooks as a parallel enforcement layer — hooks intercept tool calls programmatically, while `copilot-instructions.md` guides the model's tool selection from session start.
|
||||
|
||||
> **Why hooks matter:** Without hooks, `copilot-instructions.md` guides the model but can't block commands. A single unrouted Playwright snapshot (56 KB) or `gh issue list` (59 KB) wipes out minutes of context savings. With hooks, these calls are intercepted and redirected to the sandbox before they execute.
|
||||
|
||||
@@ -185,7 +195,13 @@ Full hook config including PreCompact: [`configs/vscode-copilot/hooks.json`](con
|
||||
<details>
|
||||
<summary><strong>OpenCode</strong></summary>
|
||||
|
||||
**Step 1 — Register the MCP server and plugin.** OpenCode uses a TypeScript plugin paradigm instead of JSON hooks. Add both the MCP server and the plugin to `opencode.json` in your project root:
|
||||
**Step 1 — Install globally:**
|
||||
|
||||
```bash
|
||||
npm install -g context-mode
|
||||
```
|
||||
|
||||
**Step 2 — Register the MCP server and plugin.** OpenCode uses a TypeScript plugin paradigm instead of JSON hooks. Add both the MCP server and the plugin to `opencode.json` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -193,7 +209,7 @@ Full hook config including PreCompact: [`configs/vscode-copilot/hooks.json`](con
|
||||
"mcp": {
|
||||
"context-mode": {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "context-mode"]
|
||||
"command": ["context-mode"]
|
||||
}
|
||||
},
|
||||
"plugin": ["context-mode"]
|
||||
@@ -202,7 +218,7 @@ Full hook config including PreCompact: [`configs/vscode-copilot/hooks.json`](con
|
||||
|
||||
The `mcp` entry gives you the 6 sandbox tools. The `plugin` entry enables hooks — OpenCode calls the plugin's TypeScript functions directly before and after each tool execution, blocking dangerous commands (like raw `curl`) and enforcing sandbox routing.
|
||||
|
||||
**Step 2 — Restart OpenCode.** On first run, an `AGENTS.md` routing instructions file is auto-created in your project root. This works alongside the plugin as a parallel enforcement layer — the plugin intercepts tool calls at runtime, while `AGENTS.md` guides the model's tool preferences from session start.
|
||||
**Step 3 — Restart OpenCode.** On first run, an `AGENTS.md` routing instructions file is auto-created in your project root. This works alongside the plugin as a parallel enforcement layer — the plugin intercepts tool calls at runtime, while `AGENTS.md` guides the model's tool preferences from session start.
|
||||
|
||||
> **Why the plugin matters:** Without the `plugin` entry, context-mode has no way to intercept tool calls. The model can run raw `curl`, read large files directly, or dump unprocessed output into context — ignoring `AGENTS.md` instructions. With the plugin, `tool.execute.before` fires on every tool call and blocks or redirects data-heavy commands before they execute. The `experimental.session.compacting` hook builds and injects resume snapshots when the conversation compacts, preserving session state.
|
||||
>
|
||||
@@ -213,15 +229,20 @@ The `mcp` entry gives you the 6 sandbox tools. The `plugin` entry enables hooks
|
||||
<details>
|
||||
<summary><strong>Codex CLI</strong></summary>
|
||||
|
||||
**Step 1 — Register the MCP server.** Add to `~/.codex/config.toml`:
|
||||
**Step 1 — Install globally:**
|
||||
|
||||
```bash
|
||||
npm install -g context-mode
|
||||
```
|
||||
|
||||
**Step 2 — Register the MCP server.** Add to `~/.codex/config.toml`:
|
||||
|
||||
```toml
|
||||
[mcp_servers.context-mode]
|
||||
command = "npx"
|
||||
args = ["-y", "context-mode"]
|
||||
command = "context-mode"
|
||||
```
|
||||
|
||||
**Step 2 — Restart Codex CLI.** On first run, an `AGENTS.md` routing instructions file is auto-created in your project root. Codex CLI reads `AGENTS.md` automatically and learns to prefer context-mode sandbox tools.
|
||||
**Step 3 — Restart Codex CLI.** On first run, an `AGENTS.md` routing instructions file is auto-created in your project root. Codex CLI reads `AGENTS.md` automatically and learns to prefer context-mode sandbox tools.
|
||||
|
||||
**About hooks:** Codex CLI does not support hooks — PRs [#2904](https://github.com/openai/codex/pull/2904) and [#9796](https://github.com/openai/codex/pull/9796) were closed without merge. The `AGENTS.md` routing instructions file is the only enforcement method (~60% compliance). The model receives the instructions at session start and sometimes follows them, but there is no programmatic interception — it can run raw `curl`, read large files, or bypass sandbox tools at any time.
|
||||
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
# Add this to ~/.codex/config.toml
|
||||
|
||||
[mcp_servers.context-mode]
|
||||
command = "npx"
|
||||
args = ["-y", "context-mode"]
|
||||
command = "context-mode"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"context-mode": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "context-mode"]
|
||||
"command": "context-mode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ./node_modules/context-mode/hooks/gemini-cli/beforetool.mjs"
|
||||
"command": "context-mode hook gemini-cli beforetool"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ./node_modules/context-mode/hooks/gemini-cli/aftertool.mjs"
|
||||
"command": "context-mode hook gemini-cli aftertool"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ./node_modules/context-mode/hooks/gemini-cli/precompress.mjs"
|
||||
"command": "context-mode hook gemini-cli precompress"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ./node_modules/context-mode/hooks/gemini-cli/sessionstart.mjs"
|
||||
"command": "context-mode hook gemini-cli sessionstart"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"mcp": {
|
||||
"context-mode": {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "context-mode"]
|
||||
"command": ["context-mode"]
|
||||
}
|
||||
},
|
||||
"plugin": ["context-mode"]
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/pretooluse.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot pretooluse" }
|
||||
],
|
||||
"PostToolUse": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/posttooluse.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot posttooluse" }
|
||||
],
|
||||
"PreCompact": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/precompact.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot precompact" }
|
||||
],
|
||||
"SessionStart": [
|
||||
{ "type": "command", "command": "node ./node_modules/context-mode/hooks/vscode-copilot/sessionstart.mjs" }
|
||||
{ "type": "command", "command": "context-mode hook vscode-copilot sessionstart" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +97,10 @@ export function isContextModeHook(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type and plugin root.
|
||||
* 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`
|
||||
*/
|
||||
export function buildHookCommand(
|
||||
hookType: HookType,
|
||||
pluginRoot: string,
|
||||
): string {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
return `node ${pluginRoot}/hooks/${scriptName}`;
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
return `context-mode hook claude-code ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ export class ClaudeCodeAdapter implements HookAdapter {
|
||||
check: "PreToolUse hook",
|
||||
status: "fail",
|
||||
message: "Could not read ~/.claude/settings.json",
|
||||
fix: "npx context-mode upgrade",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
return results;
|
||||
}
|
||||
@@ -326,14 +326,14 @@ export class ClaudeCodeAdapter implements HookAdapter {
|
||||
message: hasHook
|
||||
? "PreToolUse hook configured"
|
||||
: "PreToolUse exists but does not point to pretooluse.mjs",
|
||||
fix: hasHook ? undefined : "npx context-mode upgrade",
|
||||
fix: hasHook ? undefined : "context-mode upgrade",
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
check: "PreToolUse hook",
|
||||
status: "fail",
|
||||
message: "No PreToolUse hooks found",
|
||||
fix: "npx context-mode upgrade",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -351,14 +351,14 @@ export class ClaudeCodeAdapter implements HookAdapter {
|
||||
message: hasHook
|
||||
? "SessionStart hook configured"
|
||||
: "SessionStart exists but does not point to sessionstart.mjs",
|
||||
fix: hasHook ? undefined : "npx context-mode upgrade",
|
||||
fix: hasHook ? undefined : "context-mode upgrade",
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
check: "SessionStart hook",
|
||||
status: "fail",
|
||||
message: "No SessionStart hooks found",
|
||||
fix: "npx context-mode upgrade",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -474,7 +474,7 @@ export class ClaudeCodeAdapter implements HookAdapter {
|
||||
];
|
||||
|
||||
for (const hookType of hookTypes) {
|
||||
const command = buildHookCommand(hookType, pluginRoot);
|
||||
const command = buildHookCommand(hookType);
|
||||
|
||||
if (hookType === HOOK_TYPES.PRE_TOOL_USE) {
|
||||
const entry = {
|
||||
|
||||
@@ -71,12 +71,10 @@ export function isContextModeHook(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type and plugin root.
|
||||
* 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`
|
||||
*/
|
||||
export function buildHookCommand(
|
||||
hookType: HookType,
|
||||
pluginRoot: string,
|
||||
): string {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
return `node ${pluginRoot}/hooks/gemini-cli/${scriptName}`;
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
return `context-mode hook gemini-cli ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -226,9 +226,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
return join(this.getSessionDir(), `${hash}-events.md`);
|
||||
}
|
||||
|
||||
generateHookConfig(pluginRoot: string): HookRegistration {
|
||||
const hooksDir = join(pluginRoot, "hooks", "gemini-cli");
|
||||
|
||||
generateHookConfig(_pluginRoot: string): HookRegistration {
|
||||
return {
|
||||
[GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
|
||||
{
|
||||
@@ -236,7 +234,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.BEFORE_TOOL]}`,
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.BEFORE_TOOL.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -247,7 +245,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.AFTER_TOOL]}`,
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.AFTER_TOOL.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -258,7 +256,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.PRE_COMPRESS]}`,
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.PRE_COMPRESS.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -269,7 +267,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.SESSION_START]}`,
|
||||
command: `context-mode hook gemini-cli ${GEMINI_HOOK_NAMES.SESSION_START.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -307,7 +305,7 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
check: "BeforeTool hook",
|
||||
status: "fail",
|
||||
message: "Could not read ~/.gemini/settings.json",
|
||||
fix: "npx context-mode upgrade --platform gemini-cli",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
return results;
|
||||
}
|
||||
@@ -328,14 +326,14 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
message: hasHook
|
||||
? "BeforeTool hook configured"
|
||||
: "BeforeTool exists but does not point to context-mode",
|
||||
fix: hasHook ? undefined : "npx context-mode upgrade --platform gemini-cli",
|
||||
fix: hasHook ? undefined : "context-mode upgrade",
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
check: "BeforeTool hook",
|
||||
status: "fail",
|
||||
message: "No BeforeTool hooks found",
|
||||
fix: "npx context-mode upgrade --platform gemini-cli",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,14 +351,14 @@ export class GeminiCLIAdapter implements HookAdapter {
|
||||
message: hasHook
|
||||
? "SessionStart hook configured"
|
||||
: "SessionStart exists but does not point to context-mode",
|
||||
fix: hasHook ? undefined : "npx context-mode upgrade --platform gemini-cli",
|
||||
fix: hasHook ? undefined : "context-mode upgrade",
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
check: "SessionStart hook",
|
||||
status: "fail",
|
||||
message: "No SessionStart hooks found",
|
||||
fix: "npx context-mode upgrade --platform gemini-cli",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -427,28 +425,20 @@ 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[] = [];
|
||||
const hooksDir = join(pluginRoot, "hooks", "gemini-cli");
|
||||
|
||||
const hookConfigs: Array<{
|
||||
name: string;
|
||||
script: string;
|
||||
}> = [
|
||||
{
|
||||
name: GEMINI_HOOK_NAMES.BEFORE_TOOL,
|
||||
script: GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.BEFORE_TOOL],
|
||||
},
|
||||
{
|
||||
name: GEMINI_HOOK_NAMES.SESSION_START,
|
||||
script: GEMINI_HOOK_SCRIPTS[GEMINI_HOOK_NAMES.SESSION_START],
|
||||
},
|
||||
{ name: GEMINI_HOOK_NAMES.BEFORE_TOOL },
|
||||
{ name: GEMINI_HOOK_NAMES.SESSION_START },
|
||||
];
|
||||
|
||||
for (const config of hookConfigs) {
|
||||
const command = `node ${hooksDir}/${config.script}`;
|
||||
const command = `context-mode hook gemini-cli ${config.name.toLowerCase()}`;
|
||||
const entry = {
|
||||
matcher: "",
|
||||
hooks: [{ type: "command", command }],
|
||||
|
||||
@@ -273,7 +273,7 @@ export class OpenCodeAdapter implements HookAdapter {
|
||||
check: "Plugin configuration",
|
||||
status: "fail",
|
||||
message: "Could not read opencode.json",
|
||||
fix: "npx context-mode upgrade --platform opencode",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
return results;
|
||||
}
|
||||
@@ -290,14 +290,14 @@ export class OpenCodeAdapter implements HookAdapter {
|
||||
: "context-mode not found in plugin array",
|
||||
fix: hasPlugin
|
||||
? undefined
|
||||
: "npx context-mode upgrade --platform opencode",
|
||||
: "context-mode upgrade",
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
check: "Plugin registration",
|
||||
status: "fail",
|
||||
message: "No plugin array found in opencode.json",
|
||||
fix: "npx context-mode upgrade --platform opencode",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ export class OpenCodeAdapter implements HookAdapter {
|
||||
check: "Plugin registration",
|
||||
status: "fail",
|
||||
message: "context-mode not found in opencode.json plugin array",
|
||||
fix: "npx context-mode upgrade --platform opencode",
|
||||
fix: "context-mode upgrade",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -78,15 +78,14 @@ export function isContextModeHook(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the hook command string for a given hook type and plugin root.
|
||||
* 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`
|
||||
*/
|
||||
export function buildHookCommand(
|
||||
hookType: HookType,
|
||||
pluginRoot: string,
|
||||
): string {
|
||||
export function buildHookCommand(hookType: HookType): string {
|
||||
const scriptName = HOOK_SCRIPTS[hookType];
|
||||
if (!scriptName) {
|
||||
throw new Error(`No script defined for hook type: ${hookType}`);
|
||||
}
|
||||
return `node ${pluginRoot}/hooks/vscode-copilot/${scriptName}`;
|
||||
return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -250,9 +250,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
return join(this.getSessionDir(), `${hash}-events.md`);
|
||||
}
|
||||
|
||||
generateHookConfig(pluginRoot: string): HookRegistration {
|
||||
const hooksDir = join(pluginRoot, "hooks", "vscode-copilot");
|
||||
|
||||
generateHookConfig(_pluginRoot: string): HookRegistration {
|
||||
return {
|
||||
[VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
|
||||
{
|
||||
@@ -260,7 +258,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${VSCODE_HOOK_SCRIPTS[VSCODE_HOOK_NAMES.PRE_TOOL_USE]}`,
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_TOOL_USE.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -271,7 +269,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${VSCODE_HOOK_SCRIPTS[VSCODE_HOOK_NAMES.POST_TOOL_USE]}`,
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.POST_TOOL_USE.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -282,7 +280,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${VSCODE_HOOK_SCRIPTS[VSCODE_HOOK_NAMES.PRE_COMPACT]}`,
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_COMPACT.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -293,7 +291,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${VSCODE_HOOK_SCRIPTS[VSCODE_HOOK_NAMES.SESSION_START]}`,
|
||||
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.SESSION_START.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -343,7 +341,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
check: "Hooks directory",
|
||||
status: "fail",
|
||||
message: ".github/hooks/ directory not found",
|
||||
fix: "npx context-mode upgrade --platform vscode-copilot",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
return results;
|
||||
}
|
||||
@@ -367,7 +365,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
check: "PreToolUse hook",
|
||||
status: "fail",
|
||||
message: "PreToolUse not found in context-mode.json",
|
||||
fix: "npx context-mode upgrade --platform vscode-copilot",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,7 +381,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
check: "SessionStart hook",
|
||||
status: "fail",
|
||||
message: "SessionStart not found in context-mode.json",
|
||||
fix: "npx context-mode upgrade --platform vscode-copilot",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
@@ -391,7 +389,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
check: "Hook configuration",
|
||||
status: "fail",
|
||||
message: "Could not read .github/hooks/context-mode.json",
|
||||
fix: "npx context-mode upgrade --platform vscode-copilot",
|
||||
fix: "context-mode upgrade",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -484,9 +482,8 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
|
||||
// ── Upgrade ────────────────────────────────────────────
|
||||
|
||||
configureAllHooks(pluginRoot: string): string[] {
|
||||
configureAllHooks(_pluginRoot: string): string[] {
|
||||
const changes: string[] = [];
|
||||
const hooksDir = join(pluginRoot, "hooks", "vscode-copilot");
|
||||
const hookConfig: Record<string, unknown> = { hooks: {} };
|
||||
const hooks = hookConfig.hooks as Record<string, unknown>;
|
||||
|
||||
@@ -507,7 +504,7 @@ export class VSCodeCopilotAdapter implements HookAdapter {
|
||||
hooks: [
|
||||
{
|
||||
type: "command",
|
||||
command: `node ${hooksDir}/${script}`,
|
||||
command: `context-mode hook vscode-copilot ${hookType.toLowerCase()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+47
-4
@@ -3,9 +3,10 @@
|
||||
* context-mode CLI
|
||||
*
|
||||
* Usage:
|
||||
* context-mode → Start MCP server (stdio)
|
||||
* context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
|
||||
* context-mode upgrade → Fix hooks, permissions, and settings
|
||||
* context-mode → Start MCP server (stdio)
|
||||
* context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
|
||||
* context-mode upgrade → Fix hooks, permissions, and settings
|
||||
* context-mode hook <platform> <event> → Dispatch a hook script (used by platform hook configs)
|
||||
*
|
||||
* Platform auto-detection: CLI detects which platform is running
|
||||
* (Claude Code, Gemini CLI, OpenCode, etc.) and uses the appropriate adapter.
|
||||
@@ -15,7 +16,7 @@ import * as p from "@clack/prompts";
|
||||
import color from "picocolors";
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFileSync, cpSync, accessSync, readdirSync, rmSync, constants } from "node:fs";
|
||||
import { resolve, dirname } from "node:path";
|
||||
import { resolve, dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
detectRuntimes,
|
||||
@@ -28,12 +29,54 @@ import {
|
||||
import { detectPlatform, getAdapter } from "./adapters/detect.js";
|
||||
import type { HookAdapter } from "./adapters/types.js";
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* Hook dispatcher — `context-mode hook <platform> <event>`
|
||||
* ------------------------------------------------------- */
|
||||
|
||||
const HOOK_MAP: Record<string, Record<string, string>> = {
|
||||
"claude-code": {
|
||||
pretooluse: "hooks/pretooluse.mjs",
|
||||
posttooluse: "hooks/posttooluse.mjs",
|
||||
precompact: "hooks/precompact.mjs",
|
||||
sessionstart: "hooks/sessionstart.mjs",
|
||||
userpromptsubmit: "hooks/userpromptsubmit.mjs",
|
||||
},
|
||||
"gemini-cli": {
|
||||
beforetool: "hooks/gemini-cli/beforetool.mjs",
|
||||
aftertool: "hooks/gemini-cli/aftertool.mjs",
|
||||
precompress: "hooks/gemini-cli/precompress.mjs",
|
||||
sessionstart: "hooks/gemini-cli/sessionstart.mjs",
|
||||
},
|
||||
"vscode-copilot": {
|
||||
pretooluse: "hooks/vscode-copilot/pretooluse.mjs",
|
||||
posttooluse: "hooks/vscode-copilot/posttooluse.mjs",
|
||||
precompact: "hooks/vscode-copilot/precompact.mjs",
|
||||
sessionstart: "hooks/vscode-copilot/sessionstart.mjs",
|
||||
},
|
||||
};
|
||||
|
||||
async function hookDispatch(platform: string, event: string): Promise<void> {
|
||||
const scriptPath = HOOK_MAP[platform]?.[event];
|
||||
if (!scriptPath) {
|
||||
console.error(`Unknown hook: ${platform}/${event}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const pluginRoot = getPluginRoot();
|
||||
await import(join(pluginRoot, scriptPath));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------
|
||||
* Entry point
|
||||
* ------------------------------------------------------- */
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args[0] === "doctor") {
|
||||
doctor().then((code) => process.exit(code));
|
||||
} else if (args[0] === "upgrade") {
|
||||
upgrade();
|
||||
} else if (args[0] === "hook") {
|
||||
hookDispatch(args[1], args[2]);
|
||||
} else {
|
||||
// Default: start MCP server
|
||||
import("./server.js");
|
||||
|
||||
Reference in New Issue
Block a user