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 +57 -45
- package/dist/analyticsAggregator.d.ts +28 -0
- package/dist/analyticsAggregator.js +133 -0
- package/dist/analyticsAggregator.js.map +1 -0
- package/dist/analyticsPrefs.d.ts +9 -0
- package/dist/analyticsPrefs.js +50 -0
- package/dist/analyticsPrefs.js.map +1 -0
- package/dist/analyticsSend.d.ts +12 -0
- package/dist/analyticsSend.js +34 -0
- package/dist/analyticsSend.js.map +1 -0
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.js +33 -1
- package/dist/bridge.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +238 -37
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/orchestratorBridge.js +14 -0
- package/dist/orchestrator/orchestratorBridge.js.map +1 -1
- package/dist/streamableHttp.d.ts +5 -1
- package/dist/streamableHttp.js +7 -1
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/getAIComments.d.ts +22 -0
- package/dist/tools/getAIComments.js +65 -0
- package/dist/tools/getAIComments.js.map +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,33 +4,57 @@
|
|
|
4
4
|
[](https://github.com/Oolab-labs/claude-ide-bridge/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Claude Code, but with your IDE's eyes.**
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
## Pick your path
|
|
32
20
|
|
|
33
|
-
|
|
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
|
|
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`
|
|
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 ~
|
|
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, {
|