claude-ide-bridge 2.6.0 → 2.6.2

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 CHANGED
@@ -4,33 +4,57 @@
4
4
  [![CI](https://github.com/Oolab-labs/claude-ide-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/Oolab-labs/claude-ide-bridge/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- A standalone MCP bridge that gives [Claude Code](https://claude.ai/code) real IDE integration — not just file access, but **live editor state**: diagnostics as you type, go-to-definition, find-all-references, hover types, rename-symbol, set breakpoints, evaluate in the debugger, and capture what's on screen. Works with any VS Code-compatible editor (VS Code, Windsurf, Cursor) via a companion extension.
7
+ **Claude Code, but with your IDE's eyes.**
8
8
 
9
- It also exposes Git, GitHub, terminals, and code analysis tools but the core value is the LSP and debugger integration that Claude has no other way to access.
9
+ A WebSocket bridge that connects Claude Code to VS Code (and Windsurf, Cursor) so Claude can see what your IDE sees: live diagnostics, go-to-definition, find references, hover types, open files, breakpoints, debugger state. Not file access actual IDE context, the same signals a developer reads while working.
10
10
 
11
- ## How It Works
11
+ Install the companion extension, start the bridge, open Claude. That's it. Claude can now navigate your codebase the way you do, run tests, check diagnostics, commit, and create PRs — without you copy-pasting anything.
12
12
 
13
13
  ```
14
- Your Phone / Laptop Your Computer
15
- ┌──────────────┐ ┌─────────────────────────────┐
16
- │ Claude Code │───── SSH/local ─────│ Bridge Server │
17
- │ (CLI) │◄── remote control ──│ ↕ WebSocket │
18
- └──────────────┘ │ IDE Extension (VS Code) │
19
- │ ↕ Real-time state │
20
- ┌──────────────────────────────│ Your Code & Editor │
21
- │ runClaudeTask └─────────────────────────────┘
22
-
23
- ┌──────────────┐
24
- │ claude -p │ Autonomous subprocess — full tools, no approval loop
25
- │ subprocess │ Output streams back to VS Code output channel
26
- └──────────────┘
14
+ Claude Code ──── bridge ──── VS Code extension ──── your editor state
27
15
  ```
28
16
 
29
- Claude Code connects to the bridge, which connects to your IDE extension. Claude can then open files, run tests, set breakpoints, check diagnostics, commit to Git, create PRs everything a developer at the keyboard can do.
17
+ **Works from your phone.** SSH into your dev machine, send a message, watch Claude fix bugs and run tests on your home machine while you're away.
30
18
 
31
- **Use it from your phone**: SSH into your dev machine, send a message via remote control, and delegate autonomous work to a `claude -p` subprocess running on your home machine. Watch it fix bugs, run tests, and commit — then go back to sleep.
19
+ ## Pick your path
32
20
 
33
- **Autonomous task mode**: With `--claude-driver subprocess`, the bridge can spawn Claude subprocesses on demand via the `runClaudeTask` MCP tool. Tasks run in parallel (up to 10 concurrent), stream output to VS Code in real time, and can be triggered automatically by diagnostics or file saves via an automation policy.
21
+ | I want to | Go to |
22
+ |---|---|
23
+ | Get started (5 min setup) | [Quick Start](#quick-start) |
24
+ | Understand what tools are available | [Platform Docs](documents/platform-docs.md) |
25
+ | Run two IDEs in parallel | [Multi-IDE Orchestrator](#multi-ide-orchestrator) |
26
+ | Access from remote / phone | [Remote Access](docs/remote-access.md) |
27
+ | Deploy to a VPS | [Deploy](deploy/README.md) |
28
+ | Write a plugin | [Plugin Authoring](documents/plugin-authoring.md) |
29
+ | Use with Claude Desktop / Dispatch | [Session Continuity](#session-continuity) |
30
+ | Use with claude.ai web | [Custom Connector](#use-with-claudeai-web) |
31
+
32
+ ## Quick Start
33
+
34
+ **Prerequisites:** [Claude Code CLI](https://claude.ai/code), Node.js ≥ 20
35
+
36
+ ```bash
37
+ npm install -g claude-ide-bridge
38
+ cd /your/project
39
+ claude-ide-bridge init
40
+ ```
41
+
42
+ `init` installs the VS Code extension, writes a `## Claude IDE Bridge` section to your `CLAUDE.md`, and registers the bridge as a global MCP server in `~/.claude.json` — so bridge tools are available in **every** `claude` session (any directory, any IDE). That's the entire setup.
43
+
44
+ **Then start the bridge and open Claude:**
45
+
46
+ ```bash
47
+ claude-ide-bridge --watch # terminal 1 — keeps running, auto-restarts on crash
48
+ claude --ide # terminal 2 — Claude Code with IDE tools active
49
+ ```
50
+
51
+ Type `/mcp` in Claude to confirm the server is connected, then `/ide` to see open files, diagnostics, and editor state.
52
+
53
+ > **One bridge per workspace.** Each project needs its own bridge instance on its own port. If you work across multiple repos, start a separate `claude-ide-bridge --watch` in each directory.
54
+
55
+ > **Why `~/.claude.json` and not `.mcp.json`?** When VS Code, Windsurf, or Cursor launches Claude Code, it injects `--mcp-config` which overrides any project `.mcp.json`. Only `~/.claude.json` is loaded in every session regardless of how Claude Code is started. `init` writes there by design — you don't need to touch `.mcp.json`.
56
+
57
+ **Tools not showing up?** See the [troubleshooting guide](docs/troubleshooting.md).
34
58
 
35
59
  ## Multi-IDE Orchestrator
36
60
 
@@ -66,35 +90,13 @@ Use `switchWorkspace ws1` / `switchWorkspace ws2` in Claude to pin to a specific
66
90
 
67
91
  See [docs/multi-ide-review.md](docs/multi-ide-review.md) for the staged review workflow.
68
92
 
69
- ## Quick Start
70
-
71
- **Prerequisites:** [Claude Code CLI](https://claude.ai/code), Node.js ≥ 20
72
-
73
- ```bash
74
- npm install -g claude-ide-bridge
75
- cd /your/project
76
- claude-ide-bridge init
77
- ```
78
-
79
- `init` installs the VS Code extension, writes a `## Claude IDE Bridge` section to your `CLAUDE.md`, and prints the two remaining steps (env var + how to start). That's the entire setup.
80
-
81
- **Then start the bridge and open Claude:**
82
-
83
- ```bash
84
- claude-ide-bridge --watch # terminal 1 — keeps running, auto-restarts on crash
85
- claude --ide # terminal 2 — Claude Code with IDE tools active
86
- ```
87
-
88
- Type `/ide` in Claude to confirm — you'll see open files, diagnostics, and editor state.
89
-
90
- > **One bridge per workspace.** Each project needs its own bridge instance on its own port. If you work across multiple repos, start a separate `claude-ide-bridge --watch` in each directory.
91
-
92
93
  ## Documentation
93
94
 
94
95
  > **These guides are essential for setup and deployment** — not optional reading. Each covers a specific scenario you'll encounter when running the bridge beyond localhost.
95
96
 
96
97
  | Guide | What it covers |
97
98
  |-------|----------------|
99
+ | **[Troubleshooting](docs/troubleshooting.md)** | Tools not showing up, wrong config file, WSL/Windows PATH, port conflicts |
98
100
  | **[Remote Access](docs/remote-access.md)** | Production reverse proxy setup (Caddy/nginx), TLS, Streamable HTTP transport |
99
101
  | **[SSH Resilience](docs/ssh-resilience.md)** | Surviving SSH drops, tmux strategies, phone-to-VPS workflows |
100
102
  | **[IP Allowlist](docs/ip-allowlist.md)** | Firewall rules, network access control for remote bridge instances |
@@ -102,6 +104,7 @@ Type `/ide` in Claude to confirm — you'll see open files, diagnostics, and edi
102
104
  | **[Privacy Policy](docs/privacy-policy.md)** | What data the bridge handles, stores, and never transmits |
103
105
  | **[Demo Setup](docs/demo-setup.md)** | Standing up a persistent demo instance for review/testing |
104
106
  | **[Architecture Decisions](docs/adr/)** | ADRs for version numbers, reconnect guards, lock files, error model, session eviction |
107
+ | **[Release Checklist](docs/release-checklist.md)** | Pre-release gate: hardcoded count audit, doc completeness, publish steps |
105
108
 
106
109
  **Reference docs** (in [`documents/`](documents/)):
107
110
 
@@ -160,7 +163,7 @@ Requires `tmux` and the `claude` CLI to be on `PATH`.
160
163
 
161
164
  ## Claude Code Plugin
162
165
 
163
- The bridge ships as a **Claude Code plugin** with 9 skills, 3 subagents, and 7 hook events — available on the [Claude Code plugin directory](https://claude.com/plugins):
166
+ The bridge ships as a **Claude Code plugin** with 9 skills, 3 subagents, and 16 hook events — available on the [Claude Code plugin directory](https://claude.com/plugins):
164
167
 
165
168
  ```bash
166
169
  # Load the plugin
@@ -195,20 +198,29 @@ claude --plugin-dir ./claude-ide-bridge-plugin
195
198
 
196
199
  | Event | What it does |
197
200
  |-------|-------------|
198
- | `PreToolUse` on Edit/Write | Resolves relative path args to absolute before bridge tools execute |
201
+ | `PreToolUse` | Resolves relative path args to absolute before bridge tools execute |
199
202
  | `PostToolUse` on Edit/Write | Reminds Claude to check diagnostics after file edits |
200
203
  | `SessionStart` | Reports bridge status, connection, and tool count |
201
204
  | `InstructionsLoaded` | Injects live bridge status each time CLAUDE.md loads |
202
205
  | `Elicitation` | Pre-fills file/path/uri fields using the active editor |
206
+ | `ElicitationResult` | Logs user responses (or cancellations) to MCP elicitation dialogs |
207
+ | `PostCompact` | Re-injects bridge status after Claude compacts context |
203
208
  | `WorktreeCreate` | Reports bridge ↔ worktree relationship; warns about LSP limitations |
209
+ | `WorktreeRemove` | Warns that IDE state may be stale after worktree removal |
204
210
  | `SubagentStart` | Verifies bridge is alive before IDE subagents run |
211
+ | `SubagentStop` | Surfaces subagent final response summary for parent agent awareness |
212
+ | `TeammateIdle` | Reports bridge health when a team agent finishes and awaits coordination |
213
+ | `TaskCompleted` | Logs task completion summary and confirms bridge availability |
214
+ | `ConfigChange` | Warns if changed config files require a bridge restart |
215
+ | `Stop` | Logs session end and surfaces final response for automated workflows |
216
+ | `StopFailure` | Logs API errors that ended the turn; checks bridge health |
205
217
 
206
218
  ## MCP Tools
207
219
 
208
220
  The bridge exposes tools in two modes:
209
221
 
210
222
  - **Slim mode (default)** — 27 IDE-exclusive tools. Only tools that require a live VS Code extension and have no native Claude equivalent. This is what you get with `claude-ide-bridge --watch`.
211
- - **Full mode (`--full`)** — all ~95 tools, adding git, terminal, file ops, HTTP, and GitHub. Use this for large projects or workflows that rely on those integrations.
223
+ - **Full mode (`--full`)** — all ~96 tools, adding git, terminal, file ops, HTTP, and GitHub. Use this for large projects or workflows that rely on those integrations.
212
224
 
213
225
  ### Slim mode — 27 IDE tools (default)
214
226
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Aggregates ActivityLog data into an anonymized session summary for opt-in
3
+ * usage analytics. No file paths, arguments, error messages, or personal data.
4
+ *
5
+ * Plugin tool names are hashed (prefix only) to avoid leaking org-specific names.
6
+ * Built-in tool names are sent verbatim.
7
+ */
8
+ export interface ToolStat {
9
+ tool: string;
10
+ calls: number;
11
+ errors: number;
12
+ p50Ms: number;
13
+ p95Ms: number;
14
+ }
15
+ export interface AnalyticsSummary {
16
+ bridgeVersion: string;
17
+ sessionDurationMs: number;
18
+ toolStats: ToolStat[];
19
+ }
20
+ /**
21
+ * Build an anonymized summary from raw tool call entries.
22
+ * Accepts the same shape as ActivityLog.stats() plus raw duration arrays.
23
+ */
24
+ export declare function buildSummary(entries: Array<{
25
+ tool: string;
26
+ durationMs: number;
27
+ status: "success" | "error";
28
+ }>, sessionDurationMs: number, bridgeVersion: string): AnalyticsSummary;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Aggregates ActivityLog data into an anonymized session summary for opt-in
3
+ * usage analytics. No file paths, arguments, error messages, or personal data.
4
+ *
5
+ * Plugin tool names are hashed (prefix only) to avoid leaking org-specific names.
6
+ * Built-in tool names are sent verbatim.
7
+ */
8
+ import crypto from "node:crypto";
9
+ /** Known built-in tool names — sent verbatim. Anything else is treated as a plugin tool. */
10
+ const BUILTIN_TOOL_NAMES = new Set([
11
+ "getOpenEditors",
12
+ "getCurrentSelection",
13
+ "getLatestSelection",
14
+ "getDiagnostics",
15
+ "watchDiagnostics",
16
+ "getDocumentSymbols",
17
+ "getHover",
18
+ "goToDefinition",
19
+ "findReferences",
20
+ "getCallHierarchy",
21
+ "searchWorkspaceSymbols",
22
+ "getCodeActions",
23
+ "applyCodeAction",
24
+ "renameSymbol",
25
+ "openFile",
26
+ "closeTab",
27
+ "checkDocumentDirty",
28
+ "saveDocument",
29
+ "captureScreenshot",
30
+ "getBridgeStatus",
31
+ "getToolCapabilities",
32
+ "executeVSCodeCommand",
33
+ "getDebugState",
34
+ "setDebugBreakpoints",
35
+ "startDebugging",
36
+ "stopDebugging",
37
+ "evaluateInDebugger",
38
+ "readFile",
39
+ "writeFile",
40
+ "createFile",
41
+ "deleteFile",
42
+ "moveFile",
43
+ "listDirectory",
44
+ "searchFiles",
45
+ "searchAndReplace",
46
+ "runCommand",
47
+ "getGitStatus",
48
+ "getGitDiff",
49
+ "gitCommit",
50
+ "gitCheckout",
51
+ "gitPush",
52
+ "gitPull",
53
+ "gitLog",
54
+ "gitWrite",
55
+ "sendHttpRequest",
56
+ "clipboardRead",
57
+ "clipboardWrite",
58
+ "getClipboard",
59
+ "setClipboard",
60
+ "openDiff",
61
+ "runClaudeTask",
62
+ "getClaudeTaskStatus",
63
+ "cancelClaudeTask",
64
+ "listClaudeTasks",
65
+ "resumeClaudeTask",
66
+ "getAIComments",
67
+ "createGithubIssueFromAIComment",
68
+ "switchWorkspace",
69
+ "getOrchestratorStatus",
70
+ "handoffNote",
71
+ "workspaceSettings",
72
+ "getHandoffNote",
73
+ "writeHandoffNote",
74
+ "logging",
75
+ ]);
76
+ /** Returns the safe tool name to include in analytics. */
77
+ function safeToolName(tool) {
78
+ if (BUILTIN_TOOL_NAMES.has(tool))
79
+ return tool;
80
+ // Plugin tool: extract prefix (everything before first underscore) and hash it
81
+ const prefix = tool.includes("_") ? (tool.split("_")[0] ?? tool) : tool;
82
+ const hash = crypto
83
+ .createHash("sha256")
84
+ .update(prefix)
85
+ .digest("hex")
86
+ .slice(0, 8);
87
+ return `plugin:${hash}`;
88
+ }
89
+ /** Compute p50 and p95 from a sorted array of durations. */
90
+ function percentiles(sorted) {
91
+ if (sorted.length === 0)
92
+ return { p50: 0, p95: 0 };
93
+ const p50 = sorted[Math.floor(sorted.length * 0.5)] ?? 0;
94
+ const p95 = sorted[Math.floor(sorted.length * 0.95)] ?? 0;
95
+ return { p50, p95 };
96
+ }
97
+ /**
98
+ * Build an anonymized summary from raw tool call entries.
99
+ * Accepts the same shape as ActivityLog.stats() plus raw duration arrays.
100
+ */
101
+ export function buildSummary(entries, sessionDurationMs, bridgeVersion) {
102
+ // Group by safe tool name
103
+ const map = new Map();
104
+ for (const entry of entries) {
105
+ const name = safeToolName(entry.tool);
106
+ const s = map.get(name) ?? { calls: 0, errors: 0, durations: [] };
107
+ s.calls++;
108
+ if (entry.status === "error")
109
+ s.errors++;
110
+ s.durations.push(entry.durationMs);
111
+ map.set(name, s);
112
+ }
113
+ const toolStats = [];
114
+ for (const [tool, s] of map) {
115
+ const sorted = [...s.durations].sort((a, b) => a - b);
116
+ const { p50, p95 } = percentiles(sorted);
117
+ toolStats.push({
118
+ tool,
119
+ calls: s.calls,
120
+ errors: s.errors,
121
+ p50Ms: Math.round(p50),
122
+ p95Ms: Math.round(p95),
123
+ });
124
+ }
125
+ // Sort by call count descending for readability
126
+ toolStats.sort((a, b) => b.calls - a.calls);
127
+ return {
128
+ bridgeVersion,
129
+ sessionDurationMs,
130
+ toolStats,
131
+ };
132
+ }
133
+ //# sourceMappingURL=analyticsAggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyticsAggregator.js","sourceRoot":"","sources":["../src/analyticsAggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Reads and writes the opt-in analytics preference.
3
+ * Stored in ~/.claude/ide/analytics.json (respects CLAUDE_CONFIG_DIR).
4
+ * File created with 0o600 permissions (owner read/write only).
5
+ */
6
+ /** Returns the current opt-in state, or null if no preference has been set. */
7
+ export declare function getAnalyticsPref(): boolean | null;
8
+ /** Persists the opt-in preference. Creates file with 0o600 permissions. */
9
+ export declare function setAnalyticsPref(enabled: boolean): void;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Reads and writes the opt-in analytics preference.
3
+ * Stored in ~/.claude/ide/analytics.json (respects CLAUDE_CONFIG_DIR).
4
+ * File created with 0o600 permissions (owner read/write only).
5
+ */
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ function prefsPath() {
10
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
11
+ return path.join(claudeDir, "ide", "analytics.json");
12
+ }
13
+ /** Returns the current opt-in state, or null if no preference has been set. */
14
+ export function getAnalyticsPref() {
15
+ const p = prefsPath();
16
+ try {
17
+ const raw = fs.readFileSync(p, "utf-8");
18
+ const obj = JSON.parse(raw);
19
+ if (typeof obj === "object" &&
20
+ obj !== null &&
21
+ "enabled" in obj &&
22
+ typeof obj.enabled === "boolean") {
23
+ return obj.enabled;
24
+ }
25
+ return null;
26
+ }
27
+ catch (err) {
28
+ const code = err.code;
29
+ if (code === "ENOENT")
30
+ return null;
31
+ return null;
32
+ }
33
+ }
34
+ /** Persists the opt-in preference. Creates file with 0o600 permissions. */
35
+ export function setAnalyticsPref(enabled) {
36
+ const p = prefsPath();
37
+ const dir = path.dirname(p);
38
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
39
+ const content = {
40
+ enabled,
41
+ decidedAt: new Date().toISOString(),
42
+ };
43
+ // Write to temp file then rename for atomicity
44
+ const tmp = `${p}.tmp`;
45
+ fs.writeFileSync(tmp, `${JSON.stringify(content, null, 2)}\n`, {
46
+ mode: 0o600,
47
+ });
48
+ fs.renameSync(tmp, p);
49
+ }
50
+ //# sourceMappingURL=analyticsPrefs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyticsPrefs.js","sourceRoot":"","sources":["../src/analyticsPrefs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,SAAS,SAAS;IAChB,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB;IAC9B,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QACvC,IACE,OAAO,GAAG,KAAK,QAAQ;YACvB,GAAG,KAAK,IAAI;YACZ,SAAS,IAAI,GAAG;YAChB,OAAQ,GAAiB,CAAC,OAAO,KAAK,SAAS,EAC/C,CAAC;YACD,OAAQ,GAAiB,CAAC,OAAO,CAAC;QACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;IACtB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,MAAM,OAAO,GAAc;QACzB,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,+CAA+C;IAC/C,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;IACvB,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC7D,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Sends an anonymized analytics summary to the usage endpoint.
3
+ * - Fire-and-forget is NOT used: callers must await this with a timeout race.
4
+ * - All errors are caught and swallowed — telemetry must never affect bridge operation.
5
+ * - Endpoint is hardcoded (not runtime-configurable) to prevent redirect attacks.
6
+ */
7
+ import type { AnalyticsSummary } from "./analyticsAggregator.js";
8
+ /**
9
+ * Sends the summary to the analytics endpoint.
10
+ * Resolves (never rejects) — all errors are swallowed silently.
11
+ */
12
+ export declare function sendAnalytics(summary: AnalyticsSummary): Promise<void>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Sends an anonymized analytics summary to the usage endpoint.
3
+ * - Fire-and-forget is NOT used: callers must await this with a timeout race.
4
+ * - All errors are caught and swallowed — telemetry must never affect bridge operation.
5
+ * - Endpoint is hardcoded (not runtime-configurable) to prevent redirect attacks.
6
+ */
7
+ /** Hardcoded endpoint — not configurable at runtime. */
8
+ const ANALYTICS_ENDPOINT = "https://analytics.claude-ide-bridge.dev/v1/usage";
9
+ const SEND_TIMEOUT_MS = 3000;
10
+ /**
11
+ * Sends the summary to the analytics endpoint.
12
+ * Resolves (never rejects) — all errors are swallowed silently.
13
+ */
14
+ export async function sendAnalytics(summary) {
15
+ try {
16
+ const controller = new AbortController();
17
+ const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
18
+ try {
19
+ await fetch(ANALYTICS_ENDPOINT, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify(summary),
23
+ signal: controller.signal,
24
+ });
25
+ }
26
+ finally {
27
+ clearTimeout(timer);
28
+ }
29
+ }
30
+ catch {
31
+ // Silently swallow all errors — telemetry must never surface to the user
32
+ }
33
+ }
34
+ //# sourceMappingURL=analyticsSend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyticsSend.js","sourceRoot":"","sources":["../src/analyticsSend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,wDAAwD;AACxD,MAAM,kBAAkB,GAAG,kDAAkD,CAAC;AAE9E,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAyB;IAC3D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,eAAe,CAAC,CAAC;QACpE,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,kBAAkB,EAAE;gBAC9B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;gBAC7B,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;AACH,CAAC"}
package/dist/bridge.d.ts CHANGED
@@ -39,6 +39,7 @@ export declare class Bridge {
39
39
  /** Debounced tools/list_changed notification — max one per 2 seconds. */
40
40
  private sendListChanged;
41
41
  private _buildCheckpoint;
42
+ private buildInstructions;
42
43
  private cleanupSession;
43
44
  /** Returns the port the bridge is listening on (0 before start()). */
44
45
  getPort(): number;
package/dist/bridge.js CHANGED
@@ -3,6 +3,9 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { WebSocket } from "ws";
5
5
  import { ActivityLog } from "./activityLog.js";
6
+ import { buildSummary } from "./analyticsAggregator.js";
7
+ import { getAnalyticsPref } from "./analyticsPrefs.js";
8
+ import { sendAnalytics } from "./analyticsSend.js";
6
9
  import { AutomationHooks, loadPolicy } from "./automation.js";
7
10
  import { createDriver } from "./claudeDriver.js";
8
11
  import { ClaudeOrchestrator } from "./claudeOrchestrator.js";
@@ -23,6 +26,7 @@ import { registerAllTools } from "./tools/index.js";
23
26
  import { cleanupTempDirs } from "./tools/openDiff.js";
24
27
  import { resolveFilePath } from "./tools/utils.js";
25
28
  import { McpTransport } from "./transport.js";
29
+ import { PACKAGE_VERSION } from "./version.js";
26
30
  const SHUTDOWN_TIMEOUT_MS = 5000;
27
31
  const MAX_SESSIONS = 5;
28
32
  let globalHandlersRegistered = false;
@@ -124,6 +128,7 @@ export class Bridge {
124
128
  transport.setActivityLog(this.activityLog);
125
129
  transport.setToolRateLimit(this.config.toolRateLimit);
126
130
  transport.setExtensionConnectedFn(() => this.extensionClient.isConnected());
131
+ transport.setInstructions(this.buildInstructions());
127
132
  transport.onInitialized = () => {
128
133
  if (this.pendingListChanged && ws.readyState === WebSocket.OPEN) {
129
134
  McpTransport.sendNotification(ws, "notifications/tools/list_changed", undefined, this.logger);
@@ -350,6 +355,16 @@ export class Bridge {
350
355
  gracePeriodMs: this.config.gracePeriodMs,
351
356
  };
352
357
  }
358
+ buildInstructions() {
359
+ const lines = [`claude-ide-bridge v${PACKAGE_VERSION}`];
360
+ lines.push("");
361
+ lines.push("CONTEXT PLATFORM:");
362
+ lines.push(" Use ctx tools for issue/PR/error context — not gh or githubViewPR.");
363
+ lines.push(" ctxGetTaskContext(ref) — unified context for any issue, PR, commit, or error");
364
+ lines.push(" ctxQueryTraces(query) — search past decisions");
365
+ lines.push(" ctxSaveTrace(ref, problem, solution) — record fix after resolving a task");
366
+ return lines.join("\n");
367
+ }
353
368
  cleanupSession(id) {
354
369
  const session = this.sessions.get(id);
355
370
  if (!session)
@@ -523,7 +538,7 @@ export class Bridge {
523
538
  // 3b. Set up Streamable HTTP transport handler (POST/GET/DELETE /mcp)
524
539
  this.httpMcpHandler = new StreamableHttpHandler(this.config, probes, this.extensionClient, this.activityLog, this.fileLock, this.sessions, this.orchestrator, this.logger, () => this.pluginWatcher?.getTools() ?? this.pluginTools, () => this.pluginWatcher, this.oauthServer
525
540
  ? (token) => this.oauthServer?.resolveBearerScope(token) ?? null
526
- : null);
541
+ : null, this.buildInstructions());
527
542
  this.server.httpMcpHandler = (req, res) => this.httpMcpHandler?.handle(req, res) ?? Promise.resolve();
528
543
  // 3. Check for stale lock files
529
544
  this.lockFile.cleanStale();
@@ -684,6 +699,23 @@ export class Bridge {
684
699
  ? `, ${totalErrors} error${totalErrors === 1 ? "" : "s"}`
685
700
  : "";
686
701
  this.logger.info(`Shutdown complete — ${totalSessions} session${totalSessions === 1 ? "" : "s"}, ${totalCalls} tool call${totalCalls === 1 ? "" : "s"}${shutdownErrorPart}`);
702
+ // Send analytics if opted in — awaited with 2s timeout so it completes before process.exit()
703
+ const analyticsOn = this.config.analyticsEnabled !== null
704
+ ? this.config.analyticsEnabled
705
+ : getAnalyticsPref();
706
+ if (analyticsOn === true && totalSessions > 0) {
707
+ try {
708
+ const entries = this.activityLog.query({ last: 500 });
709
+ const summary = buildSummary(entries, maxDurationMs, PACKAGE_VERSION);
710
+ await Promise.race([
711
+ sendAnalytics(summary),
712
+ new Promise((resolve) => setTimeout(resolve, 2000)),
713
+ ]);
714
+ }
715
+ catch {
716
+ // Swallow all errors — analytics must never affect shutdown
717
+ }
718
+ }
687
719
  // Send aggregate session-end notification to the extension before disconnecting
688
720
  if (totalSessions > 0) {
689
721
  this.extensionClient.notifyClaudeConnectionState(false, {