@glasstrace/sdk 1.1.0 → 1.1.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 +78 -1
- package/dist/{chunk-55FBXXER.js → chunk-2M57EO6U.js} +2 -2
- package/dist/chunk-3LILTM3T.js +384 -0
- package/dist/chunk-3LILTM3T.js.map +1 -0
- package/dist/{chunk-KE7MCPO5.js → chunk-4EZ6JTDG.js} +2 -2
- package/dist/{chunk-67RIOAXV.js → chunk-6RNBUUBR.js} +2 -2
- package/dist/{chunk-DO2YPMQ5.js → chunk-C567H5EQ.js} +23 -5
- package/dist/chunk-C567H5EQ.js.map +1 -0
- package/dist/{chunk-UGJ3X4CT.js → chunk-DST4UBXU.js} +2 -2
- package/dist/{chunk-DXRZKKSO.js → chunk-NB7GJE4S.js} +2 -4
- package/dist/chunk-NB7GJE4S.js.map +1 -0
- package/dist/{chunk-HAU66QBQ.js → chunk-P4OYPFQ5.js} +9 -9
- package/dist/chunk-P4OYPFQ5.js.map +1 -0
- package/dist/{chunk-LU3PPAOQ.js → chunk-UJ2JC7PZ.js} +4 -4
- package/dist/chunk-UJ2JC7PZ.js.map +1 -0
- package/dist/{chunk-TQ54WLCZ.js → chunk-X5MAXP5T.js} +2 -1
- package/dist/{chunk-ZBTC5QIQ.js → chunk-Z35HKVSO.js} +137 -10
- package/dist/chunk-Z35HKVSO.js.map +1 -0
- package/dist/cli/init.cjs +1313 -1118
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +456 -72
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs +257 -85
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.d.cts +35 -4
- package/dist/cli/mcp-add.d.ts +35 -4
- package/dist/cli/mcp-add.js +61 -25
- package/dist/cli/mcp-add.js.map +1 -1
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.js +1 -1
- package/dist/cli/uninit.cjs +1 -1
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.js +4 -4
- package/dist/cli/validate.cjs +14162 -2
- package/dist/cli/validate.cjs.map +1 -1
- package/dist/cli/validate.d.cts +7 -3
- package/dist/cli/validate.d.ts +7 -3
- package/dist/cli/validate.js +25 -2
- package/dist/cli/validate.js.map +1 -1
- package/dist/edge-entry.js +2 -2
- package/dist/index.cjs +423 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/dist/{monorepo-N5Z63XP7.js → monorepo-PFVNPQ6X.js} +3 -3
- package/dist/node-entry.cjs +423 -12
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.js +7 -7
- package/dist/node-subpath.js +3 -3
- package/dist/{source-map-uploader-BJIXRLJ6.js → source-map-uploader-DPUUCLNW.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-DO2YPMQ5.js.map +0 -1
- package/dist/chunk-DXRZKKSO.js.map +0 -1
- package/dist/chunk-HAU66QBQ.js.map +0 -1
- package/dist/chunk-IP4NMDJK.js +0 -98
- package/dist/chunk-IP4NMDJK.js.map +0 -1
- package/dist/chunk-LU3PPAOQ.js.map +0 -1
- package/dist/chunk-O63DJKIJ.js +0 -460
- package/dist/chunk-O63DJKIJ.js.map +0 -1
- package/dist/chunk-ZBTC5QIQ.js.map +0 -1
- /package/dist/{chunk-55FBXXER.js.map → chunk-2M57EO6U.js.map} +0 -0
- /package/dist/{chunk-KE7MCPO5.js.map → chunk-4EZ6JTDG.js.map} +0 -0
- /package/dist/{chunk-67RIOAXV.js.map → chunk-6RNBUUBR.js.map} +0 -0
- /package/dist/{chunk-UGJ3X4CT.js.map → chunk-DST4UBXU.js.map} +0 -0
- /package/dist/{chunk-TQ54WLCZ.js.map → chunk-X5MAXP5T.js.map} +0 -0
- /package/dist/{monorepo-N5Z63XP7.js.map → monorepo-PFVNPQ6X.js.map} +0 -0
- /package/dist/{source-map-uploader-BJIXRLJ6.js.map → source-map-uploader-DPUUCLNW.js.map} +0 -0
package/README.md
CHANGED
|
@@ -4,7 +4,14 @@ Server-side debugging SDK for AI coding agents. Captures traces,
|
|
|
4
4
|
errors, and runtime context from your Node.js application and delivers
|
|
5
5
|
them to coding agents through an MCP server and live dashboard.
|
|
6
6
|
|
|
7
|
-
> **Status
|
|
7
|
+
> **Status:** Stable, published as [`@glasstrace/sdk`](https://www.npmjs.com/package/@glasstrace/sdk) on npm.
|
|
8
|
+
>
|
|
9
|
+
> ```bash
|
|
10
|
+
> npm install @glasstrace/sdk
|
|
11
|
+
> ```
|
|
12
|
+
>
|
|
13
|
+
> See [CHANGELOG.md](https://github.com/Erik-1259/glasstrace-sdk/blob/main/packages/sdk/CHANGELOG.md)
|
|
14
|
+
> for the release history.
|
|
8
15
|
|
|
9
16
|
See the [monorepo README](../../README.md) for the full API overview,
|
|
10
17
|
including the [Coexistence with Other OTel Tools](../../README.md#coexistence-with-other-otel-tools)
|
|
@@ -156,6 +163,42 @@ GLASSTRACE_SUPPRESS_ACTION_NUDGE=1
|
|
|
156
163
|
The nudge never fires in production (detected via `NODE_ENV` or
|
|
157
164
|
`VERCEL_ENV`) unless `GLASSTRACE_FORCE_ENABLE=true` is also set.
|
|
158
165
|
|
|
166
|
+
## Capturing error response bodies
|
|
167
|
+
|
|
168
|
+
When debugging a 4xx or 5xx, the response body is often the most useful
|
|
169
|
+
signal — it carries the validation message, the tRPC error envelope, or
|
|
170
|
+
the upstream error code. The SDK can attach the body to the span as
|
|
171
|
+
`glasstrace.error.response_body`, but only under a strict three-gate
|
|
172
|
+
policy designed to prevent accidental leakage of customer data:
|
|
173
|
+
|
|
174
|
+
1. **Account opt-in.** The capture is gated on the
|
|
175
|
+
`errorResponseBodies` flag in your account's capture configuration,
|
|
176
|
+
which the SDK fetches at init time. The flag defaults to `false`, so
|
|
177
|
+
no body is ever attached unless your account has explicitly enabled
|
|
178
|
+
it.
|
|
179
|
+
2. **HTTP error status.** The body is only attached when the span's
|
|
180
|
+
HTTP status is in `[400..599]`. A successful response (2xx/3xx)
|
|
181
|
+
never leaks even if an upstream adapter populated the internal
|
|
182
|
+
attribute.
|
|
183
|
+
3. **Adapter-supplied body.** The exporter does not read response
|
|
184
|
+
bodies itself. An adapter (e.g., a future tRPC handler wrapper) sets
|
|
185
|
+
the body on `glasstrace.internal.response_body`; the exporter
|
|
186
|
+
promotes it to the public `glasstrace.error.response_body` attribute
|
|
187
|
+
only when the gates above pass.
|
|
188
|
+
|
|
189
|
+
Before promotion, the body is sanitized to redact common secret
|
|
190
|
+
patterns — Bearer tokens, JWT-shaped tokens, Glasstrace API keys
|
|
191
|
+
(`gt_dev_*` / `gt_anon_*`), AWS access-key prefixes (`AKIA…` /
|
|
192
|
+
`ASIA…`), and generic `apikey`/`secret`/`password`/`token` key-value
|
|
193
|
+
pairs — and truncated to 4096 UTF-8 bytes with a `...[truncated]`
|
|
194
|
+
marker appended when truncation fires. Truncation respects codepoint
|
|
195
|
+
boundaries so multi-byte characters are never split mid-sequence.
|
|
196
|
+
|
|
197
|
+
If your account does not enable the flag, the SDK ships zero response
|
|
198
|
+
body data. If your account enables the flag but a span never carries
|
|
199
|
+
the internal attribute (no adapter set it), the public attribute is
|
|
200
|
+
still absent. The default is "off, twice".
|
|
201
|
+
|
|
159
202
|
## Browser-extension discovery
|
|
160
203
|
|
|
161
204
|
`glasstrace init` writes a small static file at
|
|
@@ -306,6 +349,40 @@ edge code, but every runtime function that produces or consumes them is
|
|
|
306
349
|
Node-only, so the practical signal is the same: reach for these from
|
|
307
350
|
your build pipeline, not from a request handler.
|
|
308
351
|
|
|
352
|
+
#### Why is X Node-only?
|
|
353
|
+
|
|
354
|
+
Two mechanisms together produce the runtime split:
|
|
355
|
+
|
|
356
|
+
1. **Conditional exports in `packages/sdk/package.json`** make
|
|
357
|
+
`@glasstrace/sdk/node` resolvable only under Node's `node` export
|
|
358
|
+
condition. Workerd, Vercel Edge, browsers, and any other runtime
|
|
359
|
+
that does not set the `node` condition fail at module resolution
|
|
360
|
+
rather than at evaluation. That is what keeps any given symbol off
|
|
361
|
+
the edge surface once it lives under `/node`.
|
|
362
|
+
2. **The edge-bundle gate** (`packages/sdk/scripts/check-edge-bundle.mjs`)
|
|
363
|
+
then guarantees the *opposite* direction: the main edge bundle
|
|
364
|
+
(`dist/edge-entry.*`) is scanned for any reference to the Node
|
|
365
|
+
`process` global or any Node built-in specifier (`node:fs`, bare
|
|
366
|
+
`fs`, `fs/promises`, and so on), and the build fails if any are
|
|
367
|
+
found. So a symbol that reaches for `process` or a Node built-in
|
|
368
|
+
cannot accidentally end up on the edge side.
|
|
369
|
+
|
|
370
|
+
The gate is scope-aware about shadowing — a local binding named
|
|
371
|
+
`process` does not trip it — but it is deliberately not
|
|
372
|
+
control-flow-aware: a `process.env.X` read or a static `require("fs")`
|
|
373
|
+
keeps a symbol on the Node-only side even when the read is wrapped in
|
|
374
|
+
`typeof process !== "undefined"` or in a `try { ... } catch` guard. A
|
|
375
|
+
`typeof` guard means "this module reaches for `process`", and an
|
|
376
|
+
edge-safe module should not reach for `process` at all.
|
|
377
|
+
|
|
378
|
+
This is by design. Per the SDK-033 strict-gate policy, the contract
|
|
379
|
+
"this bundle passes the gate" must imply "this bundle is safe in any
|
|
380
|
+
edge runtime", and that implication only holds if the gate refuses
|
|
381
|
+
guards rather than trusting them. If you need a symbol that is currently
|
|
382
|
+
on the Node-only side to become edge-safe, the right move is to remove
|
|
383
|
+
the `process` and Node built-in reaches from the symbol's transitive
|
|
384
|
+
closure, not to add a runtime guard.
|
|
385
|
+
|
|
309
386
|
## Security
|
|
310
387
|
|
|
311
388
|
The SDK transmits your API key exclusively via the `Authorization: Bearer`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
NEXT_CONFIG_NAMES
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NB7GJE4S.js";
|
|
4
4
|
|
|
5
5
|
// src/cli/monorepo.ts
|
|
6
6
|
import * as fs from "node:fs";
|
|
@@ -239,4 +239,4 @@ export {
|
|
|
239
239
|
findNextJsApps,
|
|
240
240
|
parsePnpmWorkspaceYaml
|
|
241
241
|
};
|
|
242
|
-
//# sourceMappingURL=chunk-
|
|
242
|
+
//# sourceMappingURL=chunk-2M57EO6U.js.map
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnonApiKeySchema,
|
|
3
|
+
DevApiKeySchema,
|
|
4
|
+
createAnonApiKey
|
|
5
|
+
} from "./chunk-X5MAXP5T.js";
|
|
6
|
+
|
|
7
|
+
// src/anon-key.ts
|
|
8
|
+
var GLASSTRACE_DIR = ".glasstrace";
|
|
9
|
+
var ANON_KEY_FILE = "anon_key";
|
|
10
|
+
var CLAIMED_KEY_FILE = "claimed-key";
|
|
11
|
+
var fsPathCache;
|
|
12
|
+
async function loadFsPath() {
|
|
13
|
+
if (fsPathCache !== void 0) return fsPathCache;
|
|
14
|
+
try {
|
|
15
|
+
const [fs, path] = await Promise.all([
|
|
16
|
+
import("node:fs/promises"),
|
|
17
|
+
import("node:path")
|
|
18
|
+
]);
|
|
19
|
+
fsPathCache = { fs, path };
|
|
20
|
+
return fsPathCache;
|
|
21
|
+
} catch {
|
|
22
|
+
fsPathCache = null;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var ephemeralKeyCache = /* @__PURE__ */ new Map();
|
|
27
|
+
async function readAnonKey(projectRoot) {
|
|
28
|
+
const root = projectRoot ?? process.cwd();
|
|
29
|
+
const modules = await loadFsPath();
|
|
30
|
+
if (modules) {
|
|
31
|
+
const keyPath = modules.path.join(root, GLASSTRACE_DIR, ANON_KEY_FILE);
|
|
32
|
+
try {
|
|
33
|
+
const content = await modules.fs.readFile(keyPath, "utf-8");
|
|
34
|
+
const result = AnonApiKeySchema.safeParse(content);
|
|
35
|
+
if (result.success) {
|
|
36
|
+
return result.data;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const cached = ephemeralKeyCache.get(root);
|
|
42
|
+
if (cached !== void 0) {
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
async function readClaimedKey(projectRoot) {
|
|
48
|
+
const root = projectRoot ?? process.cwd();
|
|
49
|
+
const modules = await loadFsPath();
|
|
50
|
+
if (!modules) return null;
|
|
51
|
+
const keyPath = modules.path.join(root, GLASSTRACE_DIR, CLAIMED_KEY_FILE);
|
|
52
|
+
try {
|
|
53
|
+
const content = await modules.fs.readFile(keyPath, "utf-8");
|
|
54
|
+
const trimmed = content.trim();
|
|
55
|
+
const parsed = DevApiKeySchema.safeParse(trimmed);
|
|
56
|
+
if (parsed.success) {
|
|
57
|
+
return parsed.data;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
async function getOrCreateAnonKey(projectRoot) {
|
|
64
|
+
const root = projectRoot ?? process.cwd();
|
|
65
|
+
const existingKey = await readAnonKey(root);
|
|
66
|
+
if (existingKey !== null) {
|
|
67
|
+
return existingKey;
|
|
68
|
+
}
|
|
69
|
+
const cached = ephemeralKeyCache.get(root);
|
|
70
|
+
if (cached !== void 0) {
|
|
71
|
+
return cached;
|
|
72
|
+
}
|
|
73
|
+
const newKey = createAnonApiKey();
|
|
74
|
+
const modules = await loadFsPath();
|
|
75
|
+
if (!modules) {
|
|
76
|
+
ephemeralKeyCache.set(root, newKey);
|
|
77
|
+
return newKey;
|
|
78
|
+
}
|
|
79
|
+
const dirPath = modules.path.join(root, GLASSTRACE_DIR);
|
|
80
|
+
const keyPath = modules.path.join(dirPath, ANON_KEY_FILE);
|
|
81
|
+
try {
|
|
82
|
+
await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
|
|
83
|
+
await modules.fs.writeFile(keyPath, newKey, { flag: "wx", mode: 384 });
|
|
84
|
+
return newKey;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const code = err.code;
|
|
87
|
+
if (code === "EEXIST") {
|
|
88
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
89
|
+
const winnerKey = await readAnonKey(root);
|
|
90
|
+
if (winnerKey !== null) {
|
|
91
|
+
return winnerKey;
|
|
92
|
+
}
|
|
93
|
+
if (attempt < 2) {
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await modules.fs.writeFile(keyPath, newKey, { mode: 384 });
|
|
99
|
+
await modules.fs.chmod(keyPath, 384);
|
|
100
|
+
return newKey;
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
ephemeralKeyCache.set(root, newKey);
|
|
105
|
+
console.warn(
|
|
106
|
+
`[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`
|
|
107
|
+
);
|
|
108
|
+
return newKey;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/mcp-runtime.ts
|
|
113
|
+
import { createHash } from "node:crypto";
|
|
114
|
+
var MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
|
|
115
|
+
var fsPathCache2;
|
|
116
|
+
async function loadFsPath2() {
|
|
117
|
+
if (fsPathCache2 !== void 0) return fsPathCache2;
|
|
118
|
+
try {
|
|
119
|
+
const [fs, path] = await Promise.all([
|
|
120
|
+
import("node:fs/promises"),
|
|
121
|
+
import("node:path")
|
|
122
|
+
]);
|
|
123
|
+
fsPathCache2 = { fs, path };
|
|
124
|
+
return fsPathCache2;
|
|
125
|
+
} catch {
|
|
126
|
+
fsPathCache2 = null;
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function identityFingerprint(token) {
|
|
131
|
+
return `sha256:${createHash("sha256").update(token).digest("hex")}`;
|
|
132
|
+
}
|
|
133
|
+
function mcpConfigMatches(existingContent, expectedContent) {
|
|
134
|
+
const trimmedExpected = expectedContent.trim();
|
|
135
|
+
try {
|
|
136
|
+
const existingParsed = JSON.parse(existingContent);
|
|
137
|
+
const expectedParsed = JSON.parse(trimmedExpected);
|
|
138
|
+
return JSON.stringify(canonicalize(existingParsed)) === JSON.stringify(canonicalize(expectedParsed));
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
return existingContent.trim() === trimmedExpected;
|
|
142
|
+
}
|
|
143
|
+
function canonicalize(value) {
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
return value.map(canonicalize);
|
|
146
|
+
}
|
|
147
|
+
if (value !== null && typeof value === "object") {
|
|
148
|
+
const obj = value;
|
|
149
|
+
const sorted = {};
|
|
150
|
+
for (const key of Object.keys(obj).sort()) {
|
|
151
|
+
sorted[key] = canonicalize(obj[key]);
|
|
152
|
+
}
|
|
153
|
+
return sorted;
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
function readEnvLocalApiKey(content) {
|
|
158
|
+
let last = null;
|
|
159
|
+
const regex = /^\s*GLASSTRACE_API_KEY\s*=\s*(.*)$/gm;
|
|
160
|
+
let match;
|
|
161
|
+
while ((match = regex.exec(content)) !== null) {
|
|
162
|
+
const raw = match[1].trim();
|
|
163
|
+
if (raw === "") continue;
|
|
164
|
+
const unquoted = raw.replace(/^(['"])(.*)\1$/, "$2");
|
|
165
|
+
if (unquoted === "" || unquoted === "your_key_here") continue;
|
|
166
|
+
last = unquoted;
|
|
167
|
+
}
|
|
168
|
+
return last;
|
|
169
|
+
}
|
|
170
|
+
function isDevApiKey(value) {
|
|
171
|
+
if (value === null || value === void 0) return false;
|
|
172
|
+
return value.trim().startsWith("gt_dev_");
|
|
173
|
+
}
|
|
174
|
+
function isAnonApiKey(value) {
|
|
175
|
+
if (value === null || value === void 0) return false;
|
|
176
|
+
return AnonApiKeySchema.safeParse(value).success;
|
|
177
|
+
}
|
|
178
|
+
async function resolveEffectiveMcpCredential(projectRoot) {
|
|
179
|
+
const root = projectRoot ?? process.cwd();
|
|
180
|
+
const warnings = [];
|
|
181
|
+
const envLocalKey = await readEnvLocalDevKey(root, warnings);
|
|
182
|
+
const claimedKey = envLocalKey === null ? await readClaimedKey(root) : null;
|
|
183
|
+
const anonKey = await readAnonKey(root);
|
|
184
|
+
let effective = null;
|
|
185
|
+
if (envLocalKey !== null) {
|
|
186
|
+
effective = { source: "env-local", key: envLocalKey };
|
|
187
|
+
} else if (claimedKey !== null) {
|
|
188
|
+
effective = { source: "claimed-key", key: claimedKey };
|
|
189
|
+
warnings.push("claimed-key-only");
|
|
190
|
+
} else if (anonKey !== null) {
|
|
191
|
+
effective = { source: "anon", key: anonKey };
|
|
192
|
+
}
|
|
193
|
+
return { effective, anonKey, warnings };
|
|
194
|
+
}
|
|
195
|
+
async function readEnvLocalDevKey(root, warnings) {
|
|
196
|
+
const modules = await loadFsPath2();
|
|
197
|
+
if (!modules) return null;
|
|
198
|
+
const envPath = modules.path.join(root, ".env.local");
|
|
199
|
+
let content;
|
|
200
|
+
try {
|
|
201
|
+
content = await modules.fs.readFile(envPath, "utf-8");
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const raw = readEnvLocalApiKey(content);
|
|
206
|
+
if (raw === null) return null;
|
|
207
|
+
const parsed = DevApiKeySchema.safeParse(raw);
|
|
208
|
+
if (!parsed.success) {
|
|
209
|
+
warnings.push("malformed-env-local");
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return parsed.data;
|
|
213
|
+
}
|
|
214
|
+
var MCP_MARKER_FILE = "mcp-connected";
|
|
215
|
+
var GLASSTRACE_DIR2 = ".glasstrace";
|
|
216
|
+
async function readMcpMarker(projectRoot) {
|
|
217
|
+
const root = projectRoot ?? process.cwd();
|
|
218
|
+
const modules = await loadFsPath2();
|
|
219
|
+
if (!modules) return { status: "absent" };
|
|
220
|
+
const markerPath = modules.path.join(root, GLASSTRACE_DIR2, MCP_MARKER_FILE);
|
|
221
|
+
let content;
|
|
222
|
+
try {
|
|
223
|
+
content = await modules.fs.readFile(markerPath, "utf-8");
|
|
224
|
+
} catch {
|
|
225
|
+
return { status: "absent" };
|
|
226
|
+
}
|
|
227
|
+
let parsed;
|
|
228
|
+
try {
|
|
229
|
+
parsed = JSON.parse(content);
|
|
230
|
+
} catch {
|
|
231
|
+
return { status: "corrupted" };
|
|
232
|
+
}
|
|
233
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
234
|
+
return { status: "corrupted" };
|
|
235
|
+
}
|
|
236
|
+
const obj = parsed;
|
|
237
|
+
const version = obj["version"];
|
|
238
|
+
if (version === void 0) {
|
|
239
|
+
const keyHash = obj["keyHash"];
|
|
240
|
+
if (typeof keyHash !== "string" || keyHash === "") {
|
|
241
|
+
return { status: "corrupted" };
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
status: "valid",
|
|
245
|
+
credentialSource: "anon",
|
|
246
|
+
credentialHash: keyHash
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (version === 2) {
|
|
250
|
+
const source = obj["credentialSource"];
|
|
251
|
+
const hash = obj["credentialHash"];
|
|
252
|
+
if (source !== "env-local" && source !== "claimed-key" && source !== "anon" || typeof hash !== "string" || hash === "") {
|
|
253
|
+
return { status: "corrupted" };
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: "valid",
|
|
257
|
+
credentialSource: source,
|
|
258
|
+
credentialHash: hash
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (typeof version === "number" && version > 2) {
|
|
262
|
+
return { status: "unknown-version" };
|
|
263
|
+
}
|
|
264
|
+
return { status: "corrupted" };
|
|
265
|
+
}
|
|
266
|
+
async function writeMcpMarker(projectRoot, target) {
|
|
267
|
+
const modules = await loadFsPath2();
|
|
268
|
+
if (!modules) return false;
|
|
269
|
+
const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR2);
|
|
270
|
+
const markerPath = modules.path.join(dirPath, MCP_MARKER_FILE);
|
|
271
|
+
const state = await readMcpMarker(projectRoot);
|
|
272
|
+
if (state.status === "valid" && state.credentialSource === target.credentialSource && state.credentialHash === target.credentialHash) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
|
|
276
|
+
const body = JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
version: 2,
|
|
279
|
+
credentialSource: target.credentialSource,
|
|
280
|
+
credentialHash: target.credentialHash,
|
|
281
|
+
configuredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
282
|
+
},
|
|
283
|
+
null,
|
|
284
|
+
2
|
|
285
|
+
);
|
|
286
|
+
await modules.fs.writeFile(markerPath, body, { mode: 384 });
|
|
287
|
+
await modules.fs.chmod(markerPath, 384);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
var MCP_CONFIG_FILE = "mcp.json";
|
|
291
|
+
var refreshNudgeEmitted = false;
|
|
292
|
+
function emitRefreshNudge(persistedSource) {
|
|
293
|
+
if (refreshNudgeEmitted) return;
|
|
294
|
+
refreshNudgeEmitted = true;
|
|
295
|
+
try {
|
|
296
|
+
if (persistedSource === "claimed-key") {
|
|
297
|
+
process.stderr.write(
|
|
298
|
+
"[glasstrace] MCP config refreshed for the new credential. Copy .glasstrace/claimed-key into .env.local so Codex can pick it up on next restart.\n"
|
|
299
|
+
);
|
|
300
|
+
} else {
|
|
301
|
+
process.stderr.write(
|
|
302
|
+
"[glasstrace] MCP config refreshed for the new credential.\n"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function genericMcpConfigContent(endpoint, bearer) {
|
|
309
|
+
return JSON.stringify(
|
|
310
|
+
{
|
|
311
|
+
mcpServers: {
|
|
312
|
+
glasstrace: {
|
|
313
|
+
url: endpoint,
|
|
314
|
+
headers: {
|
|
315
|
+
Authorization: `Bearer ${bearer}`
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
null,
|
|
321
|
+
2
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
async function refreshGenericMcpConfigAtRuntime(projectRoot, effective, anonKeyOnDisk) {
|
|
325
|
+
if (effective === null || effective.source === "anon") {
|
|
326
|
+
return { action: "skipped-anon-source" };
|
|
327
|
+
}
|
|
328
|
+
if (anonKeyOnDisk === null) {
|
|
329
|
+
return { action: "absent" };
|
|
330
|
+
}
|
|
331
|
+
const modules = await loadFsPath2();
|
|
332
|
+
if (!modules) return { action: "absent" };
|
|
333
|
+
const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR2);
|
|
334
|
+
const configPath = modules.path.join(dirPath, MCP_CONFIG_FILE);
|
|
335
|
+
const tmpPath = configPath + ".tmp";
|
|
336
|
+
let existing;
|
|
337
|
+
try {
|
|
338
|
+
existing = await modules.fs.readFile(configPath, "utf-8");
|
|
339
|
+
} catch (err) {
|
|
340
|
+
const code = err.code;
|
|
341
|
+
if (code === "ENOENT") {
|
|
342
|
+
return { action: "absent" };
|
|
343
|
+
}
|
|
344
|
+
return { action: "preserved" };
|
|
345
|
+
}
|
|
346
|
+
const expectedAnon = genericMcpConfigContent(MCP_ENDPOINT, anonKeyOnDisk);
|
|
347
|
+
if (!mcpConfigMatches(existing, expectedAnon)) {
|
|
348
|
+
return { action: "preserved" };
|
|
349
|
+
}
|
|
350
|
+
const replacement = genericMcpConfigContent(MCP_ENDPOINT, effective.key);
|
|
351
|
+
try {
|
|
352
|
+
await modules.fs.writeFile(tmpPath, replacement, { mode: 384 });
|
|
353
|
+
await modules.fs.chmod(tmpPath, 384);
|
|
354
|
+
await modules.fs.rename(tmpPath, configPath);
|
|
355
|
+
await writeMcpMarker(projectRoot, {
|
|
356
|
+
credentialSource: effective.source,
|
|
357
|
+
credentialHash: identityFingerprint(effective.key)
|
|
358
|
+
});
|
|
359
|
+
} catch {
|
|
360
|
+
try {
|
|
361
|
+
await modules.fs.unlink(tmpPath);
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
return { action: "preserved" };
|
|
365
|
+
}
|
|
366
|
+
emitRefreshNudge(effective.source);
|
|
367
|
+
return { action: "rewrote" };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export {
|
|
371
|
+
readAnonKey,
|
|
372
|
+
getOrCreateAnonKey,
|
|
373
|
+
MCP_ENDPOINT,
|
|
374
|
+
identityFingerprint,
|
|
375
|
+
mcpConfigMatches,
|
|
376
|
+
readEnvLocalApiKey,
|
|
377
|
+
isDevApiKey,
|
|
378
|
+
isAnonApiKey,
|
|
379
|
+
resolveEffectiveMcpCredential,
|
|
380
|
+
readMcpMarker,
|
|
381
|
+
writeMcpMarker,
|
|
382
|
+
refreshGenericMcpConfigAtRuntime
|
|
383
|
+
};
|
|
384
|
+
//# sourceMappingURL=chunk-3LILTM3T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/anon-key.ts","../src/mcp-runtime.ts"],"sourcesContent":["import { AnonApiKeySchema, DevApiKeySchema, createAnonApiKey } from \"@glasstrace/protocol\";\nimport type { AnonApiKey, DevApiKey } from \"@glasstrace/protocol\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst ANON_KEY_FILE = \"anon_key\";\nconst CLAIMED_KEY_FILE = \"claimed-key\";\n\n/**\n * Lazily imports `node:fs/promises` and `node:path`. Returns `null` if\n * the modules are unavailable (non-Node environments). The result is\n * cached after first resolution.\n */\nlet fsPathCache: { fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null | undefined;\n\nasync function loadFsPath(): Promise<{ fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") } | null> {\n if (fsPathCache !== undefined) return fsPathCache;\n try {\n const [fs, path] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:path\"),\n ]);\n fsPathCache = { fs, path };\n return fsPathCache;\n } catch {\n fsPathCache = null;\n return null;\n }\n}\n\n/**\n * In-memory cache for ephemeral keys when filesystem persistence fails.\n * Keyed by resolved project root to support multiple roots in tests.\n */\nconst ephemeralKeyCache = new Map<string, AnonApiKey>();\n\n/**\n * Reads an existing anonymous key from the filesystem.\n * Returns the key if valid, or null if:\n * - The file does not exist\n * - The file content is invalid\n * - An I/O error occurs\n * - `node:fs` is unavailable (non-Node environment)\n */\nexport async function readAnonKey(projectRoot?: string): Promise<AnonApiKey | null> {\n const root = projectRoot ?? process.cwd();\n\n const modules = await loadFsPath();\n if (modules) {\n const keyPath = modules.path.join(root, GLASSTRACE_DIR, ANON_KEY_FILE);\n try {\n const content = await modules.fs.readFile(keyPath, \"utf-8\");\n const result = AnonApiKeySchema.safeParse(content);\n if (result.success) {\n return result.data;\n }\n } catch {\n // Fall through to check ephemeral cache\n }\n }\n\n // Check in-memory cache (used when filesystem persistence failed\n // or when node:fs is unavailable)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n return null;\n}\n\n/**\n * Reads a claimed developer API key persisted at\n * `.glasstrace/claimed-key`. The file is the runtime fallback used by\n * {@link import(\"./init-client.js\").writeClaimedKey} when `.env.local`\n * is not writable.\n *\n * Returns the key when the file exists and its contents pass\n * `DevApiKeySchema` validation. Returns `null` when:\n * - The file does not exist.\n * - The file content fails strict schema validation (a malformed or\n * stale value cannot be distinguished from a valid one without a\n * server roundtrip — callers should treat this as \"no key\").\n * - An I/O error occurs.\n * - `node:fs` is unavailable (non-Node environment).\n */\nexport async function readClaimedKey(projectRoot?: string): Promise<DevApiKey | null> {\n const root = projectRoot ?? process.cwd();\n\n const modules = await loadFsPath();\n if (!modules) return null;\n\n const keyPath = modules.path.join(root, GLASSTRACE_DIR, CLAIMED_KEY_FILE);\n try {\n const content = await modules.fs.readFile(keyPath, \"utf-8\");\n const trimmed = content.trim();\n const parsed = DevApiKeySchema.safeParse(trimmed);\n if (parsed.success) {\n return parsed.data;\n }\n } catch {\n // Fall through — file missing, unreadable, or invalid; treat as absent.\n }\n\n return null;\n}\n\n/**\n * Gets an existing anonymous key from the filesystem, or creates a new one.\n *\n * - If file exists and contains a valid key, returns it\n * - If file does not exist or content is invalid, generates a new key via createAnonApiKey()\n * - Writes the new key to `.glasstrace/anon_key`, creating the directory if needed\n * - On file write failure: logs a warning, caches an ephemeral in-memory key so\n * repeated calls in the same process return the same key\n * - In non-Node environments: returns an ephemeral in-memory key\n */\nexport async function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey> {\n const root = projectRoot ?? process.cwd();\n\n // Try reading existing key from filesystem\n const existingKey = await readAnonKey(root);\n if (existingKey !== null) {\n return existingKey;\n }\n\n // Check in-memory cache (used when filesystem is unavailable)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n // Generate a new key\n const newKey = createAnonApiKey();\n\n // Attempt filesystem persistence (only in Node.js environments)\n const modules = await loadFsPath();\n if (!modules) {\n // No filesystem access — cache in memory\n ephemeralKeyCache.set(root, newKey);\n return newKey;\n }\n\n const dirPath = modules.path.join(root, GLASSTRACE_DIR);\n const keyPath = modules.path.join(dirPath, ANON_KEY_FILE);\n\n // Persist to filesystem using atomic create-or-fail (O_CREAT | O_EXCL)\n // to prevent TOCTOU races where concurrent cold starts both generate keys.\n try {\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n await modules.fs.writeFile(keyPath, newKey, { flag: \"wx\", mode: 0o600 });\n return newKey;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"EEXIST\") {\n // Another process won the race. Retry reading their key with\n // short delays — the winner's writeFile is atomic for small\n // payloads but the filesystem may not have flushed yet.\n for (let attempt = 0; attempt < 3; attempt++) {\n const winnerKey = await readAnonKey(root);\n if (winnerKey !== null) {\n return winnerKey;\n }\n // Short delay before next retry (50ms), skip after final attempt\n if (attempt < 2) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n }\n // All retries exhausted — overwrite as last resort.\n // Use explicit chmod after overwrite since writeFile mode only\n // applies on creation on some platforms.\n try {\n await modules.fs.writeFile(keyPath, newKey, { mode: 0o600 });\n await modules.fs.chmod(keyPath, 0o600);\n return newKey;\n } catch {\n // Overwrite failed — fall through to ephemeral cache\n }\n }\n\n // Non-EEXIST error (EACCES, ENOTDIR, etc.) — cache in memory so\n // repeated calls get the same ephemeral key within this process.\n ephemeralKeyCache.set(root, newKey);\n console.warn(\n `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`,\n );\n return newKey;\n }\n}\n","import { createHash } from \"node:crypto\";\nimport {\n AnonApiKeySchema,\n DevApiKeySchema,\n type AnonApiKey,\n type DevApiKey,\n} from \"@glasstrace/protocol\";\nimport { readAnonKey, readClaimedKey } from \"./anon-key.js\";\n\n/**\n * Glasstrace MCP endpoint embedded in managed MCP configs and used by\n * the runtime claim-refresh path. Lives here (not in `cli/constants.ts`)\n * so the runtime helper can reach it without crossing the runtime/CLI\n * boundary; CLI callers import it directly from this module.\n */\nexport const MCP_ENDPOINT = \"https://api.glasstrace.dev/mcp\";\n\n/**\n * Runtime-safe MCP credential and config utilities.\n *\n * This module is loaded into user processes at SDK boot. It must not\n * import from `cli/*` or `agent-detection/*` so the runtime bundle does\n * not pull in CLI scaffolding or filesystem scanners. The boundary is\n * enforced by an import-graph guard test.\n *\n * Internal: not re-exported via `node-entry.ts` or `index.ts`.\n *\n * @module\n */\n\nlet fsPathCache:\n | { fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") }\n | null\n | undefined;\n\nasync function loadFsPath(): Promise<\n | { fs: typeof import(\"node:fs/promises\"); path: typeof import(\"node:path\") }\n | null\n> {\n if (fsPathCache !== undefined) return fsPathCache;\n try {\n const [fs, path] = await Promise.all([\n import(\"node:fs/promises\"),\n import(\"node:path\"),\n ]);\n fsPathCache = { fs, path };\n return fsPathCache;\n } catch {\n fsPathCache = null;\n return null;\n }\n}\n\n/**\n * Computes a stable identity fingerprint for deduplication purposes.\n * This is NOT password hashing — the input is an opaque token used as\n * a marker identity, not a credential stored for authentication.\n *\n * @internal Exported for unit testing and for `cli/scaffolder.ts`'s\n * marker writer.\n */\nexport function identityFingerprint(token: string): string {\n return `sha256:${createHash(\"sha256\").update(token).digest(\"hex\")}`;\n}\n\n/**\n * Compares two MCP config strings for canonical-JSON equality. Returns\n * `true` when both inputs parse as JSON and produce structurally equal\n * objects after recursive key sorting; falls back to trimmed text\n * comparison for TOML and other non-JSON formats. Returns `false` on\n * parse errors that don't fall through to text comparison.\n *\n * Used to detect manually-edited MCP configs before overwriting them\n * (DISC-1247 Scenario 2c) and as the staleness signal for SDK-managed\n * configs that must be refreshed when the project's effective\n * credential changes.\n *\n * @internal Exported for unit testing only.\n */\nexport function mcpConfigMatches(\n existingContent: string,\n expectedContent: string,\n): boolean {\n const trimmedExpected = expectedContent.trim();\n\n try {\n const existingParsed: unknown = JSON.parse(existingContent);\n const expectedParsed: unknown = JSON.parse(trimmedExpected);\n return (\n JSON.stringify(canonicalize(existingParsed)) ===\n JSON.stringify(canonicalize(expectedParsed))\n );\n } catch {\n // Fall through to text comparison for TOML and other non-JSON formats.\n }\n\n return existingContent.trim() === trimmedExpected;\n}\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(canonicalize);\n }\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize(obj[key]);\n }\n return sorted;\n }\n return value;\n}\n\n/**\n * Parses a `.env.local` file's text content for `GLASSTRACE_API_KEY`,\n * returning the last assignment's value. Empty values\n * (`GLASSTRACE_API_KEY=`) and the `your_key_here` placeholder are\n * filtered out. Surrounding single or double quotes are stripped.\n *\n * The resolver validates the returned value against `DevApiKeySchema`\n * before accepting it; this parser is permissive on purpose so that\n * malformed values can be flagged with a `malformed-env-local`\n * warning rather than silently dropped.\n *\n * @internal Exported for unit testing only.\n */\nexport function readEnvLocalApiKey(content: string): string | null {\n let last: string | null = null;\n const regex = /^\\s*GLASSTRACE_API_KEY\\s*=\\s*(.*)$/gm;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(content)) !== null) {\n const raw = match[1].trim();\n if (raw === \"\") continue;\n const unquoted = raw.replace(/^(['\"])(.*)\\1$/, \"$2\");\n if (unquoted === \"\" || unquoted === \"your_key_here\") continue;\n last = unquoted;\n }\n return last;\n}\n\n/**\n * Returns true when the given API key value looks like a claimed\n * developer key (prefix `gt_dev_`). Defensive against leading or\n * trailing whitespace.\n *\n * **This is a prefix-only check, not strict validation.** Use it as a\n * fast path for \"looks like a claimed key, do not overwrite\". The\n * effective-credential resolver validates with\n * `DevApiKeySchema.safeParse` because a `gt_dev_` prefix alone is not\n * sufficient to authenticate against the backend.\n *\n * @internal Exported for unit testing only.\n */\nexport function isDevApiKey(value: string | null | undefined): boolean {\n if (value === null || value === undefined) return false;\n return value.trim().startsWith(\"gt_dev_\");\n}\n\n/**\n * Returns true when the given API key value is a fully-valid anonymous\n * API key (matches `AnonApiKeySchema`). Used by `registerViaCli` as a\n * runtime guard so that a `DevApiKey` cannot be passed via process\n * arguments to vendor MCP CLIs (which would expose it via `ps` on\n * multi-user hosts).\n *\n * @internal Exported for unit testing only.\n */\nexport function isAnonApiKey(value: string | null | undefined): boolean {\n if (value === null || value === undefined) return false;\n return AnonApiKeySchema.safeParse(value).success;\n}\n\n/**\n * The MCP-effective credential, tagged by which on-disk source produced\n * it. `env-local` and `claimed-key` carry a branded `DevApiKey`;\n * `anon` carries a branded `AnonApiKey`. Internal — not re-exported.\n */\nexport type EffectiveMcpCredential =\n | { source: \"env-local\"; key: DevApiKey }\n | { source: \"claimed-key\"; key: DevApiKey }\n | { source: \"anon\"; key: AnonApiKey };\n\n/**\n * Surfaced when the resolver detected a recoverable anomaly the caller\n * should inform the user about without printing key material.\n *\n * - `malformed-env-local`: `.env.local` set `GLASSTRACE_API_KEY` to a\n * value that fails `DevApiKeySchema`. The resolver fell through.\n * - `claimed-key-only`: the effective credential came from\n * `.glasstrace/claimed-key` because `.env.local` had no usable dev\n * key. Suggest the user copy the key into `.env.local`.\n */\nexport type ResolveWarning = \"malformed-env-local\" | \"claimed-key-only\";\n\n/**\n * The resolved credential plus the on-disk anon key (returned\n * separately so the staleness check does not have to re-read the\n * file) and any warnings the caller should surface to the user.\n */\nexport interface ResolveResult {\n effective: EffectiveMcpCredential | null;\n anonKey: AnonApiKey | null;\n warnings: ReadonlyArray<ResolveWarning>;\n}\n\n/**\n * Resolves the MCP-effective credential for a project, in priority\n * order: `.env.local` `GLASSTRACE_API_KEY` (validated as\n * `DevApiKeySchema`) → `.glasstrace/claimed-key` (validated as\n * `DevApiKeySchema`) → `.glasstrace/anon_key` (`AnonApiKey`). Returns\n * `null` for `effective` only when no source produced a usable key.\n *\n * The function is async because it touches the filesystem. It is\n * called only on the post-claim runtime branch and from the CLI\n * commands `glasstrace init` and `glasstrace mcp add`. It is **not**\n * on the steady-state init path.\n */\nexport async function resolveEffectiveMcpCredential(\n projectRoot?: string,\n): Promise<ResolveResult> {\n const root = projectRoot ?? process.cwd();\n const warnings: ResolveWarning[] = [];\n\n const envLocalKey = await readEnvLocalDevKey(root, warnings);\n const claimedKey = envLocalKey === null ? await readClaimedKey(root) : null;\n const anonKey = await readAnonKey(root);\n\n let effective: EffectiveMcpCredential | null = null;\n if (envLocalKey !== null) {\n effective = { source: \"env-local\", key: envLocalKey };\n } else if (claimedKey !== null) {\n effective = { source: \"claimed-key\", key: claimedKey };\n warnings.push(\"claimed-key-only\");\n } else if (anonKey !== null) {\n effective = { source: \"anon\", key: anonKey };\n }\n\n return { effective, anonKey, warnings };\n}\n\nasync function readEnvLocalDevKey(\n root: string,\n warnings: ResolveWarning[],\n): Promise<DevApiKey | null> {\n const modules = await loadFsPath();\n if (!modules) return null;\n\n const envPath = modules.path.join(root, \".env.local\");\n let content: string;\n try {\n content = await modules.fs.readFile(envPath, \"utf-8\");\n } catch {\n return null;\n }\n\n const raw = readEnvLocalApiKey(content);\n if (raw === null) return null;\n\n const parsed = DevApiKeySchema.safeParse(raw);\n if (!parsed.success) {\n warnings.push(\"malformed-env-local\");\n return null;\n }\n return parsed.data;\n}\n\n/**\n * Source label for the credential a marker file describes.\n *\n * @internal\n */\nexport type MarkerCredentialSource = \"env-local\" | \"claimed-key\" | \"anon\";\n\n/**\n * Descriptor passed to {@link writeMcpMarker} and matched by\n * {@link readMcpMarker}. `credentialHash` is the\n * `identityFingerprint` of the credential actually written into the\n * managed MCP config — never the credential itself.\n *\n * @internal\n */\nexport interface MarkerTarget {\n credentialSource: MarkerCredentialSource;\n credentialHash: string;\n}\n\n/**\n * Normalized state of a `.glasstrace/mcp-connected` marker on disk.\n *\n * - `absent`: no marker file present.\n * - `valid`: a v1 or v2 marker that parsed cleanly. v1 markers are\n * reported as `credentialSource = \"anon\"` with `credentialHash`\n * taken from the legacy `keyHash` field (the v1 schema can only\n * describe an anon credential).\n * - `unknown-version`: the marker has `version > 2`. Treat as\n * not-configured so a future SDK that wrote the marker doesn't\n * block this SDK from refreshing.\n * - `corrupted`: parse failure or schema mismatch. Treat as\n * not-configured.\n *\n * @internal\n */\nexport type MarkerState =\n | { status: \"absent\" }\n | { status: \"valid\"; credentialSource: MarkerCredentialSource; credentialHash: string }\n | { status: \"unknown-version\" }\n | { status: \"corrupted\" };\n\nconst MCP_MARKER_FILE = \"mcp-connected\";\nconst GLASSTRACE_DIR = \".glasstrace\";\n\n/**\n * Reads `.glasstrace/mcp-connected` and returns its normalized state.\n * Used by `mcp add` (marker-mismatch detection) and by\n * {@link writeMcpMarker} (skip-if-match optimization).\n *\n * Reader rules per the design (`SDK-034 D3`):\n * - `version === undefined` → v1: `{ keyHash, configuredAt }`. Mapped\n * to `credentialSource: \"anon\"`, `credentialHash: keyHash`. v1's\n * `keyHash` is itself produced by `identityFingerprint`, so the\n * format matches v2 without conversion.\n * - `version === 2` → v2 reader.\n * - `version > 2` → `unknown-version` (conservative-fail).\n * - Parse failure → `corrupted` (conservative-fail).\n *\n * @internal Exported for unit testing only.\n */\nexport async function readMcpMarker(projectRoot?: string): Promise<MarkerState> {\n const root = projectRoot ?? process.cwd();\n const modules = await loadFsPath();\n if (!modules) return { status: \"absent\" };\n\n const markerPath = modules.path.join(root, GLASSTRACE_DIR, MCP_MARKER_FILE);\n let content: string;\n try {\n content = await modules.fs.readFile(markerPath, \"utf-8\");\n } catch {\n return { status: \"absent\" };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n return { status: \"corrupted\" };\n }\n\n if (parsed === null || typeof parsed !== \"object\") {\n return { status: \"corrupted\" };\n }\n\n const obj = parsed as Record<string, unknown>;\n const version = obj[\"version\"];\n\n if (version === undefined) {\n // v1: { keyHash, configuredAt }\n const keyHash = obj[\"keyHash\"];\n if (typeof keyHash !== \"string\" || keyHash === \"\") {\n return { status: \"corrupted\" };\n }\n return {\n status: \"valid\",\n credentialSource: \"anon\",\n credentialHash: keyHash,\n };\n }\n\n if (version === 2) {\n const source = obj[\"credentialSource\"];\n const hash = obj[\"credentialHash\"];\n if (\n (source !== \"env-local\" && source !== \"claimed-key\" && source !== \"anon\") ||\n typeof hash !== \"string\" ||\n hash === \"\"\n ) {\n return { status: \"corrupted\" };\n }\n return {\n status: \"valid\",\n credentialSource: source,\n credentialHash: hash,\n };\n }\n\n if (typeof version === \"number\" && version > 2) {\n return { status: \"unknown-version\" };\n }\n\n return { status: \"corrupted\" };\n}\n\n/**\n * Writes a v2 `.glasstrace/mcp-connected` marker. Returns `true` when\n * the marker was created or updated, `false` when an existing marker\n * already records the same `(credentialSource, credentialHash)` pair\n * and was left untouched.\n *\n * Writer always emits v2 with `version: 2`. The legacy `keyHash`\n * field is intentionally omitted from new writes — v1 readers ignore\n * unknown fields and the duplicate would diverge over time. v3+ and\n * corrupted markers are unconditionally overwritten.\n *\n * The directory is created with `0o700` and the file with `0o600`,\n * matching existing scaffolder behavior.\n *\n * @internal Exported for unit testing only.\n */\nexport async function writeMcpMarker(\n projectRoot: string,\n target: MarkerTarget,\n): Promise<boolean> {\n const modules = await loadFsPath();\n if (!modules) return false;\n\n const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR);\n const markerPath = modules.path.join(dirPath, MCP_MARKER_FILE);\n\n const state = await readMcpMarker(projectRoot);\n if (\n state.status === \"valid\" &&\n state.credentialSource === target.credentialSource &&\n state.credentialHash === target.credentialHash\n ) {\n return false;\n }\n\n await modules.fs.mkdir(dirPath, { recursive: true, mode: 0o700 });\n\n const body = JSON.stringify(\n {\n version: 2,\n credentialSource: target.credentialSource,\n credentialHash: target.credentialHash,\n configuredAt: new Date().toISOString(),\n },\n null,\n 2,\n );\n\n await modules.fs.writeFile(markerPath, body, { mode: 0o600 });\n // writeFile mode only applies on creation on some platforms.\n await modules.fs.chmod(markerPath, 0o600);\n return true;\n}\n\nconst MCP_CONFIG_FILE = \"mcp.json\";\n\n/**\n * The set of outcomes the runtime claim-refresh helper can produce.\n *\n * - `rewrote`: `.glasstrace/mcp.json` matched the SDK-shaped output\n * for the on-disk anon key, was rewritten with the effective\n * credential, and the marker was updated.\n * - `preserved`: `.glasstrace/mcp.json` exists but does not match the\n * SDK-shaped output for the on-disk anon key. The file is left\n * untouched (the user may have hand-edited it). The marker is not\n * touched.\n * - `absent`: `.glasstrace/mcp.json` does not exist (`ENOENT`), or\n * no anon key is on disk so there is nothing to compare against. A\n * project without an anon key never had an SDK-shaped `mcp.json`\n * written by the runtime path, so this branch is a true no-op.\n * - `skipped-anon-source`: the effective credential is `null` or its\n * source is `\"anon\"`. Either way, there is no claim transition to\n * refresh for. Caller should generally gate on\n * `effective.source !== \"anon\"` before invoking the helper; this\n * branch is the runtime-side belt-and-suspenders.\n * - `skipped-not-persisted`: never reached in practice — the caller\n * in `init-client.ts` gates on `writeClaimedKey`'s `persisted` not\n * being `\"none\"`. The variant exists so an exhaustive switch in\n * the caller stays exhaustive if the gate is removed.\n *\n * @internal\n */\nexport type RuntimeRefreshAction =\n | \"rewrote\"\n | \"preserved\"\n | \"absent\"\n | \"skipped-anon-source\"\n | \"skipped-not-persisted\";\n\nlet refreshNudgeEmitted = false;\n\n/**\n * @internal Exported for unit testing only — resets the per-process\n * \"refresh nudge already emitted\" flag.\n */\nexport function __resetRefreshNudgeForTest(): void {\n refreshNudgeEmitted = false;\n}\n\n/**\n * Emits a single redacted stderr line announcing the MCP config\n * refresh. Deduplicated per process via a module-level flag — a\n * second call within the same process is a no-op. Cross-process\n * dedup (the same user running `mcp add` in another terminal moments\n * later) is explicitly out of scope.\n */\nfunction emitRefreshNudge(persistedSource: \"env-local\" | \"claimed-key\"): void {\n if (refreshNudgeEmitted) return;\n refreshNudgeEmitted = true;\n try {\n if (persistedSource === \"claimed-key\") {\n process.stderr.write(\n \"[glasstrace] MCP config refreshed for the new credential. \" +\n \"Copy .glasstrace/claimed-key into .env.local so Codex can pick it up on next restart.\\n\",\n );\n } else {\n process.stderr.write(\n \"[glasstrace] MCP config refreshed for the new credential.\\n\",\n );\n }\n } catch {\n // stderr is best-effort; refresh outcome must not depend on it.\n }\n}\n\n/**\n * Returns the SDK-shaped JSON for `.glasstrace/mcp.json` (the generic\n * MCP config used at runtime). Inlined here — and intentionally not\n * imported from `agent-detection/configs.ts` — because the runtime\n * path must not pull `agent-detection` into the runtime bundle. The\n * shape matches what `generateMcpConfig({ name: \"generic\", ... },\n * endpoint, bearer)` would produce. If the agent-detection version\n * diverges, the staleness check stops detecting SDK-managed configs;\n * a regression test against `generateMcpConfig`'s \"generic\" branch\n * lives in `tests/unit/sdk/mcp-runtime.test.ts`.\n */\nfunction genericMcpConfigContent(endpoint: string, bearer: string): string {\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n url: endpoint,\n headers: {\n Authorization: `Bearer ${bearer}`,\n },\n },\n },\n },\n null,\n 2,\n );\n}\n\n/**\n * Refreshes `.glasstrace/mcp.json` after a successful account claim\n * transition has persisted a dev/account credential to disk (via\n * `writeClaimedKey`). The file is rewritten only when its content\n * matches the SDK-shaped output for the project's on-disk anon key\n * (canonical-JSON equivalence via `mcpConfigMatches` — whitespace and\n * key order are normalised before comparison). User-edited or\n * third-party `mcp.json` content is preserved.\n *\n * Atomic write protocol: write the replacement to a sibling temp\n * path, set `0o600`, then `rename` into place. This matches the\n * existing pattern at `init-client.ts` for `.glasstrace/config`,\n * `anon-key.ts` for `.glasstrace/anon_key`, and `runtime-state.ts`.\n * The temp must be on the same filesystem as the destination for the\n * `rename` to be atomic.\n *\n * The helper is invoked only on the post-claim runtime branch (see\n * `init-client.ts` `performInit`) and never on the steady-state init\n * path. It must not throw — failures during write/chmod/rename or\n * marker update surface as `\"preserved\"` so the caller's\n * `claimResult` return is preserved. The temp file is best-effort\n * cleaned up on failure to avoid leaving stale `.tmp` siblings on\n * disk.\n *\n * @internal Exported for unit testing only; not re-exported from\n * `node-entry.ts` or `index.ts`.\n */\nexport async function refreshGenericMcpConfigAtRuntime(\n projectRoot: string,\n effective: EffectiveMcpCredential | null,\n anonKeyOnDisk: AnonApiKey | null,\n): Promise<{ action: RuntimeRefreshAction }> {\n if (effective === null || effective.source === \"anon\") {\n return { action: \"skipped-anon-source\" };\n }\n\n // Dev-key-only project (no .glasstrace/anon_key on disk): the\n // staleness check has nothing to compare against. The SDK never\n // wrote mcp.json without an anon key, so there is nothing to\n // refresh.\n if (anonKeyOnDisk === null) {\n return { action: \"absent\" };\n }\n\n const modules = await loadFsPath();\n if (!modules) return { action: \"absent\" };\n\n const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR);\n const configPath = modules.path.join(dirPath, MCP_CONFIG_FILE);\n const tmpPath = configPath + \".tmp\";\n\n let existing: string;\n try {\n existing = await modules.fs.readFile(configPath, \"utf-8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n return { action: \"absent\" };\n }\n return { action: \"preserved\" };\n }\n\n const expectedAnon = genericMcpConfigContent(MCP_ENDPOINT, anonKeyOnDisk);\n if (!mcpConfigMatches(existing, expectedAnon)) {\n return { action: \"preserved\" };\n }\n\n // SDK-managed and stale. Replace atomically. Any failure in the\n // write/chmod/rename or marker update path must produce a non-throw\n // outcome so the caller's claimResult return is preserved; the\n // .tmp sibling is best-effort cleaned up.\n const replacement = genericMcpConfigContent(MCP_ENDPOINT, effective.key);\n try {\n await modules.fs.writeFile(tmpPath, replacement, { mode: 0o600 });\n await modules.fs.chmod(tmpPath, 0o600);\n await modules.fs.rename(tmpPath, configPath);\n\n await writeMcpMarker(projectRoot, {\n credentialSource: effective.source,\n credentialHash: identityFingerprint(effective.key),\n });\n } catch {\n try {\n await modules.fs.unlink(tmpPath);\n } catch {\n // Tmp may not exist (rename succeeded, marker write failed) or\n // unlink itself may fail; either way nothing else to do.\n }\n return { action: \"preserved\" };\n }\n\n emitRefreshNudge(effective.source);\n\n return { action: \"rewrote\" };\n}\n"],"mappings":"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","names":["fsPathCache","loadFsPath","GLASSTRACE_DIR","loadFsPath","GLASSTRACE_DIR"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PresignedUploadResponseSchema,
|
|
6
6
|
SourceMapManifestResponseSchema,
|
|
7
7
|
SourceMapUploadResponseSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X5MAXP5T.js";
|
|
9
9
|
|
|
10
10
|
// src/source-map-uploader.ts
|
|
11
11
|
import * as fs from "node:fs/promises";
|
|
@@ -331,4 +331,4 @@ export {
|
|
|
331
331
|
uploadSourceMapsPresigned,
|
|
332
332
|
uploadSourceMapsAuto
|
|
333
333
|
};
|
|
334
|
-
//# sourceMappingURL=chunk-
|
|
334
|
+
//# sourceMappingURL=chunk-4EZ6JTDG.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-DQ25VOKK.js";
|
|
4
4
|
import {
|
|
5
5
|
GLASSTRACE_ATTRIBUTE_NAMES
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-X5MAXP5T.js";
|
|
7
7
|
|
|
8
8
|
// src/errors.ts
|
|
9
9
|
var SdkError = class extends Error {
|
|
@@ -102,4 +102,4 @@ export {
|
|
|
102
102
|
GlasstraceSpanProcessor,
|
|
103
103
|
captureCorrelationId
|
|
104
104
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
105
|
+
//# sourceMappingURL=chunk-6RNBUUBR.js.map
|