@glasstrace/sdk 0.9.1 → 0.10.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.
@@ -0,0 +1,77 @@
1
+ import {
2
+ AnonApiKeySchema,
3
+ createAnonApiKey
4
+ } from "./chunk-O3Y45VGV.js";
5
+
6
+ // src/anon-key.ts
7
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
8
+ import { join } from "path";
9
+ var GLASSTRACE_DIR = ".glasstrace";
10
+ var ANON_KEY_FILE = "anon_key";
11
+ var ephemeralKeyCache = /* @__PURE__ */ new Map();
12
+ async function readAnonKey(projectRoot) {
13
+ const root = projectRoot ?? process.cwd();
14
+ const keyPath = join(root, GLASSTRACE_DIR, ANON_KEY_FILE);
15
+ try {
16
+ const content = await readFile(keyPath, "utf-8");
17
+ const result = AnonApiKeySchema.safeParse(content);
18
+ if (result.success) {
19
+ return result.data;
20
+ }
21
+ } catch {
22
+ }
23
+ const cached = ephemeralKeyCache.get(root);
24
+ if (cached !== void 0) {
25
+ return cached;
26
+ }
27
+ return null;
28
+ }
29
+ async function getOrCreateAnonKey(projectRoot) {
30
+ const root = projectRoot ?? process.cwd();
31
+ const dirPath = join(root, GLASSTRACE_DIR);
32
+ const keyPath = join(dirPath, ANON_KEY_FILE);
33
+ const existingKey = await readAnonKey(root);
34
+ if (existingKey !== null) {
35
+ return existingKey;
36
+ }
37
+ const cached = ephemeralKeyCache.get(root);
38
+ if (cached !== void 0) {
39
+ return cached;
40
+ }
41
+ const newKey = createAnonApiKey();
42
+ try {
43
+ await mkdir(dirPath, { recursive: true, mode: 448 });
44
+ await writeFile(keyPath, newKey, { flag: "wx", mode: 384 });
45
+ return newKey;
46
+ } catch (err) {
47
+ const code = err.code;
48
+ if (code === "EEXIST") {
49
+ for (let attempt = 0; attempt < 3; attempt++) {
50
+ const winnerKey = await readAnonKey(root);
51
+ if (winnerKey !== null) {
52
+ return winnerKey;
53
+ }
54
+ if (attempt < 2) {
55
+ await new Promise((resolve) => setTimeout(resolve, 50));
56
+ }
57
+ }
58
+ try {
59
+ await writeFile(keyPath, newKey, { mode: 384 });
60
+ await chmod(keyPath, 384);
61
+ return newKey;
62
+ } catch {
63
+ }
64
+ }
65
+ ephemeralKeyCache.set(root, newKey);
66
+ console.warn(
67
+ `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`
68
+ );
69
+ return newKey;
70
+ }
71
+ }
72
+
73
+ export {
74
+ readAnonKey,
75
+ getOrCreateAnonKey
76
+ };
77
+ //# sourceMappingURL=chunk-ZRNG36LU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/anon-key.ts"],"sourcesContent":["import { readFile, writeFile, mkdir, chmod } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { AnonApiKeySchema, createAnonApiKey } from \"@glasstrace/protocol\";\nimport type { AnonApiKey } from \"@glasstrace/protocol\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst ANON_KEY_FILE = \"anon_key\";\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 */\nexport async function readAnonKey(projectRoot?: string): Promise<AnonApiKey | null> {\n const root = projectRoot ?? process.cwd();\n const keyPath = join(root, GLASSTRACE_DIR, ANON_KEY_FILE);\n\n try {\n const content = await 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 // Check in-memory cache (used when filesystem persistence failed)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\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 */\nexport async function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey> {\n const root = projectRoot ?? process.cwd();\n const dirPath = join(root, GLASSTRACE_DIR);\n const keyPath = join(dirPath, ANON_KEY_FILE);\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 // 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 mkdir(dirPath, { recursive: true, mode: 0o700 });\n await 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 writeFile(keyPath, newKey, { mode: 0o600 });\n await 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"],"mappings":";;;;;;AAAA,SAAS,UAAU,WAAW,OAAO,aAAa;AAClD,SAAS,YAAY;AAIrB,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AAMtB,IAAM,oBAAoB,oBAAI,IAAwB;AAStD,eAAsB,YAAY,aAAkD;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AACxC,QAAM,UAAU,KAAK,MAAM,gBAAgB,aAAa;AAExD,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,SAAS,OAAO;AAC/C,UAAM,SAAS,iBAAiB,UAAU,OAAO;AACjD,QAAI,OAAO,SAAS;AAClB,aAAO,OAAO;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,SAAS,kBAAkB,IAAI,IAAI;AACzC,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAWA,eAAsB,mBAAmB,aAA2C;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AACxC,QAAM,UAAU,KAAK,MAAM,cAAc;AACzC,QAAM,UAAU,KAAK,SAAS,aAAa;AAG3C,QAAM,cAAc,MAAM,YAAY,IAAI;AAC1C,MAAI,gBAAgB,MAAM;AACxB,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,kBAAkB,IAAI,IAAI;AACzC,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,iBAAiB;AAIhC,MAAI;AACF,UAAM,MAAM,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACrD,UAAM,UAAU,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAM,CAAC;AAC5D,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AAIrB,eAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,cAAM,YAAY,MAAM,YAAY,IAAI;AACxC,YAAI,cAAc,MAAM;AACtB,iBAAO;AAAA,QACT;AAEA,YAAI,UAAU,GAAG;AACf,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,QACxD;AAAA,MACF;AAIA,UAAI;AACF,cAAM,UAAU,SAAS,QAAQ,EAAE,MAAM,IAAM,CAAC;AAChD,cAAM,MAAM,SAAS,GAAK;AAC1B,eAAO;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF;AAIA,sBAAkB,IAAI,MAAM,MAAM;AAClC,YAAQ;AAAA,MACN,mDAAmD,OAAO,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACjH;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/cli/init.cjs CHANGED
@@ -15658,6 +15658,7 @@ var init_mcp_add = __esm({
15658
15658
  // src/cli/uninit.ts
15659
15659
  var uninit_exports = {};
15660
15660
  __export(uninit_exports, {
15661
+ findMatchingDelimiter: () => findMatchingDelimiter,
15661
15662
  findMatchingParen: () => findMatchingParen,
15662
15663
  isInitCreatedInstrumentation: () => isInitCreatedInstrumentation,
15663
15664
  processJsonMcpConfig: () => processJsonMcpConfig,
@@ -15666,23 +15667,64 @@ __export(uninit_exports, {
15666
15667
  removeMarkerSection: () => removeMarkerSection,
15667
15668
  removeRegisterGlasstrace: () => removeRegisterGlasstrace,
15668
15669
  runUninit: () => runUninit,
15670
+ skipString: () => skipString,
15669
15671
  unwrapCJSExport: () => unwrapCJSExport,
15670
15672
  unwrapExport: () => unwrapExport
15671
15673
  });
15672
- function findMatchingParen(text, openPos) {
15674
+ function skipString(text, start, quote) {
15675
+ let i = start + 1;
15676
+ while (i < text.length) {
15677
+ if (text[i] === "\\") {
15678
+ i += 2;
15679
+ continue;
15680
+ }
15681
+ if (text[i] === quote) {
15682
+ return i + 1;
15683
+ }
15684
+ i++;
15685
+ }
15686
+ return text.length;
15687
+ }
15688
+ function findMatchingDelimiter(text, openPos, openChar, closeChar) {
15673
15689
  let depth = 0;
15674
- for (let i = openPos; i < text.length; i++) {
15675
- if (text[i] === "(") {
15690
+ let i = openPos;
15691
+ while (i < text.length) {
15692
+ const ch = text[i];
15693
+ if (ch === '"' || ch === "'" || ch === "`") {
15694
+ i = skipString(text, i, ch);
15695
+ continue;
15696
+ }
15697
+ if (ch === "/" && text[i + 1] === "/") {
15698
+ const newline = text.indexOf("\n", i);
15699
+ if (newline === -1) {
15700
+ return -1;
15701
+ }
15702
+ i = newline + 1;
15703
+ continue;
15704
+ }
15705
+ if (ch === "/" && text[i + 1] === "*") {
15706
+ const end = text.indexOf("*/", i + 2);
15707
+ if (end === -1) {
15708
+ return -1;
15709
+ }
15710
+ i = end + 2;
15711
+ continue;
15712
+ }
15713
+ if (ch === openChar) {
15676
15714
  depth++;
15677
- } else if (text[i] === ")") {
15715
+ } else if (ch === closeChar) {
15678
15716
  depth--;
15679
15717
  if (depth === 0) {
15680
15718
  return i;
15681
15719
  }
15682
15720
  }
15721
+ i++;
15683
15722
  }
15684
15723
  return -1;
15685
15724
  }
15725
+ function findMatchingParen(text, openPos) {
15726
+ return findMatchingDelimiter(text, openPos, "(", ")");
15727
+ }
15686
15728
  function unwrapExport(content) {
15687
15729
  const pattern = /export\s+default\s+withGlasstraceConfig\s*\(/;
15688
15730
  const match = pattern.exec(content);
@@ -15813,18 +15855,7 @@ function isInitCreatedInstrumentation(content) {
15813
15855
  return topLevelBefore.length === 0 && topLevelAfter.length === 0;
15814
15856
  }
15815
15857
  function findMatchingBrace(text, openPos) {
15816
- let depth = 0;
15817
- for (let i = openPos; i < text.length; i++) {
15818
- if (text[i] === "{") {
15819
- depth++;
15820
- } else if (text[i] === "}") {
15821
- depth--;
15822
- if (depth === 0) {
15823
- return i;
15824
- }
15825
- }
15826
- }
15827
- return -1;
15858
+ return findMatchingDelimiter(text, openPos, "{", "}");
15828
15859
  }
15829
15860
  function removeRegisterGlasstrace(content) {
15830
15861
  let result = content;
@@ -16134,16 +16165,22 @@ async function runUninit(options) {
16134
16165
  if (fs5.existsSync(windsurfConfigPath)) {
16135
16166
  const content = fs5.readFileSync(windsurfConfigPath, "utf-8");
16136
16167
  const windsurfResult = processJsonMcpConfig(content);
16168
+ const home = os.homedir();
16169
+ const displayPath = windsurfConfigPath.startsWith(home) ? "~" + windsurfConfigPath.slice(home.length) : windsurfConfigPath;
16137
16170
  if (windsurfResult.action === "deleted") {
16138
16171
  if (!dryRun) {
16139
16172
  fs5.unlinkSync(windsurfConfigPath);
16140
16173
  }
16141
- summary.push(`${prefix}Deleted Windsurf MCP config`);
16174
+ summary.push(
16175
+ `${prefix}Deleted global Windsurf config (${displayPath})`
16176
+ );
16142
16177
  } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
16143
16178
  if (!dryRun) {
16144
16179
  fs5.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
16145
16180
  }
16146
- summary.push(`${prefix}Removed glasstrace from Windsurf MCP config`);
16181
+ summary.push(
16182
+ `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`
16183
+ );
16147
16184
  }
16148
16185
  }
16149
16186
  }