context-lens 0.5.4 → 0.6.0

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.
Files changed (69) hide show
  1. package/README.md +32 -4
  2. package/dist/analysis/ingest.d.ts.map +1 -1
  3. package/dist/analysis/ingest.js +8 -2
  4. package/dist/analysis/ingest.js.map +1 -1
  5. package/dist/cli-utils.d.ts +2 -0
  6. package/dist/cli-utils.d.ts.map +1 -1
  7. package/dist/cli-utils.js +49 -3
  8. package/dist/cli-utils.js.map +1 -1
  9. package/dist/cli.js +156 -14
  10. package/dist/cli.js.map +1 -1
  11. package/dist/core/conversation.d.ts +1 -0
  12. package/dist/core/conversation.d.ts.map +1 -1
  13. package/dist/core/conversation.js +12 -1
  14. package/dist/core/conversation.js.map +1 -1
  15. package/dist/core/models.d.ts +7 -1
  16. package/dist/core/models.d.ts.map +1 -1
  17. package/dist/core/models.js +95 -13
  18. package/dist/core/models.js.map +1 -1
  19. package/dist/core/routing.d.ts +0 -7
  20. package/dist/core/routing.d.ts.map +1 -1
  21. package/dist/core/routing.js +18 -4
  22. package/dist/core/routing.js.map +1 -1
  23. package/dist/core/session-format.d.ts.map +1 -1
  24. package/dist/proxy/capture.d.ts +3 -25
  25. package/dist/proxy/capture.d.ts.map +1 -1
  26. package/dist/proxy/capture.js +1 -3
  27. package/dist/proxy/capture.js.map +1 -1
  28. package/dist/proxy/config.d.ts +1 -2
  29. package/dist/proxy/config.d.ts.map +1 -1
  30. package/dist/proxy/config.js +0 -1
  31. package/dist/proxy/config.js.map +1 -1
  32. package/dist/proxy/server.d.ts +8 -19
  33. package/dist/proxy/server.d.ts.map +1 -1
  34. package/dist/proxy/server.js +97 -44
  35. package/dist/proxy/server.js.map +1 -1
  36. package/dist/server/api.d.ts.map +1 -1
  37. package/dist/server/api.js +62 -4
  38. package/dist/server/api.js.map +1 -1
  39. package/dist/server/store.d.ts +7 -0
  40. package/dist/server/store.d.ts.map +1 -1
  41. package/dist/server/store.js +73 -0
  42. package/dist/server/store.js.map +1 -1
  43. package/dist/server/tags-store.d.ts +33 -0
  44. package/dist/server/tags-store.d.ts.map +1 -0
  45. package/dist/server/tags-store.js +150 -0
  46. package/dist/server/tags-store.js.map +1 -0
  47. package/dist/types.d.ts +4 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/version.generated.d.ts +1 -1
  50. package/dist/version.generated.js +1 -1
  51. package/mitm_addon.py +18 -0
  52. package/package.json +4 -3
  53. package/ui/dist/assets/index-CC_AtJBy.css +1 -0
  54. package/ui/dist/assets/index-If9FfL3y.js +36 -0
  55. package/ui/dist/index.html +2 -2
  56. package/dist/proxy/forward.d.ts +0 -20
  57. package/dist/proxy/forward.d.ts.map +0 -1
  58. package/dist/proxy/forward.js +0 -210
  59. package/dist/proxy/forward.js.map +0 -1
  60. package/dist/proxy/headers.d.ts +0 -16
  61. package/dist/proxy/headers.d.ts.map +0 -1
  62. package/dist/proxy/headers.js +0 -37
  63. package/dist/proxy/headers.js.map +0 -1
  64. package/dist/proxy/routing.d.ts +0 -45
  65. package/dist/proxy/routing.d.ts.map +0 -1
  66. package/dist/proxy/routing.js +0 -139
  67. package/dist/proxy/routing.js.map +0 -1
  68. package/ui/dist/assets/index-D1eb5Vsp.css +0 -1
  69. package/ui/dist/assets/index-JtXLWNzT.js +0 -35
package/README.md CHANGED
@@ -41,9 +41,10 @@ context-lens doctor # check ports, certs, background state
41
41
  context-lens background start # start detached proxy + UI
42
42
  context-lens background status
43
43
  context-lens background stop
44
+ context-lens stop # shorthand for background stop
44
45
  ```
45
46
 
46
- Aliases: `cc` → `claude`, `cx` → `codex`, `cpi` → `pi`, `gm` `gemini`.
47
+ Aliases: `cc` → `claude`, `cx` → `codex`, `gm` → `gemini`. For `pi`, add `alias cpi='context-lens pi'` to your shell rc.
47
48
 
48
49
  ## Docker
49
50
 
@@ -128,6 +129,7 @@ services:
128
129
  - **Context diff:** turn-to-turn delta showing what grew, shrank, or appeared
129
130
  - **Findings:** flags large tool results, unused tool definitions, context overflow risk, compaction events
130
131
  - **Auto-detection:** recognizes Claude Code, Codex, aider, Pi, and others by source tag or system prompt
132
+ - **Session tagging:** label sessions with custom tags, filter the session list by tag
131
133
  - **LHAR export:** download session data as LHAR (LLM HTTP Archive) format ([doc](docs/LHAR.md))
132
134
  - **State persistence:** data survives restarts; delete individual sessions or reset all from the UI
133
135
  - **Streaming support:** passes through SSE chunks in real-time
@@ -175,14 +177,29 @@ If you prefer to configure it manually, set `baseUrl` in `~/.pi/agent/models.jso
175
177
  }
176
178
  ```
177
179
 
178
- ### OpenAI-Compatible Endpoints
180
+ ### OpenCode
181
+
182
+ OpenCode connects to multiple providers simultaneously over HTTPS. Use `context-lens opencode` — it routes all traffic through mitmproxy so every provider call is captured regardless of which model is active:
183
+
184
+ ```bash
185
+ pipx install mitmproxy
186
+ context-lens opencode
187
+ ```
179
188
 
180
- Many providers expose OpenAI-compatible APIs (OpenRouter, Together, Groq, Fireworks, Ollama, vLLM, OpenCode Zen, etc.). Override the upstream URL to point at your provider:
189
+ If you only use OpenCode with a single OpenAI-compatible endpoint (e.g. OpenCode Zen), you can also use the base URL override approach instead:
181
190
 
182
191
  ```bash
183
192
  UPSTREAM_OPENAI_URL=https://opencode.ai/zen/v1 context-lens -- opencode "prompt"
184
193
  ```
185
194
 
195
+ ### OpenAI-Compatible Endpoints
196
+
197
+ Many providers expose OpenAI-compatible APIs (OpenRouter, Together, Groq, Fireworks, Ollama, vLLM, etc.). Override the upstream URL to point at your provider:
198
+
199
+ ```bash
200
+ UPSTREAM_OPENAI_URL=https://my-provider.com/v1 context-lens -- my-tool "prompt"
201
+ ```
202
+
186
203
  `UPSTREAM_OPENAI_URL` is global: all OpenAI-format requests go to that upstream. Use separate proxy instances if you need to hit multiple endpoints simultaneously.
187
204
 
188
205
  ### Codex Subscription Mode
@@ -196,6 +213,17 @@ context-lens codex
196
213
 
197
214
  If Codex fails with certificate trust errors, install/trust the mitmproxy CA certificate (`~/.mitmproxy/mitmproxy-ca-cert.pem`) for your environment.
198
215
 
216
+ ### Pi with ChatGPT Subscription Models
217
+
218
+ Pi's `openai-codex` provider (e.g. `gpt-5.2-codex`) connects directly to `chatgpt.com` and cannot be redirected via base URL overrides. Use the `--mitm` flag to route through mitmproxy instead:
219
+
220
+ ```bash
221
+ pipx install mitmproxy
222
+ context-lens pi --mitm
223
+ ```
224
+
225
+ Standard OpenAI API models in Pi work fine without `--mitm`.
226
+
199
227
  ## How It Works
200
228
 
201
229
  Context Lens sits between your coding tool and the LLM API, capturing requests in transit. It has two parts: a **proxy** and an **analysis server**.
@@ -208,7 +236,7 @@ Tool ─HTTP─▶ Proxy (:4040) ─HTTPS─▶ api.anthropic.com / api.open
208
236
  Analysis Server (:4041) → Web UI
209
237
  ```
210
238
 
211
- The **proxy** forwards requests to the LLM API and writes each request/response pair to disk. It has **zero external dependencies** (only Node.js built-ins), so you can read the entire proxy source and verify it does nothing unexpected with your API keys.
239
+ The **proxy** forwards requests to the LLM API and writes each request/response pair to disk. It is built on [`@contextio/proxy`](https://github.com/larsderidder/contextio), a minimal package with no external dependencies, so you can read the entire proxy source and verify it does nothing unexpected with your API keys.
212
240
 
213
241
  The **analysis server** picks up those captures, parses request bodies, estimates tokens, groups requests into conversations, computes composition breakdowns, calculates costs, and scores context health. It serves the web UI and API.
214
242
 
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/analysis/ingest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAGhD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAuEtE"}
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/analysis/ingest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAGhD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CA+EtE"}
@@ -12,7 +12,9 @@ export function ingestCapture(store, capture) {
12
12
  const { provider, apiFormat, requestBody, responseBody } = capture;
13
13
  // Build contextInfo from the request body
14
14
  let contextInfo;
15
- if (requestBody) {
15
+ if (requestBody &&
16
+ typeof requestBody === "object" &&
17
+ !Array.isArray(requestBody)) {
16
18
  const body = { ...requestBody };
17
19
  // Gemini: model is in the URL path, not in the body
18
20
  if (apiFormat === "gemini" && !body.model) {
@@ -70,6 +72,10 @@ export function ingestCapture(store, capture) {
70
72
  responseHeaders: capture.responseHeaders,
71
73
  };
72
74
  // Feed into the store (which handles fingerprinting, scoring, etc.)
73
- store.storeRequest(contextInfo, responseData, capture.source, requestBody ?? undefined, meta, capture.requestHeaders, capture.sessionId ?? null);
75
+ store.storeRequest(contextInfo, responseData, capture.source, requestBody &&
76
+ typeof requestBody === "object" &&
77
+ !Array.isArray(requestBody)
78
+ ? requestBody
79
+ : undefined, meta, capture.requestHeaders, capture.sessionId ?? null);
74
80
  }
75
81
  //# sourceMappingURL=ingest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/analysis/ingest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK9D;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,OAAoB;IAC9D,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEnE,0CAA0C;IAC1C,IAAI,WAAwB,CAAC;IAC7B,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC;QAChC,oDAAoD;QACpD,IAAI,SAAS,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC5D,IAAI,UAAU;gBAAE,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,WAAW,GAAG,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,6CAA6C;QAC7C,WAAW,GAAG;YACZ,QAAQ;YACR,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,SAAS;YAChB,YAAY,EAAE,CAAC;YACf,WAAW,EAAE,CAAC;YACd,cAAc,EAAE,cAAc,CAAC,YAAY,CAAC;YAC5C,WAAW,EAAE,cAAc,CAAC,YAAY,CAAC;YACzC,aAAa,EAAE,EAAE;YACjB,KAAK,EAAE,EAAE;YACT,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC;oBACxC,MAAM,EAAE,cAAc,CAAC,YAAY,CAAC;iBACrC;aACF;SACF,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,IAAI,YAA0B,CAAC;IAC/B,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;QAChC,YAAY,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,MAAM,IAAI,GAAgB;QACxB,UAAU,EAAE,OAAO,CAAC,cAAc;QAClC,OAAO,EAAE;YACP,GAAG,OAAO,CAAC,OAAO;YAClB,iBAAiB,EAAE,IAAI;SACxB;QACD,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;KACzC,CAAC;IAEF,oEAAoE;IACpE,KAAK,CAAC,YAAY,CAChB,WAAW,EACX,YAAY,EACZ,OAAO,CAAC,MAAM,EACd,WAAW,IAAI,SAAS,EACxB,IAAI,EACJ,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,SAAS,IAAI,IAAI,CAC1B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/analysis/ingest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK9D;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,OAAoB;IAC9D,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEnE,0CAA0C;IAC1C,IAAI,WAAwB,CAAC;IAC7B,IACE,WAAW;QACX,OAAO,WAAW,KAAK,QAAQ;QAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAC3B,CAAC;QACD,MAAM,IAAI,GAAG,EAAE,GAAI,WAAuC,EAAE,CAAC;QAC7D,oDAAoD;QACpD,IAAI,SAAS,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;YAC5D,IAAI,UAAU;gBAAE,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,WAAW,GAAG,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,6CAA6C;QAC7C,WAAW,GAAG;YACZ,QAAQ;YACR,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,SAAS;YAChB,YAAY,EAAE,CAAC;YACf,WAAW,EAAE,CAAC;YACd,cAAc,EAAE,cAAc,CAAC,YAAY,CAAC;YAC5C,WAAW,EAAE,cAAc,CAAC,YAAY,CAAC;YACzC,aAAa,EAAE,EAAE;YACjB,KAAK,EAAE,EAAE;YACT,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC;oBACxC,MAAM,EAAE,cAAc,CAAC,YAAY,CAAC;iBACrC;aACF;SACF,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,IAAI,YAA0B,CAAC;IAC/B,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;QAChC,YAAY,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,MAAM,IAAI,GAAgB;QACxB,UAAU,EAAE,OAAO,CAAC,cAAc;QAClC,OAAO,EAAE;YACP,GAAG,OAAO,CAAC,OAAO;YAClB,iBAAiB,EAAE,IAAI;SACxB;QACD,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;KACzC,CAAC;IAEF,oEAAoE;IACpE,KAAK,CAAC,YAAY,CAChB,WAAW,EACX,YAAY,EACZ,OAAO,CAAC,MAAM,EACd,WAAW;QACT,OAAO,WAAW,KAAK,QAAQ;QAC/B,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;QAC3B,CAAC,CAAE,WAAmC;QACtC,CAAC,CAAC,SAAS,EACb,IAAI,EACJ,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,SAAS,IAAI,IAAI,CAC1B,CAAC;AACJ,CAAC"}
@@ -5,6 +5,7 @@ export interface ParsedCliArgs {
5
5
  noOpen: boolean;
6
6
  noUi: boolean;
7
7
  noUpdateCheck: boolean;
8
+ useMitm: boolean;
8
9
  privacyLevel?: string;
9
10
  commandName?: string;
10
11
  commandArguments: string[];
@@ -19,6 +20,7 @@ export declare const CLI_CONSTANTS: {
19
20
  readonly MITM_PORT: 8080;
20
21
  readonly MITM_PROXY_URL: "http://localhost:8080";
21
22
  readonly PI_AGENT_DIR_PREFIX: "/tmp/context-lens-pi-agent-";
23
+ readonly BRYTI_DATA_DIR_PREFIX: "/tmp/context-lens-bryti-";
22
24
  readonly COMMAND_ALIASES: Record<string, string>;
23
25
  readonly KNOWN_PRIVACY_LEVELS: readonly ["minimal", "standard", "full"];
24
26
  readonly MITM_ADDON_PATH: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cli-utils.d.ts","sourceRoot":"","sources":["../src/cli-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmB7C,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4DD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAY1D;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAiH1D;AAED,wBAAgB,cAAc,IAAI,MAAM,CA4DvC;AAGD,eAAO,MAAM,aAAa;;;;;;;;CAShB,CAAC"}
1
+ {"version":3,"file":"cli-utils.d.ts","sourceRoot":"","sources":["../src/cli-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqB7C,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsFD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAY1D;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CA2H1D;AAED,wBAAgB,cAAc,IAAI,MAAM,CAmEvC;AAGD,eAAO,MAAM,aAAa;;;;;;;;;CAUhB,CAAC"}
package/dist/cli-utils.js CHANGED
@@ -8,10 +8,12 @@ const PROXY_URL = "http://localhost:4040";
8
8
  const MITM_PORT = 8080;
9
9
  const MITM_PROXY_URL = `http://localhost:${MITM_PORT}`;
10
10
  const PI_AGENT_DIR_PREFIX = "/tmp/context-lens-pi-agent-";
11
+ const BRYTI_DATA_DIR_PREFIX = "/tmp/context-lens-bryti-";
11
12
  const COMMAND_ALIASES = {
12
13
  cc: "claude",
13
14
  cx: "codex",
14
15
  gm: "gemini",
16
+ oc: "opencode",
15
17
  };
16
18
  const KNOWN_PRIVACY_LEVELS = ["minimal", "standard", "full"];
17
19
  function isPrivacyLevel(value) {
@@ -49,6 +51,19 @@ const TOOL_CONFIG = {
49
51
  serverEnv: {},
50
52
  needsMitm: false,
51
53
  },
54
+ opencode: {
55
+ // OpenCode connects directly to each provider's official API over HTTPS
56
+ // and cannot be redirected via base URL env vars alone when using multiple
57
+ // providers simultaneously. We use mitmproxy as a forward HTTPS proxy so
58
+ // all provider traffic is captured regardless of which model is active.
59
+ childEnv: {
60
+ https_proxy: MITM_PROXY_URL,
61
+ SSL_CERT_FILE: "", // filled in by cli.ts with mitmproxy CA cert path
62
+ },
63
+ extraArgs: [],
64
+ serverEnv: {},
65
+ needsMitm: true,
66
+ },
52
67
  gemini: {
53
68
  childEnv: {
54
69
  GOOGLE_GEMINI_BASE_URL: `${PROXY_URL}/gemini/`, // API-key auth path
@@ -69,6 +84,19 @@ const TOOL_CONFIG = {
69
84
  serverEnv: {},
70
85
  needsMitm: false,
71
86
  },
87
+ bryti: {
88
+ // Bryti reads base_url from its config.yml, not env vars. We point it at
89
+ // a temporary data dir where cli.ts writes a proxy-aware config.yml copy.
90
+ // run.sh handles dev mode (tsx) vs prod (node dist/cli.js) automatically
91
+ // and restarts on crash, matching normal bryti dev workflow.
92
+ childEnv: {
93
+ BRYTI_DATA_DIR: BRYTI_DATA_DIR_PREFIX,
94
+ },
95
+ extraArgs: [],
96
+ serverEnv: {},
97
+ needsMitm: false,
98
+ executable: "./run.sh",
99
+ },
72
100
  };
73
101
  export function getToolConfig(toolName) {
74
102
  return (TOOL_CONFIG[toolName] || {
@@ -90,6 +118,7 @@ export function parseCliArgs(args) {
90
118
  let noOpen = false;
91
119
  let noUi = false;
92
120
  let noUpdateCheck = false;
121
+ let useMitm = false;
93
122
  let privacyLevel;
94
123
  let explicitSeparator = false;
95
124
  let commandStartIndex = -1;
@@ -124,6 +153,10 @@ export function parseCliArgs(args) {
124
153
  noUpdateCheck = true;
125
154
  continue;
126
155
  }
156
+ if (arg === "--mitm") {
157
+ useMitm = true;
158
+ continue;
159
+ }
127
160
  if (arg === "--privacy") {
128
161
  if (i + 1 >= args.length) {
129
162
  return {
@@ -132,6 +165,7 @@ export function parseCliArgs(args) {
132
165
  noOpen,
133
166
  noUi,
134
167
  noUpdateCheck,
168
+ useMitm,
135
169
  commandArguments: [],
136
170
  error: "Error: Missing value for --privacy. Expected one of: minimal, standard, full",
137
171
  };
@@ -150,6 +184,7 @@ export function parseCliArgs(args) {
150
184
  noOpen,
151
185
  noUi,
152
186
  noUpdateCheck,
187
+ useMitm,
153
188
  commandArguments: [],
154
189
  error: `Error: Unknown option '${arg}'. Run 'context-lens --help' for usage.`,
155
190
  };
@@ -161,6 +196,7 @@ export function parseCliArgs(args) {
161
196
  noOpen,
162
197
  noUi,
163
198
  noUpdateCheck,
199
+ useMitm,
164
200
  commandArguments: [],
165
201
  error: `Error: Invalid privacy level '${privacyLevel}'. Must be one of: ${KNOWN_PRIVACY_LEVELS.join(", ")}`,
166
202
  };
@@ -175,6 +211,7 @@ export function parseCliArgs(args) {
175
211
  noOpen,
176
212
  noUi,
177
213
  noUpdateCheck,
214
+ useMitm,
178
215
  privacyLevel,
179
216
  commandArguments: [],
180
217
  error: "Error: No command specified after --",
@@ -186,6 +223,7 @@ export function parseCliArgs(args) {
186
223
  noOpen,
187
224
  noUi,
188
225
  noUpdateCheck,
226
+ useMitm,
189
227
  privacyLevel,
190
228
  commandName,
191
229
  commandArguments,
@@ -200,16 +238,20 @@ export function formatHelpText() {
200
238
  " context-lens [global-options] -- [command] [args...]",
201
239
  " context-lens [global-options] (no command = standalone mode)",
202
240
  " context-lens doctor",
241
+ " context-lens stop",
203
242
  " context-lens background <start|stop|status> [--no-ui]",
204
243
  " context-lens analyze <session.lhar> [options]",
205
244
  "",
206
245
  "Examples:",
207
246
  " context-lens claude",
208
247
  " context-lens codex",
248
+ " context-lens opencode",
209
249
  " context-lens gm",
250
+ " context-lens bryti",
210
251
  " context-lens --privacy=minimal aider --model claude-sonnet-4",
211
252
  " context-lens -- python my_agent.py",
212
253
  " context-lens doctor",
254
+ " context-lens stop",
213
255
  " context-lens background start --no-ui",
214
256
  " context-lens analyze ~/.context-lens/data/claude-abc123.lhar",
215
257
  " context-lens analyze session.lhar --json --main-only",
@@ -222,14 +264,16 @@ export function formatHelpText() {
222
264
  " --no-open Don't auto-open http://localhost:4041",
223
265
  " --no-ui Run proxy only (no analysis/web UI server)",
224
266
  " --no-update-check Skip npm update check for this run",
267
+ " --mitm Use mitmproxy for interception instead of base URL override (pi only)",
225
268
  "",
226
269
  "Command aliases:",
227
270
  " cc -> claude",
228
271
  " cx -> codex",
229
272
  " gm -> gemini",
273
+ " oc -> opencode",
230
274
  "",
231
- "Binary aliases:",
232
- " picl Equivalent to 'context-lens pi'",
275
+ "Shell alias (add to ~/.zshrc or ~/.bashrc):",
276
+ " alias cpi='context-lens pi'",
233
277
  "",
234
278
  "Environment variables:",
235
279
  " UPSTREAM_OPENAI_URL Override OpenAI upstream (for OpenAI-compatible APIs)",
@@ -238,7 +282,8 @@ export function formatHelpText() {
238
282
  "",
239
283
  "Notes:",
240
284
  " - No command starts standalone mode (proxy + analysis/web UI by default).",
241
- " - 'codex' uses mitmproxy for HTTPS interception (requires mitmproxy; install: pipx install mitmproxy).",
285
+ " - 'codex' and 'opencode' use mitmproxy for HTTPS interception (requires mitmproxy; install: pipx install mitmproxy).",
286
+ " - 'pi --mitm' uses mitmproxy for full interception, useful for subscription-based models (openai-codex provider).",
242
287
  " - 'doctor' is a local diagnostics command.",
243
288
  " - 'background' manages detached proxy/web-ui processes.",
244
289
  " - 'analyze' reads an .lhar file and prints session statistics.",
@@ -258,6 +303,7 @@ export const CLI_CONSTANTS = {
258
303
  MITM_PORT,
259
304
  MITM_PROXY_URL,
260
305
  PI_AGENT_DIR_PREFIX,
306
+ BRYTI_DATA_DIR_PREFIX,
261
307
  COMMAND_ALIASES,
262
308
  KNOWN_PRIVACY_LEVELS,
263
309
  // Resolved relative to compiled output (dist/ or dist-test/), matching cli.ts behavior.
@@ -1 +1 @@
1
- {"version":3,"file":"cli-utils.js","sourceRoot":"","sources":["../src/cli-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,sHAAsH;AACtH,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAC1C,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,cAAc,GAAG,oBAAoB,SAAS,EAAE,CAAC;AACvD,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;AAC1D,MAAM,eAAe,GAA2B;IAC9C,EAAE,EAAE,QAAQ;IACZ,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,QAAQ;CACb,CAAC;AACF,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAU,CAAC;AAetE,SAAS,cAAc,CAAC,KAAa;IACnC,OAAQ,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,WAAW,GAA+B;IAC9C,MAAM,EAAE;QACN,QAAQ,EAAE,EAAE,kBAAkB,EAAE,GAAG,SAAS,SAAS,EAAE;QACvD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;KACjB;IACD,KAAK,EAAE;QACL,6DAA6D;QAC7D,uEAAuE;QACvE,wEAAwE;QACxE,EAAE;QACF,kEAAkE;QAClE,8DAA8D;QAC9D,gCAAgC;QAChC,QAAQ,EAAE;YACR,WAAW,EAAE,cAAc;YAC3B,aAAa,EAAE,EAAE,EAAE,kDAAkD;SACtE;QACD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,IAAI;KAChB;IACD,KAAK,EAAE;QACL,QAAQ,EAAE;YACR,kBAAkB,EAAE,GAAG,SAAS,QAAQ;YACxC,eAAe,EAAE,GAAG,SAAS,QAAQ;SACtC;QACD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;KACjB;IACD,MAAM,EAAE;QACN,QAAQ,EAAE;YACR,sBAAsB,EAAE,GAAG,SAAS,UAAU,EAAE,oBAAoB;YACpE,sBAAsB,EAAE,GAAG,SAAS,UAAU,EAAE,sBAAsB;YACtE,oBAAoB,EAAE,GAAG,SAAS,SAAS,EAAE,0BAA0B;SACxE;QACD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;KACjB;IACD,EAAE,EAAE;QACF,0EAA0E;QAC1E,2DAA2D;QAC3D,QAAQ,EAAE;YACR,mBAAmB,EAAE,mBAAmB;SACzC;QACD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;KACjB;CACF,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,CACL,WAAW,CAAC,QAAQ,CAAC,IAAI;QACvB,QAAQ,EAAE;YACR,kBAAkB,EAAE,GAAG,SAAS,IAAI,QAAQ,EAAE;YAC9C,eAAe,EAAE,GAAG,SAAS,IAAI,QAAQ,EAAE;SAC5C;QACD,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,EAAE;QACb,SAAS,EAAE,KAAK;KACjB,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,OAAO,eAAe,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,YAAgC,CAAC;IACrC,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAC9B,IAAI,iBAAiB,GAAG,CAAC,CAAC,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,iBAAiB,GAAG,IAAI,CAAC;YACzB,iBAAiB,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM;QACR,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,iBAAiB,GAAG,CAAC,CAAC;YACtB,MAAM;QACR,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrC,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxC,WAAW,GAAG,IAAI,CAAC;YACnB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,mBAAmB,EAAE,CAAC;YAChC,aAAa,GAAG,IAAI,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACzB,OAAO;oBACL,QAAQ;oBACR,WAAW;oBACX,MAAM;oBACN,IAAI;oBACJ,aAAa;oBACb,gBAAgB,EAAE,EAAE;oBACpB,KAAK,EACH,8EAA8E;iBACjF,CAAC;YACJ,CAAC;YACD,YAAY,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,SAAS;QACX,CAAC;QACD,OAAO;YACL,QAAQ;YACR,WAAW;YACX,MAAM;YACN,IAAI;YACJ,aAAa;YACb,gBAAgB,EAAE,EAAE;YACpB,KAAK,EAAE,0BAA0B,GAAG,yCAAyC;SAC9E,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC;QAClD,OAAO;YACL,QAAQ;YACR,WAAW;YACX,MAAM;YACN,IAAI;YACJ,aAAa;YACb,gBAAgB,EAAE,EAAE;YACpB,KAAK,EAAE,iCAAiC,YAAY,sBAAsB,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAC5G,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GACd,iBAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7E,MAAM,gBAAgB,GACpB,iBAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAElE,IAAI,iBAAiB,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,CAAC,WAAW,EAAE,CAAC;QAClE,OAAO;YACL,QAAQ;YACR,WAAW;YACX,MAAM;YACN,IAAI;YACJ,aAAa;YACb,YAAY;YACZ,gBAAgB,EAAE,EAAE;YACpB,KAAK,EAAE,sCAAsC;SAC9C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ;QACR,WAAW;QACX,MAAM;QACN,IAAI;QACJ,aAAa;QACb,YAAY;QACZ,WAAW;QACX,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,iBAAiB,OAAO,EAAE;QAC1B,EAAE;QACF,QAAQ;QACR,6DAA6D;QAC7D,wDAAwD;QACxD,kEAAkE;QAClE,uBAAuB;QACvB,yDAAyD;QACzD,iDAAiD;QACjD,EAAE;QACF,WAAW;QACX,uBAAuB;QACvB,sBAAsB;QACtB,mBAAmB;QACnB,gEAAgE;QAChE,sCAAsC;QACtC,uBAAuB;QACvB,yCAAyC;QACzC,gEAAgE;QAChE,wDAAwD;QACxD,kEAAkE;QAClE,EAAE;QACF,iBAAiB;QACjB,8CAA8C;QAC9C,uCAAuC;QACvC,mEAAmE;QACnE,gEAAgE;QAChE,qEAAqE;QACrE,6DAA6D;QAC7D,EAAE;QACF,kBAAkB;QAClB,gBAAgB;QAChB,eAAe;QACf,gBAAgB;QAChB,EAAE;QACF,iBAAiB;QACjB,yCAAyC;QACzC,EAAE;QACF,wBAAwB;QACxB,oFAAoF;QACpF,0DAA0D;QAC1D,uDAAuD;QACvD,EAAE;QACF,QAAQ;QACR,6EAA6E;QAC7E,0GAA0G;QAC1G,8CAA8C;QAC9C,2DAA2D;QAC3D,kEAAkE;QAClE,EAAE;QACF,kBAAkB;QAClB,sEAAsE;QACtE,uDAAuD;QACvD,6DAA6D;QAC7D,qEAAqE;QACrE,oEAAoE;QACpE,+DAA+D;KAChE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS;IACT,SAAS;IACT,cAAc;IACd,mBAAmB;IACnB,eAAe;IACf,oBAAoB;IACpB,wFAAwF;IACxF,eAAe,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC;CAC/C,CAAC"}
1
+ {"version":3,"file":"cli-utils.js","sourceRoot":"","sources":["../src/cli-utils.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
+ import { randomBytes } from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import http from "node:http";
5
6
  import https from "node:https";
@@ -14,13 +15,8 @@ const __dirname = dirname(__filename);
14
15
  // Known tool config: env vars for the child process, extra CLI args, server env vars, and whether mitmproxy is needed
15
16
  // Note: actual tool config lives in cli-utils.ts so it can be unit-tested without importing this entrypoint.
16
17
  const LOCKFILE = "/tmp/context-lens.lock";
17
- // When invoked as `picl`, default to the `pi` tool if no command is given.
18
- const binaryName = process.argv[1] ? process.argv[1].split("/").pop() : "";
19
18
  const rawArgs = process.argv.slice(2);
20
- const isPiclInvocation = binaryName === "picl" &&
21
- !rawArgs.some((a) => !a.startsWith("-")) &&
22
- !rawArgs.includes("--");
23
- const parsedArgs = parseCliArgs(isPiclInvocation ? ["pi", ...rawArgs] : rawArgs);
19
+ const parsedArgs = parseCliArgs(rawArgs);
24
20
  if (parsedArgs.error) {
25
21
  console.error(parsedArgs.error);
26
22
  process.exit(1);
@@ -46,6 +42,9 @@ if (parsedArgs.commandName === "analyze") {
46
42
  else if (parsedArgs.commandName === "doctor") {
47
43
  void runDoctor().then((exitCode) => process.exit(exitCode));
48
44
  }
45
+ else if (parsedArgs.commandName === "stop") {
46
+ process.exit(backgroundStop());
47
+ }
49
48
  else if (parsedArgs.commandName === "background") {
50
49
  void runBackgroundCommand(parsedArgs.commandArguments, parsedArgs.noUi).then((exitCode) => process.exit(exitCode));
51
50
  }
@@ -99,8 +98,19 @@ else {
99
98
  const commandArguments = parsedArgs.commandArguments;
100
99
  const noOpen = parsedArgs.noOpen;
101
100
  const noUi = parsedArgs.noUi;
102
- // Get tool-specific config
103
- const toolConfig = getToolConfig(commandName);
101
+ const useMitm = parsedArgs.useMitm;
102
+ // Get tool-specific config, with optional mitmproxy override for pi
103
+ let toolConfig = getToolConfig(commandName);
104
+ if (useMitm && commandName === "pi") {
105
+ toolConfig = {
106
+ ...toolConfig,
107
+ childEnv: {
108
+ https_proxy: `http://localhost:${CLI_CONSTANTS.MITM_PORT}`,
109
+ SSL_CERT_FILE: "", // filled in below with mitmproxy CA cert path
110
+ },
111
+ needsMitm: true,
112
+ };
113
+ }
104
114
  if (noUi && toolConfig.needsMitm) {
105
115
  console.error("Error: --no-ui is not supported for this command because mitm capture requires the analysis ingest API on :4041.");
106
116
  process.exit(1);
@@ -173,6 +183,7 @@ else {
173
183
  let mitmReady = false;
174
184
  let childProcess = null;
175
185
  let piAgentDirToCleanup = null;
186
+ let brytiDataDirToCleanup = null;
176
187
  let shouldShutdownServers = false;
177
188
  let cleanupDidRun = false;
178
189
  const requiresAnalysis = !noUi;
@@ -357,11 +368,45 @@ else {
357
368
  function startChild() {
358
369
  // Inject extra args (e.g. codex -c chatgpt_base_url=...) before user args
359
370
  const allArgs = [...toolConfig.extraArgs, ...commandArguments];
360
- console.log(`\n🚀 Launching: ${commandName} ${allArgs.join(" ")}\n`);
371
+ const displayTarget = toolConfig.executable
372
+ ? `${commandName} (${toolConfig.executable})`
373
+ : commandName;
374
+ console.log(`\n🚀 Launching: ${displayTarget} ${allArgs.join(" ")}\n`);
361
375
  const childEnv = {
362
376
  ...process.env,
363
377
  ...toolConfig.childEnv,
364
378
  };
379
+ // Embed a per-invocation session ID into proxy base URLs so that separate
380
+ // CLI runs are always grouped into distinct conversations, even when they
381
+ // start with identical prompts. The session ID is injected as a path
382
+ // segment after the source tag, which extractSource() picks up as a
383
+ // stable conversation key for the lifetime of this process.
384
+ //
385
+ // Format: http://localhost:4040/<source>/<session-id>/
386
+ // Example: http://localhost:4040/gemini/a1b2c3d4/
387
+ //
388
+ // Codex uses mitmproxy and has its own chaining via previous_response_id.
389
+ // Claude Code and Pi embed their own session IDs in request metadata.
390
+ // Tools without built-in session IDs (Gemini, Aider, custom) rely on this.
391
+ if (!toolConfig.needsMitm) {
392
+ const sessionTag = randomBytes(4).toString("hex"); // 8 hex chars
393
+ for (const key of Object.keys(childEnv)) {
394
+ const val = childEnv[key];
395
+ if (typeof val !== "string")
396
+ continue;
397
+ // Match any value that points at our proxy and ends with /<source> or /<source>/
398
+ const proxyBase = `http://localhost:4040/`;
399
+ if (!val.startsWith(proxyBase))
400
+ continue;
401
+ const hadTrailingSlash = val.endsWith("/");
402
+ const after = val.slice(proxyBase.length).replace(/\/$/, "");
403
+ // Only inject if the remaining path is just the source tag (no session already)
404
+ if (after && !after.includes("/")) {
405
+ const suffix = hadTrailingSlash ? "/" : "";
406
+ childEnv[key] = `${proxyBase}${after}/${sessionTag}${suffix}`;
407
+ }
408
+ }
409
+ }
365
410
  // Fill in mitmproxy CA cert path for tools that need HTTPS interception
366
411
  if (toolConfig.needsMitm && childEnv.SSL_CERT_FILE === "") {
367
412
  const certPath = join(homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
@@ -372,18 +417,22 @@ else {
372
417
  console.error(`Warning: mitmproxy CA cert not found at ${certPath}. Run 'mitmdump' once to generate it.`);
373
418
  }
374
419
  }
375
- if (commandName === "pi") {
420
+ if (commandName === "pi" && !useMitm) {
376
421
  childEnv.PI_CODING_AGENT_DIR = preparePiAgentDir(childEnv.PI_CODING_AGENT_DIR);
377
422
  }
423
+ if (commandName === "bryti") {
424
+ childEnv.BRYTI_DATA_DIR = prepareBrytiDataDir(childEnv.BRYTI_DATA_DIR);
425
+ }
378
426
  // Spawn the child process with inherited stdio (interactive)
379
427
  // No shell: true. Avoids intermediate process that breaks signal delivery
380
- childProcess = spawn(commandName, allArgs, {
428
+ const spawnTarget = toolConfig.executable ?? commandName;
429
+ childProcess = spawn(spawnTarget, allArgs, {
381
430
  stdio: "inherit",
382
431
  env: childEnv,
383
432
  });
384
433
  childProcess.on("error", (err) => {
385
434
  if (err.code === "ENOENT") {
386
- console.error(`\nFailed to start '${commandName}': command not found.`);
435
+ console.error(`\nFailed to start '${spawnTarget}': command not found.`);
387
436
  console.error("Try a known tool (claude, codex, gemini, aider, pi) or use:");
388
437
  console.error(" context-lens -- <your-command> [args...]");
389
438
  cleanup(127);
@@ -554,6 +603,91 @@ else {
554
603
  return targetDir;
555
604
  }
556
605
  }
606
+ /**
607
+ * Create a temporary Bryti data directory with a proxy-aware config.yml.
608
+ *
609
+ * Bryti reads base_url for each model provider from its config.yml and does
610
+ * not respect environment variable overrides. We copy the real config (from
611
+ * the original BRYTI_DATA_DIR or ./data) into a temp dir, rewriting every
612
+ * provider's base_url to point at the context-lens proxy, then set
613
+ * BRYTI_DATA_DIR to the temp dir so Bryti picks up the patched config.
614
+ *
615
+ * The original config is never modified. The temp dir is cleaned up on exit.
616
+ *
617
+ * Bryti also writes runtime state (history, memory, pi agent dir, etc.) into
618
+ * BRYTI_DATA_DIR. Those paths all live under the temp dir for this session,
619
+ * which is intentional: captures are ephemeral, not merged back.
620
+ *
621
+ * If no config.yml is found in the source data dir, we warn and fall back to
622
+ * the temp dir without a config patch (bryti will error on its own).
623
+ */
624
+ function prepareBrytiDataDir(targetDirEnv) {
625
+ const dirPrefix = targetDirEnv && targetDirEnv.length > 0
626
+ ? targetDirEnv
627
+ : join(tmpdir(), "context-lens-bryti-");
628
+ const targetDir = fs.mkdtempSync(dirPrefix);
629
+ brytiDataDirToCleanup = targetDir;
630
+ // Find the real bryti data dir: check BRYTI_DATA_DIR in current env (before
631
+ // our override) or fall back to ./data relative to cwd.
632
+ const realDataDir = resolve(process.env.BRYTI_DATA_DIR || join(process.cwd(), "data"));
633
+ const sourceConfigPath = join(realDataDir, "config.yml");
634
+ try {
635
+ if (!fs.existsSync(sourceConfigPath)) {
636
+ console.error(`Warning: no Bryti config.yml found at ${sourceConfigPath}. ` +
637
+ "Bryti will start without a proxy-patched config.");
638
+ return targetDir;
639
+ }
640
+ const raw = fs.readFileSync(sourceConfigPath, "utf-8");
641
+ // Patch every `base_url:` value in the YAML that looks like an HTTP(S)
642
+ // URL (or is empty, meaning default Anthropic). We replace all provider
643
+ // base URLs with the proxy URL so all traffic is captured.
644
+ //
645
+ // We do a targeted line-level rewrite rather than full YAML parse+emit to
646
+ // avoid disturbing formatting, comments, or env-var substitution markers
647
+ // (${VAR}) that would fail a raw parse before substitution.
648
+ const proxyBase = `${CLI_CONSTANTS.PROXY_URL}/bryti`;
649
+ const patched = raw
650
+ .split("\n")
651
+ .map((line) => {
652
+ // Match lines like: base_url: "..." or base_url: '' or base_url:
653
+ // Only rewrite lines that are clearly provider base_url fields.
654
+ const m = line.match(/^(\s*base_url:\s*)(.*)$/);
655
+ if (!m)
656
+ return line;
657
+ // Preserve the indent + key, replace the value
658
+ return `${m[1]}"${proxyBase}"`;
659
+ })
660
+ .join("\n");
661
+ const targetConfigPath = join(targetDir, "config.yml");
662
+ fs.writeFileSync(targetConfigPath, patched, "utf-8");
663
+ // Symlink everything else from the real data dir (history, memory,
664
+ // extension files, etc.) so bryti picks up existing state.
665
+ // Skip config.yml (already written above) and the .pi subdir (bryti
666
+ // regenerates it from config, and mixing temp + real paths would be
667
+ // confusing).
668
+ if (fs.existsSync(realDataDir)) {
669
+ for (const entry of fs.readdirSync(realDataDir, {
670
+ withFileTypes: true,
671
+ })) {
672
+ if (entry.name === "config.yml" || entry.name === ".pi")
673
+ continue;
674
+ const src = join(realDataDir, entry.name);
675
+ const dst = join(targetDir, entry.name);
676
+ try {
677
+ fs.symlinkSync(src, dst);
678
+ }
679
+ catch {
680
+ // Non-fatal: if symlink fails, bryti will recreate the dir/file
681
+ }
682
+ }
683
+ }
684
+ return targetDir;
685
+ }
686
+ catch (err) {
687
+ console.error("Warning: failed to prepare Bryti proxy config:", err instanceof Error ? err.message : String(err));
688
+ return targetDir;
689
+ }
690
+ }
557
691
  // Open browser (cross-platform)
558
692
  function openBrowser(url) {
559
693
  const cmd = platform() === "darwin"
@@ -584,6 +718,14 @@ else {
584
718
  console.error("Warning: failed to clean up temporary Pi config dir:", err instanceof Error ? err.message : String(err));
585
719
  }
586
720
  }
721
+ if (brytiDataDirToCleanup) {
722
+ try {
723
+ fs.rmSync(brytiDataDirToCleanup, { recursive: true, force: true });
724
+ }
725
+ catch (err) {
726
+ console.error("Warning: failed to clean up temporary Bryti data dir:", err instanceof Error ? err.message : String(err));
727
+ }
728
+ }
587
729
  if (remainingRefs === 0 && shouldShutdownServers) {
588
730
  if (proxyProcess && !proxyProcess.killed)
589
731
  proxyProcess.kill();
@@ -915,9 +1057,9 @@ async function runDoctor() {
915
1057
  // mitmproxy is only needed for Codex subscription mode, so report as
916
1058
  // informational rather than a hard failure.
917
1059
  const mitmdumpPath = findBinaryOnPath("mitmdump");
918
- info("mitmdump (Codex only)", mitmdumpPath ?? "not found (install: pipx install mitmproxy)");
1060
+ info("mitmdump (Codex, pi --mitm)", mitmdumpPath ?? "not found (install: pipx install mitmproxy)");
919
1061
  const certPath = join(homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
920
- info("mitm CA cert (Codex only)", fs.existsSync(certPath)
1062
+ info("mitm CA cert (Codex, pi --mitm)", fs.existsSync(certPath)
921
1063
  ? certPath
922
1064
  : "not present (run 'mitmdump' once to generate)");
923
1065
  const contextDir = join(homedir(), ".context-lens");