context-lens 0.5.5 → 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.
- package/README.md +32 -4
- package/dist/analysis/ingest.d.ts.map +1 -1
- package/dist/analysis/ingest.js +8 -2
- package/dist/analysis/ingest.js.map +1 -1
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.d.ts.map +1 -1
- package/dist/cli-utils.js +37 -3
- package/dist/cli-utils.js.map +1 -1
- package/dist/cli.js +140 -9
- package/dist/cli.js.map +1 -1
- package/dist/core/conversation.d.ts +1 -0
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js +12 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/models.d.ts +7 -1
- package/dist/core/models.d.ts.map +1 -1
- package/dist/core/models.js +95 -13
- package/dist/core/models.js.map +1 -1
- package/dist/core/routing.d.ts +0 -7
- package/dist/core/routing.d.ts.map +1 -1
- package/dist/core/routing.js +18 -4
- package/dist/core/routing.js.map +1 -1
- package/dist/core/session-format.d.ts.map +1 -1
- package/dist/proxy/capture.d.ts +3 -25
- package/dist/proxy/capture.d.ts.map +1 -1
- package/dist/proxy/capture.js +1 -3
- package/dist/proxy/capture.js.map +1 -1
- package/dist/proxy/config.d.ts +1 -2
- package/dist/proxy/config.d.ts.map +1 -1
- package/dist/proxy/config.js +0 -1
- package/dist/proxy/config.js.map +1 -1
- package/dist/proxy/server.d.ts +8 -19
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/server.js +97 -44
- package/dist/proxy/server.js.map +1 -1
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +62 -4
- package/dist/server/api.js.map +1 -1
- package/dist/server/store.d.ts +7 -0
- package/dist/server/store.d.ts.map +1 -1
- package/dist/server/store.js +73 -0
- package/dist/server/store.js.map +1 -1
- package/dist/server/tags-store.d.ts +33 -0
- package/dist/server/tags-store.d.ts.map +1 -0
- package/dist/server/tags-store.js +150 -0
- package/dist/server/tags-store.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/mitm_addon.py +18 -0
- package/package.json +4 -3
- package/ui/dist/assets/index-CC_AtJBy.css +1 -0
- package/ui/dist/assets/index-If9FfL3y.js +36 -0
- package/ui/dist/index.html +2 -2
- package/dist/proxy/forward.d.ts +0 -20
- package/dist/proxy/forward.d.ts.map +0 -1
- package/dist/proxy/forward.js +0 -210
- package/dist/proxy/forward.js.map +0 -1
- package/dist/proxy/headers.d.ts +0 -16
- package/dist/proxy/headers.d.ts.map +0 -1
- package/dist/proxy/headers.js +0 -37
- package/dist/proxy/headers.js.map +0 -1
- package/dist/proxy/routing.d.ts +0 -45
- package/dist/proxy/routing.d.ts.map +0 -1
- package/dist/proxy/routing.js +0 -139
- package/dist/proxy/routing.js.map +0 -1
- package/ui/dist/assets/index-D1eb5Vsp.css +0 -1
- 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`, `
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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,
|
|
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"}
|
package/dist/analysis/ingest.js
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/cli-utils.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare const CLI_CONSTANTS: {
|
|
|
20
20
|
readonly MITM_PORT: 8080;
|
|
21
21
|
readonly MITM_PROXY_URL: "http://localhost:8080";
|
|
22
22
|
readonly PI_AGENT_DIR_PREFIX: "/tmp/context-lens-pi-agent-";
|
|
23
|
+
readonly BRYTI_DATA_DIR_PREFIX: "/tmp/context-lens-bryti-";
|
|
23
24
|
readonly COMMAND_ALIASES: Record<string, string>;
|
|
24
25
|
readonly KNOWN_PRIVACY_LEVELS: readonly ["minimal", "standard", "full"];
|
|
25
26
|
readonly MITM_ADDON_PATH: string;
|
package/dist/cli-utils.d.ts.map
CHANGED
|
@@ -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;
|
|
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] || {
|
|
@@ -210,16 +238,20 @@ export function formatHelpText() {
|
|
|
210
238
|
" context-lens [global-options] -- [command] [args...]",
|
|
211
239
|
" context-lens [global-options] (no command = standalone mode)",
|
|
212
240
|
" context-lens doctor",
|
|
241
|
+
" context-lens stop",
|
|
213
242
|
" context-lens background <start|stop|status> [--no-ui]",
|
|
214
243
|
" context-lens analyze <session.lhar> [options]",
|
|
215
244
|
"",
|
|
216
245
|
"Examples:",
|
|
217
246
|
" context-lens claude",
|
|
218
247
|
" context-lens codex",
|
|
248
|
+
" context-lens opencode",
|
|
219
249
|
" context-lens gm",
|
|
250
|
+
" context-lens bryti",
|
|
220
251
|
" context-lens --privacy=minimal aider --model claude-sonnet-4",
|
|
221
252
|
" context-lens -- python my_agent.py",
|
|
222
253
|
" context-lens doctor",
|
|
254
|
+
" context-lens stop",
|
|
223
255
|
" context-lens background start --no-ui",
|
|
224
256
|
" context-lens analyze ~/.context-lens/data/claude-abc123.lhar",
|
|
225
257
|
" context-lens analyze session.lhar --json --main-only",
|
|
@@ -238,9 +270,10 @@ export function formatHelpText() {
|
|
|
238
270
|
" cc -> claude",
|
|
239
271
|
" cx -> codex",
|
|
240
272
|
" gm -> gemini",
|
|
273
|
+
" oc -> opencode",
|
|
241
274
|
"",
|
|
242
|
-
"
|
|
243
|
-
"
|
|
275
|
+
"Shell alias (add to ~/.zshrc or ~/.bashrc):",
|
|
276
|
+
" alias cpi='context-lens pi'",
|
|
244
277
|
"",
|
|
245
278
|
"Environment variables:",
|
|
246
279
|
" UPSTREAM_OPENAI_URL Override OpenAI upstream (for OpenAI-compatible APIs)",
|
|
@@ -249,7 +282,7 @@ export function formatHelpText() {
|
|
|
249
282
|
"",
|
|
250
283
|
"Notes:",
|
|
251
284
|
" - No command starts standalone mode (proxy + analysis/web UI by default).",
|
|
252
|
-
" - 'codex'
|
|
285
|
+
" - 'codex' and 'opencode' use mitmproxy for HTTPS interception (requires mitmproxy; install: pipx install mitmproxy).",
|
|
253
286
|
" - 'pi --mitm' uses mitmproxy for full interception, useful for subscription-based models (openai-codex provider).",
|
|
254
287
|
" - 'doctor' is a local diagnostics command.",
|
|
255
288
|
" - 'background' manages detached proxy/web-ui processes.",
|
|
@@ -270,6 +303,7 @@ export const CLI_CONSTANTS = {
|
|
|
270
303
|
MITM_PORT,
|
|
271
304
|
MITM_PROXY_URL,
|
|
272
305
|
PI_AGENT_DIR_PREFIX,
|
|
306
|
+
BRYTI_DATA_DIR_PREFIX,
|
|
273
307
|
COMMAND_ALIASES,
|
|
274
308
|
KNOWN_PRIVACY_LEVELS,
|
|
275
309
|
// Resolved relative to compiled output (dist/ or dist-test/), matching cli.ts behavior.
|
package/dist/cli-utils.js.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
}
|
|
@@ -184,6 +183,7 @@ else {
|
|
|
184
183
|
let mitmReady = false;
|
|
185
184
|
let childProcess = null;
|
|
186
185
|
let piAgentDirToCleanup = null;
|
|
186
|
+
let brytiDataDirToCleanup = null;
|
|
187
187
|
let shouldShutdownServers = false;
|
|
188
188
|
let cleanupDidRun = false;
|
|
189
189
|
const requiresAnalysis = !noUi;
|
|
@@ -368,11 +368,45 @@ else {
|
|
|
368
368
|
function startChild() {
|
|
369
369
|
// Inject extra args (e.g. codex -c chatgpt_base_url=...) before user args
|
|
370
370
|
const allArgs = [...toolConfig.extraArgs, ...commandArguments];
|
|
371
|
-
|
|
371
|
+
const displayTarget = toolConfig.executable
|
|
372
|
+
? `${commandName} (${toolConfig.executable})`
|
|
373
|
+
: commandName;
|
|
374
|
+
console.log(`\n🚀 Launching: ${displayTarget} ${allArgs.join(" ")}\n`);
|
|
372
375
|
const childEnv = {
|
|
373
376
|
...process.env,
|
|
374
377
|
...toolConfig.childEnv,
|
|
375
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
|
+
}
|
|
376
410
|
// Fill in mitmproxy CA cert path for tools that need HTTPS interception
|
|
377
411
|
if (toolConfig.needsMitm && childEnv.SSL_CERT_FILE === "") {
|
|
378
412
|
const certPath = join(homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
|
|
@@ -386,15 +420,19 @@ else {
|
|
|
386
420
|
if (commandName === "pi" && !useMitm) {
|
|
387
421
|
childEnv.PI_CODING_AGENT_DIR = preparePiAgentDir(childEnv.PI_CODING_AGENT_DIR);
|
|
388
422
|
}
|
|
423
|
+
if (commandName === "bryti") {
|
|
424
|
+
childEnv.BRYTI_DATA_DIR = prepareBrytiDataDir(childEnv.BRYTI_DATA_DIR);
|
|
425
|
+
}
|
|
389
426
|
// Spawn the child process with inherited stdio (interactive)
|
|
390
427
|
// No shell: true. Avoids intermediate process that breaks signal delivery
|
|
391
|
-
|
|
428
|
+
const spawnTarget = toolConfig.executable ?? commandName;
|
|
429
|
+
childProcess = spawn(spawnTarget, allArgs, {
|
|
392
430
|
stdio: "inherit",
|
|
393
431
|
env: childEnv,
|
|
394
432
|
});
|
|
395
433
|
childProcess.on("error", (err) => {
|
|
396
434
|
if (err.code === "ENOENT") {
|
|
397
|
-
console.error(`\nFailed to start '${
|
|
435
|
+
console.error(`\nFailed to start '${spawnTarget}': command not found.`);
|
|
398
436
|
console.error("Try a known tool (claude, codex, gemini, aider, pi) or use:");
|
|
399
437
|
console.error(" context-lens -- <your-command> [args...]");
|
|
400
438
|
cleanup(127);
|
|
@@ -565,6 +603,91 @@ else {
|
|
|
565
603
|
return targetDir;
|
|
566
604
|
}
|
|
567
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
|
+
}
|
|
568
691
|
// Open browser (cross-platform)
|
|
569
692
|
function openBrowser(url) {
|
|
570
693
|
const cmd = platform() === "darwin"
|
|
@@ -595,6 +718,14 @@ else {
|
|
|
595
718
|
console.error("Warning: failed to clean up temporary Pi config dir:", err instanceof Error ? err.message : String(err));
|
|
596
719
|
}
|
|
597
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
|
+
}
|
|
598
729
|
if (remainingRefs === 0 && shouldShutdownServers) {
|
|
599
730
|
if (proxyProcess && !proxyProcess.killed)
|
|
600
731
|
proxyProcess.kill();
|