@glasstrace/sdk 0.14.1 → 0.15.1

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 (40) hide show
  1. package/dist/{chunk-ERGEG4ZQ.js → chunk-2LDBR3F3.js} +16 -3
  2. package/dist/chunk-2LDBR3F3.js.map +1 -0
  3. package/dist/chunk-A2AZL6MZ.js +309 -0
  4. package/dist/chunk-A2AZL6MZ.js.map +1 -0
  5. package/dist/{chunk-ARAOZCZT.js → chunk-ROFOJQWN.js} +118 -16
  6. package/dist/chunk-ROFOJQWN.js.map +1 -0
  7. package/dist/{chunk-WV3NIPWJ.js → chunk-ZNOD6FC7.js} +18 -276
  8. package/dist/chunk-ZNOD6FC7.js.map +1 -0
  9. package/dist/cli/init.cjs +458 -115
  10. package/dist/cli/init.cjs.map +1 -1
  11. package/dist/cli/init.d.cts +33 -1
  12. package/dist/cli/init.d.ts +33 -1
  13. package/dist/cli/init.js +144 -42
  14. package/dist/cli/init.js.map +1 -1
  15. package/dist/cli/mcp-add.cjs.map +1 -1
  16. package/dist/cli/mcp-add.js +4 -2
  17. package/dist/cli/mcp-add.js.map +1 -1
  18. package/dist/cli/uninit.cjs +181 -60
  19. package/dist/cli/uninit.cjs.map +1 -1
  20. package/dist/cli/uninit.d.cts +38 -8
  21. package/dist/cli/uninit.d.ts +38 -8
  22. package/dist/cli/uninit.js +6 -3
  23. package/dist/cli/validate.cjs +135 -0
  24. package/dist/cli/validate.cjs.map +1 -0
  25. package/dist/cli/validate.d.cts +60 -0
  26. package/dist/cli/validate.d.ts +60 -0
  27. package/dist/cli/validate.js +103 -0
  28. package/dist/cli/validate.js.map +1 -0
  29. package/dist/index.cjs +123 -47
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +45 -5
  32. package/dist/index.d.ts +45 -5
  33. package/dist/index.js +109 -46
  34. package/dist/index.js.map +1 -1
  35. package/dist/{source-map-uploader-W6VPGY26.js → source-map-uploader-3GWUQDTS.js} +6 -2
  36. package/package.json +6 -4
  37. package/dist/chunk-ARAOZCZT.js.map +0 -1
  38. package/dist/chunk-ERGEG4ZQ.js.map +0 -1
  39. package/dist/chunk-WV3NIPWJ.js.map +0 -1
  40. /package/dist/{source-map-uploader-W6VPGY26.js.map → source-map-uploader-3GWUQDTS.js.map} +0 -0
@@ -3,14 +3,16 @@ import {
3
3
  generateInfoSection,
4
4
  generateMcpConfig,
5
5
  injectInfoSection,
6
- scaffoldMcpMarker,
7
6
  updateGitignore,
8
7
  writeMcpConfig
9
- } from "../chunk-WV3NIPWJ.js";
8
+ } from "../chunk-ZNOD6FC7.js";
10
9
  import {
11
10
  readAnonKey
12
11
  } from "../chunk-ECEN724Y.js";
13
12
  import "../chunk-YMEXDDTA.js";
13
+ import {
14
+ scaffoldMcpMarker
15
+ } from "../chunk-A2AZL6MZ.js";
14
16
  import {
15
17
  MCP_ENDPOINT,
16
18
  formatAgentName
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/mcp-add.ts"],"sourcesContent":["import { execFile as execFileCb } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { readAnonKey } from \"../anon-key.js\";\nimport { detectAgents } from \"../agent-detection/detect.js\";\nimport { generateMcpConfig, generateInfoSection } from \"../agent-detection/configs.js\";\nimport {\n writeMcpConfig,\n injectInfoSection,\n updateGitignore,\n} from \"../agent-detection/inject.js\";\nimport { scaffoldMcpMarker } from \"./scaffolder.js\";\nimport type { DetectedAgent } from \"../agent-detection/detect.js\";\nimport { MCP_ENDPOINT, formatAgentName } from \"./constants.js\";\n\nconst execFileAsync = promisify(execFileCb);\n\n/** Options for the mcp add command. */\nexport interface McpAddOptions {\n force?: boolean;\n dryRun?: boolean;\n}\n\n/** Result of the mcp add command. */\nexport interface McpAddResult {\n exitCode: number;\n results: AgentResult[];\n messages: string[];\n}\n\n/**\n * Result of a single agent registration attempt.\n */\ninterface AgentResult {\n agent: DetectedAgent[\"name\"];\n success: boolean;\n method: \"cli\" | \"file\" | \"skipped\";\n message: string;\n}\n\n/**\n * Attempts CLI-based MCP registration for agents that support it.\n * Returns true if the CLI command succeeded.\n *\n * Note: anonymous keys are passed in process arguments for CLI registration.\n * This is acceptable because anon keys are non-secret identifiers (not\n * credentials) designed for semi-public use. They identify a project\n * but cannot be used to access user data.\n */\nasync function registerViaCli(\n agent: DetectedAgent,\n anonKey: string,\n): Promise<boolean> {\n if (!agent.cliAvailable) {\n return false;\n }\n\n try {\n switch (agent.name) {\n case \"claude\": {\n const payload = JSON.stringify({\n type: \"http\",\n url: MCP_ENDPOINT,\n headers: { Authorization: `Bearer ${anonKey}` },\n });\n await execFileAsync(\"claude\", [\n \"mcp\",\n \"add-json\",\n \"glasstrace\",\n payload,\n \"--scope\",\n \"project\",\n ]);\n return true;\n }\n\n case \"codex\": {\n await execFileAsync(\"codex\", [\n \"mcp\",\n \"add\",\n \"glasstrace\",\n \"--url\",\n MCP_ENDPOINT,\n ]);\n // Ensure .codex/config.toml has bearer_token_env_var\n const configPath = agent.mcpConfigPath;\n if (configPath !== null && fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, \"utf-8\");\n if (!content.includes(\"bearer_token_env_var\")) {\n const appendContent =\n content.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(\n configPath,\n content +\n appendContent +\n 'bearer_token_env_var = \"GLASSTRACE_API_KEY\"\\n',\n \"utf-8\",\n );\n }\n }\n process.stderr.write(\n \" Note: Set GLASSTRACE_API_KEY environment variable for Codex authentication.\\n\",\n );\n return true;\n }\n\n case \"gemini\": {\n await execFileAsync(\"gemini\", [\n \"mcp\",\n \"add\",\n \"--transport\",\n \"http\",\n \"--header\",\n `Authorization: Bearer ${anonKey}`,\n \"glasstrace\",\n MCP_ENDPOINT,\n ]);\n return true;\n }\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n/**\n * Registers the Glasstrace MCP server with detected AI coding agents.\n *\n * For each agent, attempts native CLI registration first, then falls back\n * to file-based configuration. Creates a marker file on success to enable\n * idempotent re-runs.\n *\n * Returns a structured result instead of calling process.exit(), so the\n * CLI entry point can decide how to handle the outcome.\n *\n * @param options - Control flags for force and dry-run modes.\n */\nexport async function mcpAdd(options?: McpAddOptions): Promise<McpAddResult> {\n const force = options?.force ?? false;\n const dryRun = options?.dryRun ?? false;\n const projectRoot = process.cwd();\n const messages: string[] = [];\n\n // Step 1: Read anon key\n const anonKey = await readAnonKey(projectRoot);\n if (anonKey === null) {\n return {\n exitCode: 1,\n results: [],\n messages: [\"Error: Run `glasstrace init` first to generate an API key.\"],\n };\n }\n\n // Step 2: Check marker file\n const markerPath = path.join(projectRoot, \".glasstrace\", \"mcp-connected\");\n if (fs.existsSync(markerPath) && !force) {\n return {\n exitCode: 0,\n results: [],\n messages: [\"MCP already configured. Use --force to reconfigure.\"],\n };\n }\n\n // Step 3: Detect agents\n const agents = await detectAgents(projectRoot);\n const detectedNonGeneric = agents.filter((a) => a.name !== \"generic\");\n\n // If no specific agents found, include the generic fallback so the command\n // still produces a usable .glasstrace/mcp.json (matching init behavior).\n const targetAgents =\n detectedNonGeneric.length > 0\n ? detectedNonGeneric\n : agents.filter((a) => a.name === \"generic\");\n\n if (dryRun) {\n messages.push(\"Dry run: would perform the following actions:\", \"\");\n for (const agent of targetAgents) {\n const name = formatAgentName(agent.name);\n if (agent.cliAvailable) {\n messages.push(\n ` ${name}: Register via CLI (${agent.name} mcp add)`,\n );\n } else if (agent.mcpConfigPath !== null) {\n messages.push(\n ` ${name}: Write config to ${agent.mcpConfigPath}`,\n );\n }\n if (agent.infoFilePath !== null) {\n messages.push(\n ` ${name}: Inject info section into ${agent.infoFilePath}`,\n );\n }\n }\n messages.push(\n \"\",\n \" Update .gitignore with MCP config paths\",\n \" Create .glasstrace/mcp-connected marker\",\n );\n return { exitCode: 0, results: [], messages };\n }\n\n // Step 4: Register with each agent\n const results: AgentResult[] = [];\n\n for (const agent of targetAgents) {\n const name = formatAgentName(agent.name);\n\n // Try CLI registration first (not applicable for generic)\n if (agent.name !== \"generic\") {\n const cliSuccess = await registerViaCli(agent, anonKey);\n if (cliSuccess) {\n // Still inject info section if applicable\n const infoContent = generateInfoSection(agent, MCP_ENDPOINT);\n if (infoContent !== \"\") {\n await injectInfoSection(agent, infoContent, projectRoot);\n }\n results.push({\n agent: agent.name,\n success: true,\n method: \"cli\",\n message: `${name}: Registered via CLI`,\n });\n continue;\n }\n }\n\n // Fall back to file-based config\n if (agent.mcpConfigPath !== null) {\n try {\n const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);\n await writeMcpConfig(agent, configContent, projectRoot);\n\n // Verify the config was written (writeMcpConfig swallows permission errors)\n if (fs.existsSync(agent.mcpConfigPath)) {\n const infoContent = generateInfoSection(agent, MCP_ENDPOINT);\n if (infoContent !== \"\") {\n await injectInfoSection(agent, infoContent, projectRoot);\n }\n results.push({\n agent: agent.name,\n success: true,\n method: \"file\",\n message: `${name}: Configured via ${agent.mcpConfigPath}`,\n });\n continue;\n }\n\n // writeMcpConfig returned without throwing but file doesn't exist\n // (permission denied handled gracefully inside writeMcpConfig)\n results.push({\n agent: agent.name,\n success: false,\n method: \"file\",\n message: `${name}: Failed to write config to ${agent.mcpConfigPath} (permission denied)`,\n });\n continue;\n } catch (err) {\n results.push({\n agent: agent.name,\n success: false,\n method: \"file\",\n message: `${name}: Failed - ${err instanceof Error ? err.message : String(err)}`,\n });\n continue;\n }\n }\n\n results.push({\n agent: agent.name,\n success: false,\n method: \"skipped\",\n message: `${name}: No registration method available`,\n });\n }\n\n // Step 5: Update gitignore\n await updateGitignore(\n [\".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\", \".codex/config.toml\"],\n projectRoot,\n );\n\n // Step 6: Create marker file if at least one succeeded\n const anySuccess = results.some((r) => r.success);\n\n if (anySuccess) {\n await scaffoldMcpMarker(projectRoot, anonKey);\n }\n\n // Step 7: Build summary messages\n messages.push(\"\", \"MCP registration summary:\");\n for (const result of results) {\n const icon = result.success ? \"+\" : \"-\";\n messages.push(` [${icon}] ${result.message}`);\n }\n\n if (results.length === 0) {\n messages.push(\n \" No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project.\",\n );\n }\n\n if (!anySuccess && results.length > 0) {\n messages.push(\n \"\",\n \"All agent registrations failed. Check errors above.\",\n );\n return { exitCode: 1, results, messages };\n }\n\n if (anySuccess) {\n messages.push(\"\", \"MCP registration complete.\");\n }\n\n return { exitCode: 0, results, messages };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/mcp-add.ts"],"sourcesContent":["import { execFile as execFileCb } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { readAnonKey } from \"../anon-key.js\";\nimport { detectAgents } from \"../agent-detection/detect.js\";\nimport { generateMcpConfig, generateInfoSection } from \"../agent-detection/configs.js\";\nimport {\n writeMcpConfig,\n injectInfoSection,\n updateGitignore,\n} from \"../agent-detection/inject.js\";\nimport { scaffoldMcpMarker } from \"./scaffolder.js\";\nimport type { DetectedAgent } from \"../agent-detection/detect.js\";\nimport { MCP_ENDPOINT, formatAgentName } from \"./constants.js\";\n\nconst execFileAsync = promisify(execFileCb);\n\n/** Options for the mcp add command. */\nexport interface McpAddOptions {\n force?: boolean;\n dryRun?: boolean;\n}\n\n/** Result of the mcp add command. */\nexport interface McpAddResult {\n exitCode: number;\n results: AgentResult[];\n messages: string[];\n}\n\n/**\n * Result of a single agent registration attempt.\n */\ninterface AgentResult {\n agent: DetectedAgent[\"name\"];\n success: boolean;\n method: \"cli\" | \"file\" | \"skipped\";\n message: string;\n}\n\n/**\n * Attempts CLI-based MCP registration for agents that support it.\n * Returns true if the CLI command succeeded.\n *\n * Note: anonymous keys are passed in process arguments for CLI registration.\n * This is acceptable because anon keys are non-secret identifiers (not\n * credentials) designed for semi-public use. They identify a project\n * but cannot be used to access user data.\n */\nasync function registerViaCli(\n agent: DetectedAgent,\n anonKey: string,\n): Promise<boolean> {\n if (!agent.cliAvailable) {\n return false;\n }\n\n try {\n switch (agent.name) {\n case \"claude\": {\n const payload = JSON.stringify({\n type: \"http\",\n url: MCP_ENDPOINT,\n headers: { Authorization: `Bearer ${anonKey}` },\n });\n await execFileAsync(\"claude\", [\n \"mcp\",\n \"add-json\",\n \"glasstrace\",\n payload,\n \"--scope\",\n \"project\",\n ]);\n return true;\n }\n\n case \"codex\": {\n await execFileAsync(\"codex\", [\n \"mcp\",\n \"add\",\n \"glasstrace\",\n \"--url\",\n MCP_ENDPOINT,\n ]);\n // Ensure .codex/config.toml has bearer_token_env_var\n const configPath = agent.mcpConfigPath;\n if (configPath !== null && fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, \"utf-8\");\n if (!content.includes(\"bearer_token_env_var\")) {\n const appendContent =\n content.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(\n configPath,\n content +\n appendContent +\n 'bearer_token_env_var = \"GLASSTRACE_API_KEY\"\\n',\n \"utf-8\",\n );\n }\n }\n process.stderr.write(\n \" Note: Set GLASSTRACE_API_KEY environment variable for Codex authentication.\\n\",\n );\n return true;\n }\n\n case \"gemini\": {\n await execFileAsync(\"gemini\", [\n \"mcp\",\n \"add\",\n \"--transport\",\n \"http\",\n \"--header\",\n `Authorization: Bearer ${anonKey}`,\n \"glasstrace\",\n MCP_ENDPOINT,\n ]);\n return true;\n }\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n/**\n * Registers the Glasstrace MCP server with detected AI coding agents.\n *\n * For each agent, attempts native CLI registration first, then falls back\n * to file-based configuration. Creates a marker file on success to enable\n * idempotent re-runs.\n *\n * Returns a structured result instead of calling process.exit(), so the\n * CLI entry point can decide how to handle the outcome.\n *\n * @param options - Control flags for force and dry-run modes.\n */\nexport async function mcpAdd(options?: McpAddOptions): Promise<McpAddResult> {\n const force = options?.force ?? false;\n const dryRun = options?.dryRun ?? false;\n const projectRoot = process.cwd();\n const messages: string[] = [];\n\n // Step 1: Read anon key\n const anonKey = await readAnonKey(projectRoot);\n if (anonKey === null) {\n return {\n exitCode: 1,\n results: [],\n messages: [\"Error: Run `glasstrace init` first to generate an API key.\"],\n };\n }\n\n // Step 2: Check marker file\n const markerPath = path.join(projectRoot, \".glasstrace\", \"mcp-connected\");\n if (fs.existsSync(markerPath) && !force) {\n return {\n exitCode: 0,\n results: [],\n messages: [\"MCP already configured. Use --force to reconfigure.\"],\n };\n }\n\n // Step 3: Detect agents\n const agents = await detectAgents(projectRoot);\n const detectedNonGeneric = agents.filter((a) => a.name !== \"generic\");\n\n // If no specific agents found, include the generic fallback so the command\n // still produces a usable .glasstrace/mcp.json (matching init behavior).\n const targetAgents =\n detectedNonGeneric.length > 0\n ? detectedNonGeneric\n : agents.filter((a) => a.name === \"generic\");\n\n if (dryRun) {\n messages.push(\"Dry run: would perform the following actions:\", \"\");\n for (const agent of targetAgents) {\n const name = formatAgentName(agent.name);\n if (agent.cliAvailable) {\n messages.push(\n ` ${name}: Register via CLI (${agent.name} mcp add)`,\n );\n } else if (agent.mcpConfigPath !== null) {\n messages.push(\n ` ${name}: Write config to ${agent.mcpConfigPath}`,\n );\n }\n if (agent.infoFilePath !== null) {\n messages.push(\n ` ${name}: Inject info section into ${agent.infoFilePath}`,\n );\n }\n }\n messages.push(\n \"\",\n \" Update .gitignore with MCP config paths\",\n \" Create .glasstrace/mcp-connected marker\",\n );\n return { exitCode: 0, results: [], messages };\n }\n\n // Step 4: Register with each agent\n const results: AgentResult[] = [];\n\n for (const agent of targetAgents) {\n const name = formatAgentName(agent.name);\n\n // Try CLI registration first (not applicable for generic)\n if (agent.name !== \"generic\") {\n const cliSuccess = await registerViaCli(agent, anonKey);\n if (cliSuccess) {\n // Still inject info section if applicable\n const infoContent = generateInfoSection(agent, MCP_ENDPOINT);\n if (infoContent !== \"\") {\n await injectInfoSection(agent, infoContent, projectRoot);\n }\n results.push({\n agent: agent.name,\n success: true,\n method: \"cli\",\n message: `${name}: Registered via CLI`,\n });\n continue;\n }\n }\n\n // Fall back to file-based config\n if (agent.mcpConfigPath !== null) {\n try {\n const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);\n await writeMcpConfig(agent, configContent, projectRoot);\n\n // Verify the config was written (writeMcpConfig swallows permission errors)\n if (fs.existsSync(agent.mcpConfigPath)) {\n const infoContent = generateInfoSection(agent, MCP_ENDPOINT);\n if (infoContent !== \"\") {\n await injectInfoSection(agent, infoContent, projectRoot);\n }\n results.push({\n agent: agent.name,\n success: true,\n method: \"file\",\n message: `${name}: Configured via ${agent.mcpConfigPath}`,\n });\n continue;\n }\n\n // writeMcpConfig returned without throwing but file doesn't exist\n // (permission denied handled gracefully inside writeMcpConfig)\n results.push({\n agent: agent.name,\n success: false,\n method: \"file\",\n message: `${name}: Failed to write config to ${agent.mcpConfigPath} (permission denied)`,\n });\n continue;\n } catch (err) {\n results.push({\n agent: agent.name,\n success: false,\n method: \"file\",\n message: `${name}: Failed - ${err instanceof Error ? err.message : String(err)}`,\n });\n continue;\n }\n }\n\n results.push({\n agent: agent.name,\n success: false,\n method: \"skipped\",\n message: `${name}: No registration method available`,\n });\n }\n\n // Step 5: Update gitignore\n await updateGitignore(\n [\".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\", \".codex/config.toml\"],\n projectRoot,\n );\n\n // Step 6: Create marker file if at least one succeeded\n const anySuccess = results.some((r) => r.success);\n\n if (anySuccess) {\n await scaffoldMcpMarker(projectRoot, anonKey);\n }\n\n // Step 7: Build summary messages\n messages.push(\"\", \"MCP registration summary:\");\n for (const result of results) {\n const icon = result.success ? \"+\" : \"-\";\n messages.push(` [${icon}] ${result.message}`);\n }\n\n if (results.length === 0) {\n messages.push(\n \" No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project.\",\n );\n }\n\n if (!anySuccess && results.length > 0) {\n messages.push(\n \"\",\n \"All agent registrations failed. Check errors above.\",\n );\n return { exitCode: 1, results, messages };\n }\n\n if (anySuccess) {\n messages.push(\"\", \"MCP registration complete.\");\n }\n\n return { exitCode: 0, results, messages };\n}\n"],"mappings":"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","names":[]}
@@ -41,16 +41,39 @@ __export(uninit_exports, {
41
41
  runUninit: () => runUninit,
42
42
  skipString: () => skipString,
43
43
  unwrapCJSExport: () => unwrapCJSExport,
44
- unwrapExport: () => unwrapExport
44
+ unwrapExport: () => unwrapExport,
45
+ writeShutdownMarker: () => writeShutdownMarker
45
46
  });
46
47
  module.exports = __toCommonJS(uninit_exports);
47
- var fs = __toESM(require("fs"), 1);
48
+ var fs2 = __toESM(require("fs"), 1);
48
49
  var os = __toESM(require("os"), 1);
49
- var path = __toESM(require("path"), 1);
50
+ var path2 = __toESM(require("path"), 1);
50
51
 
51
52
  // src/cli/constants.ts
52
53
  var NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
53
54
 
55
+ // src/cli/scaffolder.ts
56
+ var import_node_crypto = require("crypto");
57
+ var fs = __toESM(require("fs"), 1);
58
+ var path = __toESM(require("path"), 1);
59
+ function readEnvLocalApiKey(content) {
60
+ let last = null;
61
+ const regex = /^\s*GLASSTRACE_API_KEY\s*=\s*(.*)$/gm;
62
+ let match;
63
+ while ((match = regex.exec(content)) !== null) {
64
+ const raw = match[1].trim();
65
+ if (raw === "") continue;
66
+ const unquoted = raw.replace(/^(['"])(.*)\1$/, "$2");
67
+ if (unquoted === "" || unquoted === "your_key_here") continue;
68
+ last = unquoted;
69
+ }
70
+ return last;
71
+ }
72
+ function isDevApiKey(value) {
73
+ if (value === null || value === void 0) return false;
74
+ return value.trim().startsWith("gt_dev_");
75
+ }
76
+
54
77
  // src/cli/uninit.ts
55
78
  var MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
56
79
  var AGENT_INFO_FILES = [
@@ -357,20 +380,83 @@ function processTomlMcpConfig(content) {
357
380
  }
358
381
  return { action: "removed-section", content: result + "\n" };
359
382
  }
383
+ function writeShutdownMarker(projectRoot) {
384
+ const dirPath = path2.join(projectRoot, ".glasstrace");
385
+ if (!fs2.existsSync(dirPath)) {
386
+ return false;
387
+ }
388
+ const markerPath = path2.join(dirPath, "shutdown-requested");
389
+ const tmpPath = `${markerPath}.tmp`;
390
+ const body = JSON.stringify({ requestedAt: (/* @__PURE__ */ new Date()).toISOString() });
391
+ try {
392
+ fs2.writeFileSync(tmpPath, body, { encoding: "utf-8", mode: 384 });
393
+ try {
394
+ fs2.chmodSync(tmpPath, 384);
395
+ } catch {
396
+ }
397
+ fs2.renameSync(tmpPath, markerPath);
398
+ return true;
399
+ } catch {
400
+ try {
401
+ fs2.unlinkSync(tmpPath);
402
+ } catch {
403
+ }
404
+ return false;
405
+ }
406
+ }
407
+ async function defaultPrompt(question, defaultValue) {
408
+ if (!process.stdin.isTTY) return defaultValue;
409
+ const readline = await import("readline");
410
+ const rl = readline.createInterface({
411
+ input: process.stdin,
412
+ output: process.stdout
413
+ });
414
+ return new Promise((resolve) => {
415
+ const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
416
+ rl.question(question + suffix, (answer) => {
417
+ rl.close();
418
+ const trimmed = answer.trim().toLowerCase();
419
+ if (trimmed === "") {
420
+ resolve(defaultValue);
421
+ return;
422
+ }
423
+ resolve(trimmed === "y" || trimmed === "yes");
424
+ });
425
+ });
426
+ }
360
427
  async function runUninit(options) {
361
428
  const { projectRoot, dryRun } = options;
429
+ const force = options.force === true;
430
+ const prompt = options.prompt ?? defaultPrompt;
362
431
  const summary = [];
363
432
  const warnings = [];
364
433
  const errors = [];
365
434
  const prefix = dryRun ? "[dry run] " : "";
435
+ try {
436
+ if (!dryRun) {
437
+ const markerWritten = writeShutdownMarker(projectRoot);
438
+ if (markerWritten) {
439
+ summary.push("Wrote .glasstrace/shutdown-requested marker");
440
+ }
441
+ } else {
442
+ const dirPath = path2.join(projectRoot, ".glasstrace");
443
+ if (fs2.existsSync(dirPath)) {
444
+ summary.push(`${prefix}Would write .glasstrace/shutdown-requested marker`);
445
+ }
446
+ }
447
+ } catch (err) {
448
+ warnings.push(
449
+ `Shutdown marker write failed: ${err instanceof Error ? err.message : String(err)}`
450
+ );
451
+ }
366
452
  try {
367
453
  let configHandled = false;
368
454
  for (const name of NEXT_CONFIG_NAMES) {
369
- const configPath = path.join(projectRoot, name);
370
- if (!fs.existsSync(configPath)) {
455
+ const configPath = path2.join(projectRoot, name);
456
+ if (!fs2.existsSync(configPath)) {
371
457
  continue;
372
458
  }
373
- const content = fs.readFileSync(configPath, "utf-8");
459
+ const content = fs2.readFileSync(configPath, "utf-8");
374
460
  if (!content.includes("withGlasstraceConfig")) {
375
461
  continue;
376
462
  }
@@ -380,7 +466,7 @@ async function runUninit(options) {
380
466
  const cleaned = removeGlasstraceConfigImport(unwrapResult.content);
381
467
  const final = cleanLeadingBlankLines(cleaned);
382
468
  if (!dryRun) {
383
- fs.writeFileSync(configPath, final, "utf-8");
469
+ fs2.writeFileSync(configPath, final, "utf-8");
384
470
  }
385
471
  summary.push(`${prefix}Unwrapped withGlasstraceConfig from ${name}`);
386
472
  configHandled = true;
@@ -401,20 +487,20 @@ async function runUninit(options) {
401
487
  );
402
488
  }
403
489
  try {
404
- const instrPath = path.join(projectRoot, "instrumentation.ts");
405
- if (fs.existsSync(instrPath)) {
406
- const content = fs.readFileSync(instrPath, "utf-8");
490
+ const instrPath = path2.join(projectRoot, "instrumentation.ts");
491
+ if (fs2.existsSync(instrPath)) {
492
+ const content = fs2.readFileSync(instrPath, "utf-8");
407
493
  if (content.includes("registerGlasstrace") || content.includes("@glasstrace/sdk")) {
408
494
  if (isInitCreatedInstrumentation(content)) {
409
495
  if (!dryRun) {
410
- fs.unlinkSync(instrPath);
496
+ fs2.unlinkSync(instrPath);
411
497
  }
412
498
  summary.push(`${prefix}Deleted instrumentation.ts (init-created)`);
413
499
  } else {
414
500
  const cleaned = removeRegisterGlasstrace(content);
415
501
  if (cleaned !== content) {
416
502
  if (!dryRun) {
417
- fs.writeFileSync(instrPath, cleaned, "utf-8");
503
+ fs2.writeFileSync(instrPath, cleaned, "utf-8");
418
504
  }
419
505
  summary.push(
420
506
  `${prefix}Removed registerGlasstrace() from instrumentation.ts`
@@ -429,10 +515,10 @@ async function runUninit(options) {
429
515
  );
430
516
  }
431
517
  try {
432
- const glasstraceDir = path.join(projectRoot, ".glasstrace");
433
- if (fs.existsSync(glasstraceDir)) {
518
+ const glasstraceDir = path2.join(projectRoot, ".glasstrace");
519
+ if (fs2.existsSync(glasstraceDir)) {
434
520
  if (!dryRun) {
435
- fs.rmSync(glasstraceDir, { recursive: true, force: true });
521
+ fs2.rmSync(glasstraceDir, { recursive: true, force: true });
436
522
  }
437
523
  summary.push(`${prefix}Removed .glasstrace/ directory`);
438
524
  }
@@ -442,26 +528,60 @@ async function runUninit(options) {
442
528
  );
443
529
  }
444
530
  try {
445
- const envPath = path.join(projectRoot, ".env.local");
446
- if (fs.existsSync(envPath)) {
447
- const content = fs.readFileSync(envPath, "utf-8");
448
- const lines = content.split("\n");
449
- const filtered = lines.filter((line) => {
450
- const trimmed = line.trim();
451
- return !(/^\s*#?\s*GLASSTRACE_API_KEY\s*=/.test(trimmed) || /^\s*#?\s*GLASSTRACE_COVERAGE_MAP\s*=/.test(trimmed));
452
- });
453
- if (filtered.length !== lines.length) {
454
- const result = filtered.join("\n");
455
- if (result.trim().length === 0) {
456
- if (!dryRun) {
457
- fs.unlinkSync(envPath);
458
- }
459
- summary.push(`${prefix}Deleted .env.local (no remaining entries)`);
531
+ const envPath = path2.join(projectRoot, ".env.local");
532
+ if (fs2.existsSync(envPath)) {
533
+ const content = fs2.readFileSync(envPath, "utf-8");
534
+ const existingKey = readEnvLocalApiKey(content);
535
+ const hasDevKey = isDevApiKey(existingKey);
536
+ let proceed = true;
537
+ let devKeyPath = "none";
538
+ if (hasDevKey) {
539
+ if (dryRun) {
540
+ devKeyPath = "dry-run-preview";
541
+ } else if (force) {
542
+ devKeyPath = "force-bypass";
460
543
  } else {
461
- if (!dryRun) {
462
- fs.writeFileSync(envPath, result, "utf-8");
544
+ const confirmed = await prompt(
545
+ ".env.local contains a claimed Glasstrace developer API key (gt_dev_...). Removing it will require you to re-authenticate. Continue?",
546
+ false
547
+ );
548
+ proceed = confirmed;
549
+ if (confirmed) devKeyPath = "interactive-confirmed";
550
+ }
551
+ }
552
+ if (!proceed) {
553
+ warnings.push(
554
+ "Preserved GLASSTRACE_API_KEY in .env.local (claimed dev key; re-run with --force to remove)"
555
+ );
556
+ } else {
557
+ const lines = content.split("\n");
558
+ const filtered = lines.filter((line) => {
559
+ const trimmed = line.trim();
560
+ return !(/^\s*#?\s*GLASSTRACE_API_KEY\s*=/.test(trimmed) || /^\s*#?\s*GLASSTRACE_COVERAGE_MAP\s*=/.test(trimmed));
561
+ });
562
+ if (filtered.length !== lines.length) {
563
+ const result = filtered.join("\n");
564
+ if (result.trim().length === 0) {
565
+ if (!dryRun) {
566
+ fs2.unlinkSync(envPath);
567
+ }
568
+ summary.push(`${prefix}Deleted .env.local (no remaining entries)`);
569
+ } else {
570
+ if (!dryRun) {
571
+ fs2.writeFileSync(envPath, result, "utf-8");
572
+ }
573
+ let devKeyAnnotation = "";
574
+ if (devKeyPath === "interactive-confirmed") {
575
+ devKeyAnnotation = " (dev key confirmed)";
576
+ } else if (devKeyPath === "force-bypass") {
577
+ devKeyAnnotation = " (dev key removed via --force)";
578
+ } else if (devKeyPath === "dry-run-preview") {
579
+ devKeyAnnotation = " (dev key would be removed; real run would require confirmation)";
580
+ }
581
+ summary.push(
582
+ `${prefix}Removed GLASSTRACE entries from .env.local${devKeyAnnotation}`
583
+ );
463
584
  }
464
- summary.push(`${prefix}Removed GLASSTRACE entries from .env.local`);
465
585
  }
466
586
  }
467
587
  }
@@ -471,9 +591,9 @@ async function runUninit(options) {
471
591
  );
472
592
  }
473
593
  try {
474
- const gitignorePath = path.join(projectRoot, ".gitignore");
475
- if (fs.existsSync(gitignorePath)) {
476
- const content = fs.readFileSync(gitignorePath, "utf-8");
594
+ const gitignorePath = path2.join(projectRoot, ".gitignore");
595
+ if (fs2.existsSync(gitignorePath)) {
596
+ const content = fs2.readFileSync(gitignorePath, "utf-8");
477
597
  const lines = content.split("\n");
478
598
  const mcpGitignoreEntries = /* @__PURE__ */ new Set([
479
599
  ".glasstrace/",
@@ -489,12 +609,12 @@ async function runUninit(options) {
489
609
  const result = filtered.join("\n");
490
610
  if (result.trim().length === 0) {
491
611
  if (!dryRun) {
492
- fs.unlinkSync(gitignorePath);
612
+ fs2.unlinkSync(gitignorePath);
493
613
  }
494
614
  summary.push(`${prefix}Deleted .gitignore (no remaining entries)`);
495
615
  } else {
496
616
  if (!dryRun) {
497
- fs.writeFileSync(gitignorePath, result, "utf-8");
617
+ fs2.writeFileSync(gitignorePath, result, "utf-8");
498
618
  }
499
619
  summary.push(`${prefix}Removed Glasstrace entries from .gitignore`);
500
620
  }
@@ -507,63 +627,63 @@ async function runUninit(options) {
507
627
  }
508
628
  try {
509
629
  for (const configFile of MCP_CONFIG_FILES) {
510
- const configPath = path.join(projectRoot, configFile);
511
- if (!fs.existsSync(configPath)) {
630
+ const configPath = path2.join(projectRoot, configFile);
631
+ if (!fs2.existsSync(configPath)) {
512
632
  continue;
513
633
  }
514
- const content = fs.readFileSync(configPath, "utf-8");
634
+ const content = fs2.readFileSync(configPath, "utf-8");
515
635
  const result = processJsonMcpConfig(content);
516
636
  if (result.action === "deleted") {
517
637
  if (!dryRun) {
518
- fs.unlinkSync(configPath);
638
+ fs2.unlinkSync(configPath);
519
639
  }
520
640
  summary.push(`${prefix}Deleted ${configFile}`);
521
641
  } else if (result.action === "removed-key" && result.content !== void 0) {
522
642
  if (!dryRun) {
523
- fs.writeFileSync(configPath, result.content, "utf-8");
643
+ fs2.writeFileSync(configPath, result.content, "utf-8");
524
644
  }
525
645
  summary.push(`${prefix}Removed glasstrace from ${configFile}`);
526
646
  }
527
647
  }
528
- const codexConfigPath = path.join(projectRoot, ".codex", "config.toml");
529
- if (fs.existsSync(codexConfigPath)) {
530
- const content = fs.readFileSync(codexConfigPath, "utf-8");
648
+ const codexConfigPath = path2.join(projectRoot, ".codex", "config.toml");
649
+ if (fs2.existsSync(codexConfigPath)) {
650
+ const content = fs2.readFileSync(codexConfigPath, "utf-8");
531
651
  const tomlResult = processTomlMcpConfig(content);
532
652
  if (tomlResult.action === "deleted") {
533
653
  if (!dryRun) {
534
- fs.unlinkSync(codexConfigPath);
654
+ fs2.unlinkSync(codexConfigPath);
535
655
  }
536
656
  summary.push(`${prefix}Deleted .codex/config.toml`);
537
657
  } else if (tomlResult.action === "removed-section" && tomlResult.content !== void 0) {
538
658
  if (!dryRun) {
539
- fs.writeFileSync(codexConfigPath, tomlResult.content, "utf-8");
659
+ fs2.writeFileSync(codexConfigPath, tomlResult.content, "utf-8");
540
660
  }
541
661
  summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);
542
662
  }
543
663
  }
544
- const hasWindsurfMarkers = fs.existsSync(path.join(projectRoot, ".windsurfrules")) || fs.existsSync(path.join(projectRoot, ".windsurf"));
664
+ const hasWindsurfMarkers = fs2.existsSync(path2.join(projectRoot, ".windsurfrules")) || fs2.existsSync(path2.join(projectRoot, ".windsurf"));
545
665
  if (hasWindsurfMarkers) {
546
- const windsurfConfigPath = path.join(
666
+ const windsurfConfigPath = path2.join(
547
667
  os.homedir(),
548
668
  ".codeium",
549
669
  "windsurf",
550
670
  "mcp_config.json"
551
671
  );
552
- if (fs.existsSync(windsurfConfigPath)) {
553
- const content = fs.readFileSync(windsurfConfigPath, "utf-8");
672
+ if (fs2.existsSync(windsurfConfigPath)) {
673
+ const content = fs2.readFileSync(windsurfConfigPath, "utf-8");
554
674
  const windsurfResult = processJsonMcpConfig(content);
555
675
  const home = os.homedir();
556
676
  const displayPath = windsurfConfigPath.startsWith(home) ? "~" + windsurfConfigPath.slice(home.length) : windsurfConfigPath;
557
677
  if (windsurfResult.action === "deleted") {
558
678
  if (!dryRun) {
559
- fs.unlinkSync(windsurfConfigPath);
679
+ fs2.unlinkSync(windsurfConfigPath);
560
680
  }
561
681
  summary.push(
562
682
  `${prefix}Deleted global Windsurf config (${displayPath})`
563
683
  );
564
684
  } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
565
685
  if (!dryRun) {
566
- fs.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
686
+ fs2.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
567
687
  }
568
688
  summary.push(
569
689
  `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`
@@ -578,21 +698,21 @@ async function runUninit(options) {
578
698
  }
579
699
  try {
580
700
  for (const infoFile of AGENT_INFO_FILES) {
581
- const filePath = path.join(projectRoot, infoFile);
582
- if (!fs.existsSync(filePath)) {
701
+ const filePath = path2.join(projectRoot, infoFile);
702
+ if (!fs2.existsSync(filePath)) {
583
703
  continue;
584
704
  }
585
- const content = fs.readFileSync(filePath, "utf-8");
705
+ const content = fs2.readFileSync(filePath, "utf-8");
586
706
  const result = removeMarkerSection(content);
587
707
  if (result.removed) {
588
708
  if (result.content.trim().length === 0) {
589
709
  if (!dryRun) {
590
- fs.unlinkSync(filePath);
710
+ fs2.unlinkSync(filePath);
591
711
  }
592
712
  summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);
593
713
  } else {
594
714
  if (!dryRun) {
595
- fs.writeFileSync(filePath, result.content, "utf-8");
715
+ fs2.writeFileSync(filePath, result.content, "utf-8");
596
716
  }
597
717
  summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);
598
718
  }
@@ -621,6 +741,7 @@ async function runUninit(options) {
621
741
  runUninit,
622
742
  skipString,
623
743
  unwrapCJSExport,
624
- unwrapExport
744
+ unwrapExport,
745
+ writeShutdownMarker
625
746
  });
626
747
  //# sourceMappingURL=uninit.cjs.map