@glasstrace/sdk 0.14.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +84 -1
  2. package/dist/adapters/drizzle.js +2 -5
  3. package/dist/adapters/drizzle.js.map +1 -1
  4. package/dist/{chunk-PD2SKFQQ.js → chunk-55FBXXER.js} +4 -8
  5. package/dist/{chunk-PD2SKFQQ.js.map → chunk-55FBXXER.js.map} +1 -1
  6. package/dist/chunk-5C2TJFLB.js +851 -0
  7. package/dist/chunk-5C2TJFLB.js.map +1 -0
  8. package/dist/{chunk-YMEXDDTA.js → chunk-7JBKXSBU.js} +3 -99
  9. package/dist/chunk-7JBKXSBU.js.map +1 -0
  10. package/dist/{chunk-2LDBR3F3.js → chunk-BANTDXUT.js} +15 -74
  11. package/dist/chunk-BANTDXUT.js.map +1 -0
  12. package/dist/{chunk-WV3NIPWJ.js → chunk-CTJI2YKA.js} +23 -288
  13. package/dist/chunk-CTJI2YKA.js.map +1 -0
  14. package/dist/{chunk-WK7MPK2T.js → chunk-DQ25VOKK.js} +1 -89
  15. package/dist/chunk-DQ25VOKK.js.map +1 -0
  16. package/dist/{chunk-BL3YDC6V.js → chunk-DXRZKKSO.js} +1 -6
  17. package/dist/{chunk-BL3YDC6V.js.map → chunk-DXRZKKSO.js.map} +1 -1
  18. package/dist/{chunk-BGZ7J74D.js → chunk-NSBPE2FW.js} +2 -16
  19. package/dist/chunk-O63DJKIJ.js +460 -0
  20. package/dist/chunk-O63DJKIJ.js.map +1 -0
  21. package/dist/{chunk-ECEN724Y.js → chunk-TM5NKZTO.js} +4 -8
  22. package/dist/{chunk-ECEN724Y.js.map → chunk-TM5NKZTO.js.map} +1 -1
  23. package/dist/chunk-VUZCLMIX.js +57 -0
  24. package/dist/chunk-VUZCLMIX.js.map +1 -0
  25. package/dist/{chunk-OSXIUKD5.js → chunk-WZXVS2EO.js} +1 -6
  26. package/dist/{chunk-OSXIUKD5.js.map → chunk-WZXVS2EO.js.map} +1 -1
  27. package/dist/{chunk-ARAOZCZT.js → chunk-XNDHQN4S.js} +122 -24
  28. package/dist/chunk-XNDHQN4S.js.map +1 -0
  29. package/dist/cli/init.cjs +1110 -255
  30. package/dist/cli/init.cjs.map +1 -1
  31. package/dist/cli/init.d.cts +86 -1
  32. package/dist/cli/init.d.ts +86 -1
  33. package/dist/cli/init.js +277 -66
  34. package/dist/cli/init.js.map +1 -1
  35. package/dist/cli/mcp-add.cjs +16 -16
  36. package/dist/cli/mcp-add.cjs.map +1 -1
  37. package/dist/cli/mcp-add.js +12 -13
  38. package/dist/cli/mcp-add.js.map +1 -1
  39. package/dist/cli/status.cjs +2 -2
  40. package/dist/cli/status.js +4 -7
  41. package/dist/cli/status.js.map +1 -1
  42. package/dist/cli/uninit.cjs +138 -20
  43. package/dist/cli/uninit.cjs.map +1 -1
  44. package/dist/cli/uninit.d.cts +38 -8
  45. package/dist/cli/uninit.d.ts +38 -8
  46. package/dist/cli/uninit.js +8 -5
  47. package/dist/cli/validate.cjs +135 -0
  48. package/dist/cli/validate.cjs.map +1 -0
  49. package/dist/cli/validate.d.cts +60 -0
  50. package/dist/cli/validate.d.ts +60 -0
  51. package/dist/cli/validate.js +100 -0
  52. package/dist/cli/validate.js.map +1 -0
  53. package/dist/{esm-MDK7CZID.js → esm-KBPHCVB4.js} +3 -3
  54. package/dist/{getMachineId-bsd-4NIRBWME.js → getMachineId-bsd-345PYXFX.js} +4 -7
  55. package/dist/{getMachineId-bsd-4NIRBWME.js.map → getMachineId-bsd-345PYXFX.js.map} +1 -1
  56. package/dist/{getMachineId-darwin-2XNOCCJQ.js → getMachineId-darwin-5L2D25AD.js} +4 -7
  57. package/dist/{getMachineId-darwin-2XNOCCJQ.js.map → getMachineId-darwin-5L2D25AD.js.map} +1 -1
  58. package/dist/{getMachineId-linux-V6YSQEY7.js → getMachineId-linux-KJR4P5HN.js} +3 -6
  59. package/dist/{getMachineId-linux-V6YSQEY7.js.map → getMachineId-linux-KJR4P5HN.js.map} +1 -1
  60. package/dist/{getMachineId-unsupported-4FKBJNVO.js → getMachineId-unsupported-NDNXDYDY.js} +3 -6
  61. package/dist/{getMachineId-unsupported-4FKBJNVO.js.map → getMachineId-unsupported-NDNXDYDY.js.map} +1 -1
  62. package/dist/{getMachineId-win-WLRZBKVG.js → getMachineId-win-T7PJNJXG.js} +4 -7
  63. package/dist/{getMachineId-win-WLRZBKVG.js.map → getMachineId-win-T7PJNJXG.js.map} +1 -1
  64. package/dist/index.cjs +519 -494
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.d.cts +47 -6
  67. package/dist/index.d.ts +47 -6
  68. package/dist/index.js +250 -719
  69. package/dist/index.js.map +1 -1
  70. package/dist/{monorepo-YILKGQXQ.js → monorepo-N5Z63XP7.js} +4 -4
  71. package/dist/{source-map-uploader-3GWUQDTS.js → source-map-uploader-MUZPI2S5.js} +5 -4
  72. package/dist/source-map-uploader-MUZPI2S5.js.map +1 -0
  73. package/package.json +6 -4
  74. package/dist/chunk-2LDBR3F3.js.map +0 -1
  75. package/dist/chunk-ARAOZCZT.js.map +0 -1
  76. package/dist/chunk-BGZ7J74D.js.map +0 -1
  77. package/dist/chunk-UPS5BGER.js +0 -182
  78. package/dist/chunk-UPS5BGER.js.map +0 -1
  79. package/dist/chunk-WK7MPK2T.js.map +0 -1
  80. package/dist/chunk-WV3NIPWJ.js.map +0 -1
  81. package/dist/chunk-YMEXDDTA.js.map +0 -1
  82. /package/dist/{esm-MDK7CZID.js.map → chunk-NSBPE2FW.js.map} +0 -0
  83. /package/dist/{monorepo-YILKGQXQ.js.map → esm-KBPHCVB4.js.map} +0 -0
  84. /package/dist/{source-map-uploader-3GWUQDTS.js.map → monorepo-N5Z63XP7.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/anon-key.ts"],"sourcesContent":["import { 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 * 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 * 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"],"mappings":";;;;;;;;;AAAA;AAGA,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AAOtB,IAAI;AAEJ,eAAe,aAA0G;AACvH,MAAI,gBAAgB,OAAW,QAAO;AACtC,MAAI;AACF,UAAM,CAAC,IAAI,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnC,OAAO,aAAkB;AAAA,MACzB,OAAO,MAAW;AAAA,IACpB,CAAC;AACD,kBAAc,EAAE,IAAI,KAAK;AACzB,WAAO;AAAA,EACT,QAAQ;AACN,kBAAc;AACd,WAAO;AAAA,EACT;AACF;AAMA,IAAM,oBAAoB,oBAAI,IAAwB;AAUtD,eAAsB,YAAY,aAAkD;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AAExC,QAAM,UAAU,MAAM,WAAW;AACjC,MAAI,SAAS;AACX,UAAM,UAAU,QAAQ,KAAK,KAAK,MAAM,gBAAgB,aAAa;AACrE,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,GAAG,SAAS,SAAS,OAAO;AAC1D,YAAM,SAAS,iBAAiB,UAAU,OAAO;AACjD,UAAI,OAAO,SAAS;AAClB,eAAO,OAAO;AAAA,MAChB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAIA,QAAM,SAAS,kBAAkB,IAAI,IAAI;AACzC,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAYA,eAAsB,mBAAmB,aAA2C;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AAGxC,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;AAGhC,QAAM,UAAU,MAAM,WAAW;AACjC,MAAI,CAAC,SAAS;AAEZ,sBAAkB,IAAI,MAAM,MAAM;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,QAAQ,KAAK,KAAK,MAAM,cAAc;AACtD,QAAM,UAAU,QAAQ,KAAK,KAAK,SAAS,aAAa;AAIxD,MAAI;AACF,UAAM,QAAQ,GAAG,MAAM,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAChE,UAAM,QAAQ,GAAG,UAAU,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAM,CAAC;AACvE,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,QAAQ,GAAG,UAAU,SAAS,QAAQ,EAAE,MAAM,IAAM,CAAC;AAC3D,cAAM,QAAQ,GAAG,MAAM,SAAS,GAAK;AACrC,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":[]}
1
+ {"version":3,"sources":["../src/anon-key.ts"],"sourcesContent":["import { 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 * 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 * 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"],"mappings":";;;;;;AAGA,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AAOtB,IAAI;AAEJ,eAAe,aAA0G;AACvH,MAAI,gBAAgB,OAAW,QAAO;AACtC,MAAI;AACF,UAAM,CAAC,IAAI,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnC,OAAO,kBAAkB;AAAA,MACzB,OAAO,WAAW;AAAA,IACpB,CAAC;AACD,kBAAc,EAAE,IAAI,KAAK;AACzB,WAAO;AAAA,EACT,QAAQ;AACN,kBAAc;AACd,WAAO;AAAA,EACT;AACF;AAMA,IAAM,oBAAoB,oBAAI,IAAwB;AAUtD,eAAsB,YAAY,aAAkD;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AAExC,QAAM,UAAU,MAAM,WAAW;AACjC,MAAI,SAAS;AACX,UAAM,UAAU,QAAQ,KAAK,KAAK,MAAM,gBAAgB,aAAa;AACrE,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,GAAG,SAAS,SAAS,OAAO;AAC1D,YAAM,SAAS,iBAAiB,UAAU,OAAO;AACjD,UAAI,OAAO,SAAS;AAClB,eAAO,OAAO;AAAA,MAChB;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAIA,QAAM,SAAS,kBAAkB,IAAI,IAAI;AACzC,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAYA,eAAsB,mBAAmB,aAA2C;AAClF,QAAM,OAAO,eAAe,QAAQ,IAAI;AAGxC,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;AAGhC,QAAM,UAAU,MAAM,WAAW;AACjC,MAAI,CAAC,SAAS;AAEZ,sBAAkB,IAAI,MAAM,MAAM;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,QAAQ,KAAK,KAAK,MAAM,cAAc;AACtD,QAAM,UAAU,QAAQ,KAAK,KAAK,SAAS,aAAa;AAIxD,MAAI;AACF,UAAM,QAAQ,GAAG,MAAM,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAChE,UAAM,QAAQ,GAAG,UAAU,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAM,IAAM,CAAC;AACvE,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,QAAQ,GAAG,UAAU,SAAS,QAAQ,EAAE,MAAM,IAAM,CAAC;AAC3D,cAAM,QAAQ,GAAG,MAAM,SAAS,GAAK;AACrC,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":[]}
@@ -0,0 +1,57 @@
1
+ // src/env-detection.ts
2
+ var DEFAULT_ENDPOINT = "https://api.glasstrace.dev";
3
+ function readEnvVars() {
4
+ return {
5
+ GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || void 0,
6
+ GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,
7
+ GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,
8
+ GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,
9
+ NODE_ENV: process.env.NODE_ENV,
10
+ VERCEL_ENV: process.env.VERCEL_ENV
11
+ };
12
+ }
13
+ function resolveConfig(options) {
14
+ const env = readEnvVars();
15
+ return {
16
+ apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,
17
+ endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,
18
+ forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === "true",
19
+ verbose: options?.verbose ?? false,
20
+ environment: env.GLASSTRACE_ENV,
21
+ coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === "true",
22
+ nodeEnv: env.NODE_ENV,
23
+ vercelEnv: env.VERCEL_ENV
24
+ };
25
+ }
26
+ function isProductionDisabled(config) {
27
+ if (config.forceEnable) {
28
+ return false;
29
+ }
30
+ if (config.nodeEnv === "production") {
31
+ return true;
32
+ }
33
+ if (config.vercelEnv === "production") {
34
+ return true;
35
+ }
36
+ return false;
37
+ }
38
+ function isAnonymousMode(config) {
39
+ if (config.apiKey === void 0) {
40
+ return true;
41
+ }
42
+ if (config.apiKey.trim() === "") {
43
+ return true;
44
+ }
45
+ if (config.apiKey.startsWith("gt_anon_")) {
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+
51
+ export {
52
+ readEnvVars,
53
+ resolveConfig,
54
+ isProductionDisabled,
55
+ isAnonymousMode
56
+ };
57
+ //# sourceMappingURL=chunk-VUZCLMIX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/env-detection.ts"],"sourcesContent":["import type { GlasstraceEnvVars, GlasstraceOptions } from \"@glasstrace/protocol\";\n\n/**\n * Resolved configuration after merging explicit options with environment variables.\n */\nexport interface ResolvedConfig {\n apiKey: string | undefined;\n endpoint: string;\n forceEnable: boolean;\n verbose: boolean;\n environment: string | undefined;\n coverageMapEnabled: boolean;\n nodeEnv: string | undefined;\n vercelEnv: string | undefined;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.glasstrace.dev\";\n\n/**\n * Reads all recognized Glasstrace environment variables from process.env.\n * Returns undefined for any variable not set. Never throws.\n */\nexport function readEnvVars(): GlasstraceEnvVars {\n return {\n GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || undefined,\n GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,\n GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,\n GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,\n NODE_ENV: process.env.NODE_ENV,\n VERCEL_ENV: process.env.VERCEL_ENV,\n };\n}\n\n/**\n * Merges explicit GlasstraceOptions with environment variables.\n * Explicit options take precedence over environment variables.\n */\nexport function resolveConfig(options?: GlasstraceOptions): ResolvedConfig {\n const env = readEnvVars();\n\n return {\n apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,\n endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,\n forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === \"true\",\n verbose: options?.verbose ?? false,\n environment: env.GLASSTRACE_ENV,\n coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === \"true\",\n nodeEnv: env.NODE_ENV,\n vercelEnv: env.VERCEL_ENV,\n };\n}\n\n/**\n * Returns true when the SDK should be inactive (production detected without force-enable).\n * Logic order:\n * 1. forceEnable === true → return false (override)\n * 2. NODE_ENV === 'production' → return true\n * 3. VERCEL_ENV === 'production' → return true\n * 4. Otherwise → return false\n */\nexport function isProductionDisabled(config: ResolvedConfig): boolean {\n if (config.forceEnable) {\n return false;\n }\n if (config.nodeEnv === \"production\") {\n return true;\n }\n if (config.vercelEnv === \"production\") {\n return true;\n }\n return false;\n}\n\n/**\n * Returns true when no API key is configured (anonymous mode).\n * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.\n */\nexport function isAnonymousMode(config: ResolvedConfig): boolean {\n if (config.apiKey === undefined) {\n return true;\n }\n if (config.apiKey.trim() === \"\") {\n return true;\n }\n if (config.apiKey.startsWith(\"gt_anon_\")) {\n return true;\n }\n return false;\n}\n"],"mappings":";AAgBA,IAAM,mBAAmB;AAMlB,SAAS,cAAiC;AAC/C,SAAO;AAAA,IACL,oBAAoB,QAAQ,IAAI,oBAAoB,KAAK,KAAK;AAAA,IAC9D,yBAAyB,QAAQ,IAAI;AAAA,IACrC,gBAAgB,QAAQ,IAAI;AAAA,IAC5B,yBAAyB,QAAQ,IAAI;AAAA,IACrC,UAAU,QAAQ,IAAI;AAAA,IACtB,YAAY,QAAQ,IAAI;AAAA,EAC1B;AACF;AAMO,SAAS,cAAc,SAA6C;AACzE,QAAM,MAAM,YAAY;AAExB,SAAO;AAAA,IACL,QAAQ,SAAS,UAAU,IAAI;AAAA,IAC/B,UAAU,SAAS,YAAY;AAAA,IAC/B,aAAa,SAAS,eAAe,IAAI,4BAA4B;AAAA,IACrE,SAAS,SAAS,WAAW;AAAA,IAC7B,aAAa,IAAI;AAAA,IACjB,oBAAoB,IAAI,4BAA4B;AAAA,IACpD,SAAS,IAAI;AAAA,IACb,WAAW,IAAI;AAAA,EACjB;AACF;AAUO,SAAS,qBAAqB,QAAiC;AACpE,MAAI,OAAO,aAAa;AACtB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,cAAc;AACnC,WAAO;AAAA,EACT;AACA,MAAI,OAAO,cAAc,cAAc;AACrC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMO,SAAS,gBAAgB,QAAiC;AAC/D,MAAI,OAAO,WAAW,QAAW;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,OAAO,KAAK,MAAM,IAAI;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,OAAO,WAAW,UAAU,GAAG;AACxC,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
@@ -1,9 +1,4 @@
1
- import {
2
- init_esm_shims
3
- } from "./chunk-BGZ7J74D.js";
4
-
5
1
  // ../../node_modules/@opentelemetry/resources/build/esm/detectors/platform/node/machine-id/execAsync.js
6
- init_esm_shims();
7
2
  import * as child_process from "child_process";
8
3
  import * as util from "util";
9
4
  var execAsync = util.promisify(child_process.exec);
@@ -11,4 +6,4 @@ var execAsync = util.promisify(child_process.exec);
11
6
  export {
12
7
  execAsync
13
8
  };
14
- //# sourceMappingURL=chunk-OSXIUKD5.js.map
9
+ //# sourceMappingURL=chunk-WZXVS2EO.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../node_modules/@opentelemetry/resources/src/detectors/platform/node/machine-id/execAsync.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport * as child_process from 'child_process';\nimport * as util from 'util';\n\nexport const execAsync = util.promisify(child_process.exec);\n"],"mappings":";;;;;AAAA;AAKA,YAAY,mBAAmB;AAC/B,YAAY,UAAU;AAEf,IAAM,YAAiB,eAAwB,kBAAI;","names":[]}
1
+ {"version":3,"sources":["../../../node_modules/@opentelemetry/resources/src/detectors/platform/node/machine-id/execAsync.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport * as child_process from 'child_process';\nimport * as util from 'util';\n\nexport const execAsync = util.promisify(child_process.exec);\n"],"mappings":";AAKA,YAAY,mBAAmB;AAC/B,YAAY,UAAU;AAEf,IAAM,YAAiB,eAAwB,kBAAI;","names":[]}
@@ -1,15 +1,15 @@
1
1
  import {
2
- NEXT_CONFIG_NAMES
3
- } from "./chunk-BL3YDC6V.js";
2
+ isDevApiKey,
3
+ readEnvLocalApiKey
4
+ } from "./chunk-O63DJKIJ.js";
4
5
  import {
5
- init_esm_shims
6
- } from "./chunk-BGZ7J74D.js";
6
+ NEXT_CONFIG_NAMES
7
+ } from "./chunk-DXRZKKSO.js";
7
8
 
8
9
  // src/cli/uninit.ts
9
- init_esm_shims();
10
- import * as fs from "fs";
11
- import * as os from "os";
12
- import * as path from "path";
10
+ import * as fs from "node:fs";
11
+ import * as os from "node:os";
12
+ import * as path from "node:path";
13
13
  var MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
14
14
  var AGENT_INFO_FILES = [
15
15
  "CLAUDE.md",
@@ -315,12 +315,75 @@ function processTomlMcpConfig(content) {
315
315
  }
316
316
  return { action: "removed-section", content: result + "\n" };
317
317
  }
318
+ function writeShutdownMarker(projectRoot) {
319
+ const dirPath = path.join(projectRoot, ".glasstrace");
320
+ if (!fs.existsSync(dirPath)) {
321
+ return false;
322
+ }
323
+ const markerPath = path.join(dirPath, "shutdown-requested");
324
+ const tmpPath = `${markerPath}.tmp`;
325
+ const body = JSON.stringify({ requestedAt: (/* @__PURE__ */ new Date()).toISOString() });
326
+ try {
327
+ fs.writeFileSync(tmpPath, body, { encoding: "utf-8", mode: 384 });
328
+ try {
329
+ fs.chmodSync(tmpPath, 384);
330
+ } catch {
331
+ }
332
+ fs.renameSync(tmpPath, markerPath);
333
+ return true;
334
+ } catch {
335
+ try {
336
+ fs.unlinkSync(tmpPath);
337
+ } catch {
338
+ }
339
+ return false;
340
+ }
341
+ }
342
+ async function defaultPrompt(question, defaultValue) {
343
+ if (!process.stdin.isTTY) return defaultValue;
344
+ const readline = await import("node:readline");
345
+ const rl = readline.createInterface({
346
+ input: process.stdin,
347
+ output: process.stdout
348
+ });
349
+ return new Promise((resolve) => {
350
+ const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
351
+ rl.question(question + suffix, (answer) => {
352
+ rl.close();
353
+ const trimmed = answer.trim().toLowerCase();
354
+ if (trimmed === "") {
355
+ resolve(defaultValue);
356
+ return;
357
+ }
358
+ resolve(trimmed === "y" || trimmed === "yes");
359
+ });
360
+ });
361
+ }
318
362
  async function runUninit(options) {
319
363
  const { projectRoot, dryRun } = options;
364
+ const force = options.force === true;
365
+ const prompt = options.prompt ?? defaultPrompt;
320
366
  const summary = [];
321
367
  const warnings = [];
322
368
  const errors = [];
323
369
  const prefix = dryRun ? "[dry run] " : "";
370
+ try {
371
+ if (!dryRun) {
372
+ const markerWritten = writeShutdownMarker(projectRoot);
373
+ if (markerWritten) {
374
+ summary.push("Wrote .glasstrace/shutdown-requested marker");
375
+ }
376
+ } else {
377
+ const dirPath = path.join(projectRoot, ".glasstrace");
378
+ if (fs.existsSync(dirPath)) {
379
+ summary.push(`${prefix}Would write .glasstrace/shutdown-requested marker`);
380
+ }
381
+ }
382
+ } catch (err) {
383
+ warnings.push(
384
+ `Shutdown marker write failed: ${err instanceof Error ? err.message : String(err)}`
385
+ );
386
+ }
324
387
  try {
325
388
  let configHandled = false;
326
389
  for (const name of NEXT_CONFIG_NAMES) {
@@ -403,23 +466,57 @@ async function runUninit(options) {
403
466
  const envPath = path.join(projectRoot, ".env.local");
404
467
  if (fs.existsSync(envPath)) {
405
468
  const content = fs.readFileSync(envPath, "utf-8");
406
- const lines = content.split("\n");
407
- const filtered = lines.filter((line) => {
408
- const trimmed = line.trim();
409
- return !(/^\s*#?\s*GLASSTRACE_API_KEY\s*=/.test(trimmed) || /^\s*#?\s*GLASSTRACE_COVERAGE_MAP\s*=/.test(trimmed));
410
- });
411
- if (filtered.length !== lines.length) {
412
- const result = filtered.join("\n");
413
- if (result.trim().length === 0) {
414
- if (!dryRun) {
415
- fs.unlinkSync(envPath);
416
- }
417
- summary.push(`${prefix}Deleted .env.local (no remaining entries)`);
469
+ const existingKey = readEnvLocalApiKey(content);
470
+ const hasDevKey = isDevApiKey(existingKey);
471
+ let proceed = true;
472
+ let devKeyPath = "none";
473
+ if (hasDevKey) {
474
+ if (dryRun) {
475
+ devKeyPath = "dry-run-preview";
476
+ } else if (force) {
477
+ devKeyPath = "force-bypass";
418
478
  } else {
419
- if (!dryRun) {
420
- fs.writeFileSync(envPath, result, "utf-8");
479
+ const confirmed = await prompt(
480
+ ".env.local contains a claimed Glasstrace developer API key (gt_dev_...). Removing it will require you to re-authenticate. Continue?",
481
+ false
482
+ );
483
+ proceed = confirmed;
484
+ if (confirmed) devKeyPath = "interactive-confirmed";
485
+ }
486
+ }
487
+ if (!proceed) {
488
+ warnings.push(
489
+ "Preserved GLASSTRACE_API_KEY in .env.local (claimed dev key; re-run with --force to remove)"
490
+ );
491
+ } else {
492
+ const lines = content.split("\n");
493
+ const filtered = lines.filter((line) => {
494
+ const trimmed = line.trim();
495
+ return !(/^\s*#?\s*GLASSTRACE_API_KEY\s*=/.test(trimmed) || /^\s*#?\s*GLASSTRACE_COVERAGE_MAP\s*=/.test(trimmed));
496
+ });
497
+ if (filtered.length !== lines.length) {
498
+ const result = filtered.join("\n");
499
+ if (result.trim().length === 0) {
500
+ if (!dryRun) {
501
+ fs.unlinkSync(envPath);
502
+ }
503
+ summary.push(`${prefix}Deleted .env.local (no remaining entries)`);
504
+ } else {
505
+ if (!dryRun) {
506
+ fs.writeFileSync(envPath, result, "utf-8");
507
+ }
508
+ let devKeyAnnotation = "";
509
+ if (devKeyPath === "interactive-confirmed") {
510
+ devKeyAnnotation = " (dev key confirmed)";
511
+ } else if (devKeyPath === "force-bypass") {
512
+ devKeyAnnotation = " (dev key removed via --force)";
513
+ } else if (devKeyPath === "dry-run-preview") {
514
+ devKeyAnnotation = " (dev key would be removed; real run would require confirmation)";
515
+ }
516
+ summary.push(
517
+ `${prefix}Removed GLASSTRACE entries from .env.local${devKeyAnnotation}`
518
+ );
421
519
  }
422
- summary.push(`${prefix}Removed GLASSTRACE entries from .env.local`);
423
520
  }
424
521
  }
425
522
  }
@@ -579,6 +676,7 @@ export {
579
676
  removeMarkerSection,
580
677
  processJsonMcpConfig,
581
678
  processTomlMcpConfig,
679
+ writeShutdownMarker,
582
680
  runUninit
583
681
  };
584
- //# sourceMappingURL=chunk-ARAOZCZT.js.map
682
+ //# sourceMappingURL=chunk-XNDHQN4S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/uninit.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { NEXT_CONFIG_NAMES } from \"./constants.js\";\nimport { readEnvLocalApiKey, isDevApiKey } from \"./scaffolder.js\";\n\n/**\n * Options for the uninit command.\n */\nexport interface UninitOptions {\n projectRoot: string;\n dryRun: boolean;\n /**\n * When true, skip interactive confirmation before destructive actions\n * such as removing a claimed developer API key from `.env.local`\n * (DISC-1247 Scenario 6).\n */\n force?: boolean;\n /**\n * Optional prompt callback; when omitted, uninit uses a TTY-based\n * `readline` prompt in interactive mode and defaults to `false`\n * (abort) when no TTY is attached. Exposed for testing.\n */\n prompt?: (question: string, defaultValue: boolean) => Promise<boolean>;\n}\n\n/**\n * Result of running the uninit command.\n */\nexport interface UninitResult {\n exitCode: number;\n summary: string[];\n warnings: string[];\n errors: string[];\n}\n\n/**\n * MCP config files that init may create.\n * These are JSON files containing `mcpServers.glasstrace`.\n */\nconst MCP_CONFIG_FILES = [\".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\"] as const;\n\n/**\n * Agent info files that may contain glasstrace marker sections.\n * Both HTML-style (`<!-- glasstrace:mcp:start -->`) and hash-style\n * (`# glasstrace:mcp:start`) markers are supported.\n */\nconst AGENT_INFO_FILES = [\n \"CLAUDE.md\",\n \"codex.md\",\n \".cursorrules\",\n] as const;\n\n/**\n * Advances past a string literal (double-quoted, single-quoted, or template\n * literal), respecting backslash escapes.\n *\n * Note: Template literals with `${...}` interpolations containing nested\n * backticks are not fully supported — the scanner stops at the first\n * unescaped backtick. This is acceptable because config files (the primary\n * use case for `findMatchingParen`/`findMatchingBrace`) do not use nested\n * template literals.\n *\n * @param text - The source text.\n * @param start - The index of the opening quote character.\n * @param quote - The quote character (`\"`, `'`, or `` ` ``).\n * @returns The index immediately after the closing quote.\n * @internal Exported for unit testing only.\n */\nexport function skipString(text: string, start: number, quote: string): number {\n let i = start + 1;\n while (i < text.length) {\n if (text[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (text[i] === quote) {\n return i + 1;\n }\n i++;\n }\n return text.length;\n}\n\n/**\n * Finds the matching closing delimiter for an opening delimiter at the given\n * position, accounting for nesting and skipping delimiters that appear inside\n * string literals (`\"`, `'`, `` ` ``), single-line comments (`//`), and block\n * comments.\n *\n * @param text - The source text to search.\n * @param openPos - The index of the opening delimiter.\n * @param openChar - The opening delimiter character (e.g., `(` or `{`).\n * @param closeChar - The closing delimiter character (e.g., `)` or `}`).\n * @returns The index of the matching closing delimiter, or -1 if not found.\n * @internal Exported for unit testing only.\n */\nexport function findMatchingDelimiter(\n text: string,\n openPos: number,\n openChar: string,\n closeChar: string,\n): number {\n let depth = 0;\n let i = openPos;\n while (i < text.length) {\n const ch = text[i];\n\n // Skip string literals\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n i = skipString(text, i, ch);\n continue;\n }\n\n // Skip single-line comments.\n // Note: This may misidentify regex literals containing `//` (e.g.,\n // `/api\\//`). Config files — the primary use case — do not contain\n // regex literals, so this trade-off is acceptable.\n if (ch === \"/\" && text[i + 1] === \"/\") {\n const newline = text.indexOf(\"\\n\", i);\n if (newline === -1) {\n return -1;\n }\n i = newline + 1;\n continue;\n }\n\n // Skip block comments\n if (ch === \"/\" && text[i + 1] === \"*\") {\n const end = text.indexOf(\"*/\", i + 2);\n if (end === -1) {\n return -1;\n }\n i = end + 2;\n continue;\n }\n\n if (ch === openChar) {\n depth++;\n } else if (ch === closeChar) {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n i++;\n }\n return -1;\n}\n\n/**\n * Finds the matching closing parenthesis for an opening paren at the given\n * position, accounting for nested parentheses and skipping delimiters inside\n * string literals and comments.\n *\n * @param text - The source text to search.\n * @param openPos - The index of the opening `(`.\n * @returns The index of the matching `)`, or -1 if not found.\n * @internal Exported for unit testing only.\n */\nexport function findMatchingParen(text: string, openPos: number): number {\n return findMatchingDelimiter(text, openPos, \"(\", \")\");\n}\n\n/**\n * Removes the `withGlasstraceConfig(...)` wrapper from an ESM default export,\n * restoring the inner expression.\n *\n * Before: `export default withGlasstraceConfig(innerExpr);`\n * After: `export default innerExpr;`\n *\n * @internal Exported for unit testing only.\n */\nexport function unwrapExport(content: string): { content: string; unwrapped: boolean } {\n const pattern = /export\\s+default\\s+withGlasstraceConfig\\s*\\(/;\n const match = pattern.exec(content);\n if (!match) {\n return { content, unwrapped: false };\n }\n\n // Find the opening paren of withGlasstraceConfig(\n const openParenIdx = match.index + match[0].length - 1;\n const closeParenIdx = findMatchingParen(content, openParenIdx);\n if (closeParenIdx === -1) {\n return { content, unwrapped: false };\n }\n\n const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();\n if (innerExpr.length === 0) {\n return { content, unwrapped: false };\n }\n\n // Everything before `export default ...`\n const before = content.slice(0, match.index);\n // Everything after the closing `)` (skip optional semicolon and trailing whitespace)\n const afterClose = content.slice(closeParenIdx + 1);\n const trailing = afterClose.replace(/^;?\\s*/, \"\");\n\n const result = before + `export default ${innerExpr};\\n` + trailing;\n\n return { content: result, unwrapped: true };\n}\n\n/**\n * Removes the `withGlasstraceConfig(...)` wrapper from a CJS module.exports,\n * restoring the inner expression.\n *\n * Before: `module.exports = withGlasstraceConfig(innerExpr);`\n * After: `module.exports = innerExpr;`\n *\n * @internal Exported for unit testing only.\n */\nexport function unwrapCJSExport(content: string): { content: string; unwrapped: boolean } {\n const pattern = /module\\.exports\\s*=\\s*withGlasstraceConfig\\s*\\(/;\n const match = pattern.exec(content);\n if (!match) {\n return { content, unwrapped: false };\n }\n\n const openParenIdx = match.index + match[0].length - 1;\n const closeParenIdx = findMatchingParen(content, openParenIdx);\n if (closeParenIdx === -1) {\n return { content, unwrapped: false };\n }\n\n const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();\n if (innerExpr.length === 0) {\n return { content, unwrapped: false };\n }\n\n const before = content.slice(0, match.index);\n const afterClose = content.slice(closeParenIdx + 1);\n const trailing = afterClose.replace(/^;?\\s*/, \"\");\n\n const result = before + `module.exports = ${innerExpr};\\n` + trailing;\n\n return { content: result, unwrapped: true };\n}\n\n/**\n * Removes the `import { withGlasstraceConfig } from \"@glasstrace/sdk\"` line\n * from file content. If `withGlasstraceConfig` is the only imported specifier,\n * the entire import line is removed. If other specifiers exist, only\n * `withGlasstraceConfig` is removed from the specifier list.\n *\n * @internal Exported for unit testing only.\n */\nexport function removeGlasstraceConfigImport(content: string): string {\n // ESM: import { withGlasstraceConfig } from \"@glasstrace/sdk\"\n const esmSoleImport =\n /import\\s*\\{\\s*withGlasstraceConfig\\s*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/;\n if (esmSoleImport.test(content)) {\n return content.replace(esmSoleImport, \"\");\n }\n\n // ESM with multiple specifiers — remove withGlasstraceConfig from the list\n const esmMultiImport =\n /import\\s*\\{([^}]*)\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']/;\n const multiMatch = esmMultiImport.exec(content);\n if (multiMatch) {\n const specifiers = multiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"withGlasstraceConfig\");\n if (specifiers.length === 0) {\n // All specifiers were withGlasstraceConfig — remove entire import\n return content.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/,\n \"\",\n );\n }\n const newImport = `import { ${specifiers.join(\", \")} } from \"@glasstrace/sdk\"`;\n return content.replace(multiMatch[0], newImport);\n }\n\n // CJS: const { withGlasstraceConfig } = require(\"@glasstrace/sdk\")\n const cjsSoleRequire =\n /const\\s*\\{\\s*withGlasstraceConfig\\s*\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)\\s*;?\\s*\\n?/;\n if (cjsSoleRequire.test(content)) {\n return content.replace(cjsSoleRequire, \"\");\n }\n\n // CJS with multiple specifiers\n const cjsMultiRequire =\n /const\\s*\\{([^}]*)\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)/;\n const cjsMultiMatch = cjsMultiRequire.exec(content);\n if (cjsMultiMatch) {\n const specifiers = cjsMultiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"withGlasstraceConfig\");\n if (specifiers.length === 0) {\n return content.replace(\n /const\\s*\\{[^}]*\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)\\s*;?\\s*\\n?/,\n \"\",\n );\n }\n const newRequire = `const { ${specifiers.join(\", \")} } = require(\"@glasstrace/sdk\")`;\n return content.replace(cjsMultiMatch[0], newRequire);\n }\n\n return content;\n}\n\n/**\n * Removes blank lines that appear consecutively (more than one empty line\n * in a row) at the top of a file, which can occur after removing import lines.\n */\nfunction cleanLeadingBlankLines(content: string): string {\n return content.replace(/^\\n{2,}/, \"\\n\");\n}\n\n/**\n * Determines whether an instrumentation.ts file was created by `glasstrace init`\n * (i.e., contains only the standard template with no user-added code).\n *\n * A file is considered init-created if:\n * - The only import from any package is `@glasstrace/sdk`\n * - The only meaningful statement in `register()` is `registerGlasstrace()`\n * - There are no other top-level statements, exports, or declarations outside\n * the register function (prevents deleting files where users added their own code)\n *\n * @internal Exported for unit testing only.\n */\nexport function isInitCreatedInstrumentation(content: string): boolean {\n const lines = content.split(\"\\n\");\n\n // Check that all imports are from @glasstrace/sdk\n const importLines = lines.filter(\n (l) => /^\\s*import\\s/.test(l) && !l.trim().startsWith(\"//\"),\n );\n const nonGlasstraceImports = importLines.filter(\n (l) => !l.includes(\"@glasstrace/sdk\"),\n );\n if (nonGlasstraceImports.length > 0) {\n return false;\n }\n\n // Check that the register() function body only contains registerGlasstrace()\n // and comments — no other meaningful statements\n const registerFnRegex = /export\\s+(?:async\\s+)?function\\s+register\\s*\\([^)]*\\)\\s*\\{/;\n const match = registerFnRegex.exec(content);\n if (!match) {\n // No register function — not a standard init template\n return false;\n }\n\n // Extract the function body\n const afterBrace = content.slice(match.index + match[0].length);\n const closingBraceIdx = findMatchingBrace(content, match.index + match[0].length - 1);\n if (closingBraceIdx === -1) {\n return false;\n }\n\n const body = afterBrace.slice(0, closingBraceIdx - (match.index + match[0].length));\n const bodyLines = body.split(\"\\n\");\n\n // Filter out comments and blank lines — only meaningful statements remain\n const statements = bodyLines.filter((l) => {\n const trimmed = l.trim();\n return trimmed !== \"\" && !trimmed.startsWith(\"//\");\n });\n\n // The only statement should be registerGlasstrace()\n if (statements.length !== 1) {\n return false;\n }\n if (!/^\\s*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*$/.test(statements[0])) {\n return false;\n }\n\n // Verify no other top-level code exists outside imports and the register function.\n // Extract everything that isn't an import line or inside the register() function.\n const beforeFn = content.slice(0, match.index);\n const afterFn = content.slice(closingBraceIdx + 1);\n\n const topLevelBefore = beforeFn.split(\"\\n\").filter((l) => {\n const trimmed = l.trim();\n return (\n trimmed !== \"\" &&\n !trimmed.startsWith(\"//\") &&\n !trimmed.startsWith(\"import \") &&\n !trimmed.startsWith(\"import{\")\n );\n });\n\n const topLevelAfter = afterFn.split(\"\\n\").filter((l) => {\n const trimmed = l.trim();\n return trimmed !== \"\" && !trimmed.startsWith(\"//\");\n });\n\n return topLevelBefore.length === 0 && topLevelAfter.length === 0;\n}\n\n/**\n * Finds the matching closing brace for an opening brace at the given position,\n * skipping delimiters inside string literals and comments.\n */\nfunction findMatchingBrace(text: string, openPos: number): number {\n return findMatchingDelimiter(text, openPos, \"{\", \"}\");\n}\n\n/**\n * Removes the `registerGlasstrace()` call and its `@glasstrace/sdk` import\n * from an instrumentation.ts file, preserving all other code.\n *\n * @internal Exported for unit testing only.\n */\nexport function removeRegisterGlasstrace(content: string): string {\n let result = content;\n\n // Remove all comment-block + registerGlasstrace() call pairs.\n // The init template creates a multi-line comment block before the call:\n // // Glasstrace must be registered before Prisma instrumentation\n // // to ensure all ORM spans are captured correctly.\n // // If you use @prisma/instrumentation, import it after this call.\n // registerGlasstrace();\n // Use global flag to handle multiple occurrences.\n result = result.replace(\n /[ \\t]*\\/\\/\\s*Glasstrace must be registered[^\\n]*\\n(?:[ \\t]*\\/\\/[^\\n]*\\n)*[ \\t]*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*\\n?/g,\n \"\",\n );\n\n // Remove any remaining standalone registerGlasstrace() calls (global)\n result = result.replace(\n /[ \\t]*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*\\n?/g,\n \"\",\n );\n\n // Remove the import line for registerGlasstrace from @glasstrace/sdk\n // If it's the sole import, remove the whole line\n const soleImportPattern =\n /import\\s*\\{\\s*registerGlasstrace\\s*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/;\n if (soleImportPattern.test(result)) {\n result = result.replace(soleImportPattern, \"\");\n } else {\n // Multiple specifiers — remove only registerGlasstrace\n const multiImportPattern =\n /import\\s*\\{([^}]*)\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']/;\n const multiMatch = multiImportPattern.exec(result);\n if (multiMatch) {\n const specifiers = multiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"registerGlasstrace\");\n if (specifiers.length === 0) {\n result = result.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/,\n \"\",\n );\n } else {\n const newImport = `import { ${specifiers.join(\", \")} } from \"@glasstrace/sdk\"`;\n result = result.replace(multiMatch[0], newImport);\n }\n }\n }\n\n return cleanLeadingBlankLines(result);\n}\n\n/**\n * Removes content between glasstrace marker comments from a file.\n * Supports both HTML markers (`<!-- glasstrace:mcp:start/end -->`) and\n * hash markers (`# glasstrace:mcp:start/end`).\n *\n * @internal Exported for unit testing only.\n */\nexport function removeMarkerSection(content: string): { content: string; removed: boolean } {\n const lines = content.split(\"\\n\");\n let startIdx = -1;\n let endIdx = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (\n trimmed === \"<!-- glasstrace:mcp:start -->\" ||\n trimmed === \"# glasstrace:mcp:start\"\n ) {\n startIdx = i;\n } else if (\n (trimmed === \"<!-- glasstrace:mcp:end -->\" ||\n trimmed === \"# glasstrace:mcp:end\") &&\n startIdx !== -1\n ) {\n endIdx = i;\n break;\n }\n }\n\n if (startIdx === -1 || endIdx === -1) {\n return { content, removed: false };\n }\n\n const before = lines.slice(0, startIdx);\n const after = lines.slice(endIdx + 1);\n\n // Remove trailing blank line that may have preceded the marker block\n while (before.length > 0 && before[before.length - 1].trim() === \"\") {\n before.pop();\n }\n\n const result = [...before, ...after].join(\"\\n\");\n // Ensure file ends with newline if it has content\n const trimmedResult = result.trimEnd();\n return {\n content: trimmedResult.length > 0 ? trimmedResult + \"\\n\" : \"\",\n removed: true,\n };\n}\n\n/**\n * Removes the `glasstrace` key from an MCP config JSON file's `mcpServers`\n * object. Only deletes the file when `mcpServers` is the sole top-level key\n * and `glasstrace` is the only server entry. When other top-level keys exist\n * (e.g., `$schema`, metadata), the `mcpServers` key is removed (if empty)\n * and the file is preserved.\n *\n * @returns `\"removed-key\"` if the key was removed (other data remains),\n * `\"deleted\"` if the file should be deleted (no other data),\n * or `\"skipped\"` if no glasstrace config was found.\n * @internal Exported for unit testing only.\n */\nexport function processJsonMcpConfig(content: string): {\n action: \"removed-key\" | \"deleted\" | \"skipped\";\n content?: string;\n} {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n return { action: \"skipped\" };\n }\n\n const mcpServers = parsed[\"mcpServers\"] as Record<string, unknown> | undefined;\n if (!mcpServers || typeof mcpServers !== \"object\" || !(\"glasstrace\" in mcpServers)) {\n return { action: \"skipped\" };\n }\n\n const remainingServers = Object.keys(mcpServers).filter((k) => k !== \"glasstrace\");\n const otherTopLevelKeys = Object.keys(parsed).filter((k) => k !== \"mcpServers\");\n\n if (remainingServers.length === 0 && otherTopLevelKeys.length === 0) {\n // mcpServers.glasstrace is the only data in the file — safe to delete\n return { action: \"deleted\" };\n }\n\n // Remove the glasstrace key, keep other servers\n const { glasstrace: _, ...rest } = mcpServers;\n // Suppress unused variable lint — the destructuring intentionally discards glasstrace\n void _;\n\n if (remainingServers.length > 0) {\n // Other servers remain — keep mcpServers with glasstrace removed\n parsed[\"mcpServers\"] = rest;\n } else {\n // No servers remain but other top-level keys exist — remove mcpServers entirely\n delete parsed[\"mcpServers\"];\n }\n\n return { action: \"removed-key\", content: JSON.stringify(parsed, null, 2) + \"\\n\" };\n}\n\n/**\n * Removes the `[mcp_servers.glasstrace]` section from a TOML config file.\n * Since TOML parsing without a dependency is complex, this uses a line-based\n * approach that handles the standard format written by init.\n *\n * @returns `\"removed-section\"` if the glasstrace section was removed,\n * `\"deleted\"` if the entire file should be deleted (only contained\n * glasstrace config), or `\"skipped\"` if no glasstrace config found.\n * @internal Exported for unit testing only.\n */\nexport function processTomlMcpConfig(content: string): {\n action: \"removed-section\" | \"deleted\" | \"skipped\";\n content?: string;\n} {\n if (!content.includes(\"[mcp_servers.glasstrace]\")) {\n return { action: \"skipped\" };\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = lines.findIndex(\n (l) => l.trim() === \"[mcp_servers.glasstrace]\",\n );\n if (startIdx === -1) {\n return { action: \"skipped\" };\n }\n\n // Find the end of the glasstrace section: next section header or end of file\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n if (/^\\s*\\[/.test(lines[i])) {\n endIdx = i;\n break;\n }\n }\n\n // Remove the section and any trailing blank lines\n const before = lines.slice(0, startIdx);\n const after = lines.slice(endIdx);\n\n // Trim trailing blank lines from the before section\n while (before.length > 0 && before[before.length - 1].trim() === \"\") {\n before.pop();\n }\n\n const result = [...before, ...after].join(\"\\n\").trimEnd();\n\n // Check if there are any remaining sections\n if (result.trim().length === 0) {\n return { action: \"deleted\" };\n }\n\n return { action: \"removed-section\", content: result + \"\\n\" };\n}\n\n/**\n * Writes the `.glasstrace/shutdown-requested` marker file atomically so\n * that a running SDK heartbeat tick (or equivalent lifecycle hook) can\n * detect that uninit has been invoked and trigger shutdown (DISC-1247\n * Scenario 1).\n *\n * Uses write-temp + rename semantics so a mid-write crash cannot leave\n * a truncated marker that the running process might misread.\n *\n * Best-effort: if `.glasstrace/` does not exist or the write fails, the\n * marker is silently skipped — uninit's cleanup is not blocked by a\n * missing running process.\n *\n * @internal Exported for unit testing only.\n */\nexport function writeShutdownMarker(projectRoot: string): boolean {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n if (!fs.existsSync(dirPath)) {\n // No .glasstrace/ directory means no running SDK state is tracked —\n // nothing to signal. The filesystem removal step will handle any\n // stray artifacts.\n return false;\n }\n const markerPath = path.join(dirPath, \"shutdown-requested\");\n const tmpPath = `${markerPath}.tmp`;\n const body = JSON.stringify({ requestedAt: new Date().toISOString() });\n try {\n fs.writeFileSync(tmpPath, body, { encoding: \"utf-8\", mode: 0o600 });\n try {\n fs.chmodSync(tmpPath, 0o600);\n } catch {\n // chmod may be unsupported on some filesystems; proceed with rename.\n }\n fs.renameSync(tmpPath, markerPath);\n return true;\n } catch {\n // Best-effort cleanup of the temp file; swallow errors so uninit\n // itself never fails because of a signal-side-channel write.\n try {\n fs.unlinkSync(tmpPath);\n } catch {\n // Ignore — the marker was best-effort to begin with.\n }\n return false;\n }\n}\n\n/**\n * Simple TTY prompt used when `UninitOptions.prompt` is not provided.\n * Returns `defaultValue` when stdin is not a TTY.\n */\nasync function defaultPrompt(question: string, defaultValue: boolean): Promise<boolean> {\n if (!process.stdin.isTTY) return defaultValue;\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise<boolean>((resolve) => {\n const suffix = defaultValue ? \" [Y/n] \" : \" [y/N] \";\n rl.question(question + suffix, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n if (trimmed === \"\") {\n resolve(defaultValue);\n return;\n }\n resolve(trimmed === \"y\" || trimmed === \"yes\");\n });\n });\n}\n\n/**\n * Reverses every step of `glasstrace init`, cleanly removing all SDK artifacts\n * from a project.\n *\n * Steps (in order):\n * 1. Write `.glasstrace/shutdown-requested` marker so a running SDK can\n * drain and exit cleanly (DISC-1247 Scenario 1)\n * 2. Unwrap `withGlasstraceConfig` from next.config\n * 3. Remove `registerGlasstrace` from instrumentation.ts (or delete if init-created)\n * 4. Remove `.glasstrace/` directory\n * 5. Remove `GLASSTRACE_*` entries from `.env.local` (with dev-key confirmation)\n * 6. Remove `.glasstrace/` from `.gitignore`\n * 7. Remove MCP config entries\n * 8. Remove info sections from agent files\n *\n * @param options - Configuration for the uninit command.\n * @returns A structured result describing what actions were taken.\n */\nexport async function runUninit(options: UninitOptions): Promise<UninitResult> {\n const { projectRoot, dryRun } = options;\n const force = options.force === true;\n const prompt = options.prompt ?? defaultPrompt;\n const summary: string[] = [];\n const warnings: string[] = [];\n const errors: string[] = [];\n const prefix = dryRun ? \"[dry run] \" : \"\";\n\n // Step 0: Signal any running SDK to shut down via a marker file.\n // Placed first so the running process has maximum time to observe\n // the marker while the remaining cleanup steps execute.\n try {\n if (!dryRun) {\n const markerWritten = writeShutdownMarker(projectRoot);\n if (markerWritten) {\n summary.push(\"Wrote .glasstrace/shutdown-requested marker\");\n }\n } else {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n if (fs.existsSync(dirPath)) {\n summary.push(`${prefix}Would write .glasstrace/shutdown-requested marker`);\n }\n }\n } catch (err) {\n // Marker is best-effort; failure is not an error for uninit.\n warnings.push(\n `Shutdown marker write failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 1: Unwrap withGlasstraceConfig from next.config\n try {\n let configHandled = false;\n for (const name of NEXT_CONFIG_NAMES) {\n const configPath = path.join(projectRoot, name);\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n if (!content.includes(\"withGlasstraceConfig\")) {\n continue;\n }\n\n const isESM = name.endsWith(\".ts\") || name.endsWith(\".mjs\");\n const unwrapResult = isESM\n ? unwrapExport(content)\n : unwrapCJSExport(content);\n\n if (unwrapResult.unwrapped) {\n const cleaned = removeGlasstraceConfigImport(unwrapResult.content);\n const final = cleanLeadingBlankLines(cleaned);\n if (!dryRun) {\n fs.writeFileSync(configPath, final, \"utf-8\");\n }\n summary.push(`${prefix}Unwrapped withGlasstraceConfig from ${name}`);\n configHandled = true;\n break;\n } else {\n warnings.push(\n `${name} contains withGlasstraceConfig but could not be automatically unwrapped. ` +\n \"Please remove withGlasstraceConfig() manually.\",\n );\n configHandled = true;\n break;\n }\n }\n if (!configHandled) {\n // No next.config with withGlasstraceConfig found — nothing to do\n }\n } catch (err) {\n errors.push(\n `Failed to process next.config: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 2: Remove registerGlasstrace from instrumentation.ts\n try {\n const instrPath = path.join(projectRoot, \"instrumentation.ts\");\n if (fs.existsSync(instrPath)) {\n const content = fs.readFileSync(instrPath, \"utf-8\");\n if (content.includes(\"registerGlasstrace\") || content.includes(\"@glasstrace/sdk\")) {\n if (isInitCreatedInstrumentation(content)) {\n if (!dryRun) {\n fs.unlinkSync(instrPath);\n }\n summary.push(`${prefix}Deleted instrumentation.ts (init-created)`);\n } else {\n const cleaned = removeRegisterGlasstrace(content);\n if (cleaned !== content) {\n if (!dryRun) {\n fs.writeFileSync(instrPath, cleaned, \"utf-8\");\n }\n summary.push(\n `${prefix}Removed registerGlasstrace() from instrumentation.ts`,\n );\n }\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 3: Remove .glasstrace/ directory\n try {\n const glasstraceDir = path.join(projectRoot, \".glasstrace\");\n if (fs.existsSync(glasstraceDir)) {\n if (!dryRun) {\n fs.rmSync(glasstraceDir, { recursive: true, force: true });\n }\n summary.push(`${prefix}Removed .glasstrace/ directory`);\n }\n } catch (err) {\n errors.push(\n `Failed to remove .glasstrace/: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 4: Remove GLASSTRACE entries from .env.local\n // DISC-1247 Scenario 6: if the file contains a claimed developer key\n // (`gt_dev_*`), require explicit confirmation before removing it so\n // users don't silently lose authentication state during uninit.\n // `--force` bypasses the prompt.\n try {\n const envPath = path.join(projectRoot, \".env.local\");\n if (fs.existsSync(envPath)) {\n const content = fs.readFileSync(envPath, \"utf-8\");\n const existingKey = readEnvLocalApiKey(content);\n const hasDevKey = isDevApiKey(existingKey);\n\n // Track how the dev-key path is resolved so the summary reflects\n // what actually happened: prompt-confirmed, force-bypassed, or\n // preview-only. Using the literal \"(dev key confirmed)\" for all\n // three paths was misleading (Copilot review).\n let proceed = true;\n let devKeyPath: \"interactive-confirmed\" | \"force-bypass\" | \"dry-run-preview\" | \"none\" = \"none\";\n if (hasDevKey) {\n if (dryRun) {\n devKeyPath = \"dry-run-preview\";\n } else if (force) {\n devKeyPath = \"force-bypass\";\n } else {\n const confirmed = await prompt(\n \".env.local contains a claimed Glasstrace developer API key (gt_dev_...). \" +\n \"Removing it will require you to re-authenticate. Continue?\",\n false,\n );\n proceed = confirmed;\n if (confirmed) devKeyPath = \"interactive-confirmed\";\n }\n }\n\n if (!proceed) {\n warnings.push(\n \"Preserved GLASSTRACE_API_KEY in .env.local (claimed dev key; re-run with --force to remove)\",\n );\n } else {\n const lines = content.split(\"\\n\");\n const filtered = lines.filter((line) => {\n const trimmed = line.trim();\n // Match both commented and uncommented GLASSTRACE_ lines\n return !(\n /^\\s*#?\\s*GLASSTRACE_API_KEY\\s*=/.test(trimmed) ||\n /^\\s*#?\\s*GLASSTRACE_COVERAGE_MAP\\s*=/.test(trimmed)\n );\n });\n\n if (filtered.length !== lines.length) {\n const result = filtered.join(\"\\n\");\n // If the file is now empty (only newlines), don't write it\n if (result.trim().length === 0) {\n if (!dryRun) {\n fs.unlinkSync(envPath);\n }\n summary.push(`${prefix}Deleted .env.local (no remaining entries)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(envPath, result, \"utf-8\");\n }\n let devKeyAnnotation = \"\";\n if (devKeyPath === \"interactive-confirmed\") {\n devKeyAnnotation = \" (dev key confirmed)\";\n } else if (devKeyPath === \"force-bypass\") {\n devKeyAnnotation = \" (dev key removed via --force)\";\n } else if (devKeyPath === \"dry-run-preview\") {\n devKeyAnnotation =\n \" (dev key would be removed; real run would require confirmation)\";\n }\n summary.push(\n `${prefix}Removed GLASSTRACE entries from .env.local${devKeyAnnotation}`,\n );\n }\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process .env.local: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 5: Remove .glasstrace/ from .gitignore\n try {\n const gitignorePath = path.join(projectRoot, \".gitignore\");\n if (fs.existsSync(gitignorePath)) {\n const content = fs.readFileSync(gitignorePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n // Remove lines that are exactly \".glasstrace/\" or MCP config file entries\n // added by init (e.g., \".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\",\n // \".codex/config.toml\")\n const mcpGitignoreEntries = new Set([\n \".glasstrace/\",\n \".mcp.json\",\n \".cursor/mcp.json\",\n \".gemini/settings.json\",\n \".codex/config.toml\",\n ]);\n\n const filtered = lines.filter(\n (line) => !mcpGitignoreEntries.has(line.trim()),\n );\n\n if (filtered.length !== lines.length) {\n const result = filtered.join(\"\\n\");\n if (result.trim().length === 0) {\n if (!dryRun) {\n fs.unlinkSync(gitignorePath);\n }\n summary.push(`${prefix}Deleted .gitignore (no remaining entries)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(gitignorePath, result, \"utf-8\");\n }\n summary.push(`${prefix}Removed Glasstrace entries from .gitignore`);\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process .gitignore: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 6: Remove MCP config entries\n try {\n for (const configFile of MCP_CONFIG_FILES) {\n const configPath = path.join(projectRoot, configFile);\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n const result = processJsonMcpConfig(content);\n\n if (result.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(configPath);\n }\n summary.push(`${prefix}Deleted ${configFile}`);\n } else if (result.action === \"removed-key\" && result.content !== undefined) {\n if (!dryRun) {\n fs.writeFileSync(configPath, result.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed glasstrace from ${configFile}`);\n }\n }\n // Handle Codex TOML config separately\n const codexConfigPath = path.join(projectRoot, \".codex\", \"config.toml\");\n if (fs.existsSync(codexConfigPath)) {\n const content = fs.readFileSync(codexConfigPath, \"utf-8\");\n const tomlResult = processTomlMcpConfig(content);\n\n if (tomlResult.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(codexConfigPath);\n }\n summary.push(`${prefix}Deleted .codex/config.toml`);\n } else if (tomlResult.action === \"removed-section\" && tomlResult.content !== undefined) {\n if (!dryRun) {\n fs.writeFileSync(codexConfigPath, tomlResult.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);\n }\n }\n\n // Handle Windsurf global config at ~/.codeium/windsurf/mcp_config.json\n // Only process if the project has Windsurf markers, to avoid touching\n // global config for non-Windsurf projects\n const hasWindsurfMarkers =\n fs.existsSync(path.join(projectRoot, \".windsurfrules\")) ||\n fs.existsSync(path.join(projectRoot, \".windsurf\"));\n if (hasWindsurfMarkers) {\n const windsurfConfigPath = path.join(\n os.homedir(),\n \".codeium\",\n \"windsurf\",\n \"mcp_config.json\",\n );\n if (fs.existsSync(windsurfConfigPath)) {\n const content = fs.readFileSync(windsurfConfigPath, \"utf-8\");\n const windsurfResult = processJsonMcpConfig(content);\n\n // Display the path with ~ for the home directory to keep output\n // readable, but derive it from the actual path for accuracy.\n const home = os.homedir();\n const displayPath = windsurfConfigPath.startsWith(home)\n ? \"~\" + windsurfConfigPath.slice(home.length)\n : windsurfConfigPath;\n\n if (windsurfResult.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(windsurfConfigPath);\n }\n summary.push(\n `${prefix}Deleted global Windsurf config (${displayPath})`,\n );\n } else if (\n windsurfResult.action === \"removed-key\" &&\n windsurfResult.content !== undefined\n ) {\n if (!dryRun) {\n fs.writeFileSync(windsurfConfigPath, windsurfResult.content, \"utf-8\");\n }\n summary.push(\n `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`,\n );\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process MCP config: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 7: Remove info sections from agent files\n try {\n for (const infoFile of AGENT_INFO_FILES) {\n const filePath = path.join(projectRoot, infoFile);\n if (!fs.existsSync(filePath)) {\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf-8\");\n const result = removeMarkerSection(content);\n\n if (result.removed) {\n if (result.content.trim().length === 0) {\n // File is now empty after removing the marker section —\n // only delete if the file was solely glasstrace content\n if (!dryRun) {\n fs.unlinkSync(filePath);\n }\n summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(filePath, result.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process agent info files: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (summary.length === 0 && errors.length === 0) {\n summary.push(\"No Glasstrace artifacts found — nothing to do.\");\n }\n\n return { exitCode: errors.length > 0 ? 1 : 0, summary, warnings, errors };\n}\n"],"mappings":"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","names":[]}