@glasstrace/sdk 0.7.1 → 0.7.3

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.
@@ -35,31 +35,34 @@ async function scaffoldNextConfig(projectRoot) {
35
35
  }
36
36
  }
37
37
  if (configPath === void 0 || configName === void 0) {
38
- return false;
38
+ return null;
39
39
  }
40
40
  const existing = fs.readFileSync(configPath, "utf-8");
41
+ if (existing.trim().length === 0) {
42
+ return { modified: false, reason: "empty-file" };
43
+ }
41
44
  if (existing.includes("withGlasstraceConfig")) {
42
- return false;
45
+ return { modified: false, reason: "already-wrapped" };
43
46
  }
44
47
  const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
45
48
  if (isESM) {
46
49
  const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
47
50
  const wrapResult2 = wrapExport(existing);
48
51
  if (!wrapResult2.wrapped) {
49
- return false;
52
+ return { modified: false, reason: "no-export" };
50
53
  }
51
54
  const modified2 = importLine + "\n" + wrapResult2.content;
52
55
  fs.writeFileSync(configPath, modified2, "utf-8");
53
- return true;
56
+ return { modified: true };
54
57
  }
55
58
  const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
56
59
  const wrapResult = wrapCJSExport(existing);
57
60
  if (!wrapResult.wrapped) {
58
- return false;
61
+ return { modified: false, reason: "no-export" };
59
62
  }
60
63
  const modified = requireLine + "\n" + wrapResult.content;
61
64
  fs.writeFileSync(configPath, modified, "utf-8");
62
- return true;
65
+ return { modified: true };
63
66
  }
64
67
  function wrapExport(content) {
65
68
  const marker = "export default";
@@ -449,8 +452,11 @@ function generateInfoSection(agent, endpoint) {
449
452
  `Glasstrace is configured as an MCP server at: ${endpoint}`,
450
453
  "",
451
454
  "Available tools:",
452
- "- `glasstrace_submit_trace` - Submit trace data for debugging analysis",
453
- "- `glasstrace_get_config` - Retrieve current SDK configuration",
455
+ "- `get_latest_error` - Get the most recent error trace from the current session",
456
+ "- `get_trace` - Get a specific trace by ID or URL pattern",
457
+ "- `get_root_cause` - Get the full span tree and root cause analysis for an error",
458
+ "- `get_test_suggestions` - Get test suggestions based on recent errors",
459
+ "- `get_session_timeline` - Get the timeline of all traces in the current session",
454
460
  "",
455
461
  "To reconfigure, run: `npx glasstrace mcp add`",
456
462
  ""
@@ -658,6 +664,20 @@ async function updateGitignore(paths, projectRoot) {
658
664
  }
659
665
  }
660
666
 
667
+ // src/cli/constants.ts
668
+ var MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
669
+ function formatAgentName(name) {
670
+ const displayNames = {
671
+ claude: "Claude Code",
672
+ codex: "Codex",
673
+ gemini: "Gemini",
674
+ cursor: "Cursor",
675
+ windsurf: "Windsurf",
676
+ generic: "Generic"
677
+ };
678
+ return displayNames[name];
679
+ }
680
+
661
681
  export {
662
682
  scaffoldInstrumentation,
663
683
  scaffoldNextConfig,
@@ -670,6 +690,8 @@ export {
670
690
  generateInfoSection,
671
691
  writeMcpConfig,
672
692
  injectInfoSection,
673
- updateGitignore
693
+ updateGitignore,
694
+ MCP_ENDPOINT,
695
+ formatAgentName
674
696
  };
675
- //# sourceMappingURL=chunk-PLJVIWHN.js.map
697
+ //# sourceMappingURL=chunk-CKK6VKKC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/scaffolder.ts","../src/agent-detection/detect.ts","../src/agent-detection/configs.ts","../src/agent-detection/inject.ts","../src/cli/constants.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Computes a stable identity fingerprint for deduplication purposes.\n * This is NOT password hashing — the input is an opaque token used\n * as a marker identity, not a credential stored for authentication.\n *\n * @internal Exported for unit testing only.\n */\nexport function identityFingerprint(token: string): string {\n return `sha256:${createHash(\"sha256\").update(token).digest(\"hex\")}`;\n}\n\n/** Result of attempting to wrap next.config with withGlasstraceConfig. */\nexport interface ScaffoldNextConfigResult {\n modified: boolean;\n reason?: \"already-wrapped\" | \"empty-file\" | \"no-export\";\n}\n\n/** Next.js config file names in priority order */\nconst NEXT_CONFIG_NAMES = [\"next.config.ts\", \"next.config.js\", \"next.config.mjs\"] as const;\n\n/**\n * Generates `instrumentation.ts` with a `registerGlasstrace()` call.\n * If the file exists and `force` is false, the file is not overwritten.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @param force - When true, overwrite an existing instrumentation.ts file.\n * @returns True if the file was written, false if it was skipped.\n */\nexport async function scaffoldInstrumentation(\n projectRoot: string,\n force: boolean,\n): Promise<boolean> {\n const filePath = path.join(projectRoot, \"instrumentation.ts\");\n\n if (fs.existsSync(filePath) && !force) {\n return false;\n }\n\n const content = `import { registerGlasstrace } from \"@glasstrace/sdk\";\n\nexport async function register() {\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}\n`;\n\n fs.writeFileSync(filePath, content, \"utf-8\");\n return true;\n}\n\n/**\n * Detects `next.config.js`, `next.config.ts`, or `next.config.mjs` and wraps\n * with `withGlasstraceConfig()`. If the config already contains\n * `withGlasstraceConfig`, the file is not modified.\n *\n * For CJS `.js` configs, adds a `require()` call and wraps `module.exports`.\n * The SDK ships dual ESM/CJS builds via tsup + conditional exports, so\n * `require(\"@glasstrace/sdk\")` resolves to the CJS entrypoint natively.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns A result object describing what happened, or `null` if no config\n * file was found at all.\n */\nexport async function scaffoldNextConfig(\n projectRoot: string,\n): Promise<ScaffoldNextConfigResult | null> {\n let configPath: string | undefined;\n let configName: string | undefined;\n\n for (const name of NEXT_CONFIG_NAMES) {\n const candidate = path.join(projectRoot, name);\n if (fs.existsSync(candidate)) {\n configPath = candidate;\n configName = name;\n break;\n }\n }\n\n if (configPath === undefined || configName === undefined) {\n return null;\n }\n\n const existing = fs.readFileSync(configPath, \"utf-8\");\n\n // Guard: empty or whitespace-only files have no export to wrap\n if (existing.trim().length === 0) {\n return { modified: false, reason: \"empty-file\" };\n }\n\n // Already wrapped — skip even in force mode to avoid double-wrapping\n if (existing.includes(\"withGlasstraceConfig\")) {\n return { modified: false, reason: \"already-wrapped\" };\n }\n\n const isESM = configName.endsWith(\".ts\") || configName.endsWith(\".mjs\");\n\n if (isESM) {\n // ESM: static import at top of file, wrap the export\n const importLine = 'import { withGlasstraceConfig } from \"@glasstrace/sdk\";\\n';\n const wrapResult = wrapExport(existing);\n if (!wrapResult.wrapped) {\n return { modified: false, reason: \"no-export\" };\n }\n const modified = importLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return { modified: true };\n }\n\n // CJS (.js): require() the SDK (resolves to the CJS dist build) and\n // wrap the module.exports expression in place — no file renaming needed.\n const requireLine = 'const { withGlasstraceConfig } = require(\"@glasstrace/sdk\");\\n';\n const wrapResult = wrapCJSExport(existing);\n if (!wrapResult.wrapped) {\n return { modified: false, reason: \"no-export\" };\n }\n const modified = requireLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return { modified: true };\n}\n\n/** @internal Exported for unit testing only. */\nexport interface WrapResult {\n content: string;\n wrapped: boolean;\n}\n\n/**\n * Wraps an ESM `export default` expression with `withGlasstraceConfig()`.\n *\n * Strategy: find the last `export default` in the file. Everything from\n * that statement to EOF is the exported expression. Strip optional trailing\n * semicolons/whitespace and wrap with `withGlasstraceConfig(...)`.\n *\n * @param content - The full file content containing an ESM default export.\n * @returns `{ wrapped: true, content }` on success, or `{ wrapped: false }` if\n * no recognizable export pattern was found (content returned unchanged).\n * @internal Exported for unit testing only.\n */\nexport function wrapExport(content: string): WrapResult {\n // Find the last `export default` — use lastIndexOf for robustness\n const marker = \"export default\";\n const idx = content.lastIndexOf(marker);\n if (idx === -1) {\n return { content, wrapped: false };\n }\n\n const preamble = content.slice(0, idx);\n const exprRaw = content.slice(idx + marker.length);\n // Trim leading whitespace; strip trailing semicolon + whitespace\n const expr = exprRaw.trim().replace(/;?\\s*$/, \"\");\n if (expr.length === 0) {\n return { content, wrapped: false };\n }\n\n return {\n content: preamble + `export default withGlasstraceConfig(${expr});\\n`,\n wrapped: true,\n };\n}\n\n/**\n * Wraps a CJS `module.exports = expr` with `withGlasstraceConfig()`.\n *\n * Strategy: find the last `module.exports =` in the file. Everything from\n * that statement to EOF is the exported expression. Strip optional trailing\n * semicolons/whitespace and wrap with `module.exports = withGlasstraceConfig(...)`.\n *\n * @param content - The full CJS file content containing `module.exports = ...`.\n * @returns `{ wrapped: true, content }` on success, or `{ wrapped: false }` if\n * no recognizable `module.exports` pattern was found (content returned unchanged).\n * @internal Exported for unit testing only.\n */\nexport function wrapCJSExport(content: string): WrapResult {\n const cjsMarker = \"module.exports\";\n const cjsIdx = content.lastIndexOf(cjsMarker);\n if (cjsIdx === -1) {\n return { content, wrapped: false };\n }\n\n const preamble = content.slice(0, cjsIdx);\n const afterMarker = content.slice(cjsIdx + cjsMarker.length);\n const eqMatch = /^\\s*=\\s*/.exec(afterMarker);\n if (!eqMatch) {\n return { content, wrapped: false };\n }\n\n const exprRaw = afterMarker.slice(eqMatch[0].length);\n const expr = exprRaw.trim().replace(/;?\\s*$/, \"\");\n if (expr.length === 0) {\n return { content, wrapped: false };\n }\n\n return {\n content: preamble + `module.exports = withGlasstraceConfig(${expr});\\n`,\n wrapped: true,\n };\n}\n\n/**\n * Creates `.env.local` with `GLASSTRACE_API_KEY=` placeholder, or appends\n * to an existing file if it does not already contain `GLASSTRACE_API_KEY`.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already configured.\n */\nexport async function scaffoldEnvLocal(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".env.local\");\n\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, \"utf-8\");\n if (/^\\s*#?\\s*GLASSTRACE_API_KEY\\s*=/m.test(existing)) {\n return false;\n }\n // Append with a newline separator if needed\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \"# GLASSTRACE_API_KEY=your_key_here\\n\", \"utf-8\");\n return true;\n }\n\n fs.writeFileSync(filePath, \"# GLASSTRACE_API_KEY=your_key_here\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Adds `GLASSTRACE_COVERAGE_MAP=true` to `.env.local`.\n * Creates the file if it does not exist. If the key is already present\n * with a value other than `true`, it is updated in place.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already set to `true`.\n */\nexport async function addCoverageMapEnv(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".env.local\");\n\n if (!fs.existsSync(filePath)) {\n fs.writeFileSync(filePath, \"GLASSTRACE_COVERAGE_MAP=true\\n\", \"utf-8\");\n return true;\n }\n\n const existing = fs.readFileSync(filePath, \"utf-8\");\n const keyRegex = /^(\\s*GLASSTRACE_COVERAGE_MAP\\s*=\\s*)(.*)$/m;\n const keyMatch = keyRegex.exec(existing);\n\n if (keyMatch) {\n const currentValue = keyMatch[2].trim();\n if (currentValue === \"true\") {\n // Already set to true — nothing to do\n return false;\n }\n // Key exists but is not `true` — update in place\n const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);\n fs.writeFileSync(filePath, updated, \"utf-8\");\n return true;\n }\n\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \"GLASSTRACE_COVERAGE_MAP=true\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Adds `.glasstrace/` to `.gitignore`, or creates `.gitignore` if missing.\n * Does not add duplicate entries.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns True if the file was created or modified, false if already configured.\n */\nexport async function scaffoldGitignore(projectRoot: string): Promise<boolean> {\n const filePath = path.join(projectRoot, \".gitignore\");\n\n if (fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, \"utf-8\");\n // Check line-by-line to avoid false positive partial matches\n const lines = existing.split(\"\\n\").map((l) => l.trim());\n if (lines.includes(\".glasstrace/\")) {\n return false;\n }\n const separator = existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n fs.writeFileSync(filePath, existing + separator + \".glasstrace/\\n\", \"utf-8\");\n return true;\n }\n\n fs.writeFileSync(filePath, \".glasstrace/\\n\", \"utf-8\");\n return true;\n}\n\n/**\n * Creates the `.glasstrace/mcp-connected` marker file, or overwrites it\n * if the key has changed (key rotation).\n *\n * The marker file records a SHA-256 fingerprint of the anonymous key and\n * the ISO 8601 timestamp when it was written. It is used by the nudge\n * system to suppress \"MCP not configured\" prompts.\n *\n * If the marker already exists with the same key fingerprint, this is a\n * no-op (the timestamp is NOT refreshed).\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @param anonKey - The anonymous API key to fingerprint.\n * @returns True if the marker was created or updated, false if it already\n * exists with the same key fingerprint.\n */\nexport async function scaffoldMcpMarker(\n projectRoot: string,\n anonKey: string,\n): Promise<boolean> {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n const markerPath = path.join(dirPath, \"mcp-connected\");\n const keyHash = identityFingerprint(anonKey);\n\n // Check if marker already exists with the same key hash\n if (fs.existsSync(markerPath)) {\n try {\n const existing = JSON.parse(fs.readFileSync(markerPath, \"utf-8\")) as {\n keyHash?: string;\n };\n if (existing.keyHash === keyHash) {\n return false;\n }\n } catch {\n // Corrupted marker — overwrite\n }\n }\n\n // Create directory with restricted permissions\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });\n\n const marker = JSON.stringify(\n { keyHash, configuredAt: new Date().toISOString() },\n null,\n 2,\n );\n\n fs.writeFileSync(markerPath, marker, { mode: 0o600 });\n\n // Ensure permissions even if file pre-existed (writeFile mode only\n // applies on creation on some platforms)\n fs.chmodSync(markerPath, 0o600);\n\n return true;\n}\n","import { execFile } from \"node:child_process\";\nimport { access, stat } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { constants } from \"node:fs\";\n\n/**\n * Describes an AI coding agent detected in a project.\n */\nexport interface DetectedAgent {\n name: \"claude\" | \"codex\" | \"gemini\" | \"cursor\" | \"windsurf\" | \"generic\";\n mcpConfigPath: string | null;\n infoFilePath: string | null;\n cliAvailable: boolean;\n registrationCommand: string | null;\n}\n\ntype AgentName = DetectedAgent[\"name\"];\n\ninterface AgentRule {\n name: AgentName;\n /** Paths relative to a search directory that indicate this agent is present. */\n markers: string[];\n /** Function to compute the MCP config path given the directory where markers were found. */\n mcpConfigPath: (markerDir: string) => string;\n /** Function to compute the info file path, or null. */\n infoFilePath: (markerDir: string) => string | null;\n /** CLI binary name to check in PATH, or null if no CLI exists. */\n cliBinary: string | null;\n /** Registration command template, or null. */\n registrationCommand: string | null;\n}\n\nconst AGENT_RULES: AgentRule[] = [\n {\n name: \"claude\",\n markers: [\".claude\", \"CLAUDE.md\"],\n mcpConfigPath: (dir) => join(dir, \".mcp.json\"),\n infoFilePath: (dir) => join(dir, \"CLAUDE.md\"),\n cliBinary: \"claude\",\n registrationCommand: \"npx glasstrace mcp add --agent claude\",\n },\n {\n name: \"codex\",\n markers: [\"codex.md\", \".codex\"],\n mcpConfigPath: (dir) => join(dir, \".codex\", \"config.toml\"),\n infoFilePath: (dir) => join(dir, \"codex.md\"),\n cliBinary: \"codex\",\n registrationCommand: \"npx glasstrace mcp add --agent codex\",\n },\n {\n name: \"gemini\",\n markers: [\".gemini\"],\n mcpConfigPath: (dir) => join(dir, \".gemini\", \"settings.json\"),\n infoFilePath: () => null,\n cliBinary: \"gemini\",\n registrationCommand: \"npx glasstrace mcp add --agent gemini\",\n },\n {\n name: \"cursor\",\n markers: [\".cursor\", \".cursorrules\"],\n mcpConfigPath: (dir) => join(dir, \".cursor\", \"mcp.json\"),\n infoFilePath: (dir) => join(dir, \".cursorrules\"),\n cliBinary: null,\n registrationCommand: \"npx glasstrace mcp add --agent cursor\",\n },\n {\n name: \"windsurf\",\n markers: [\".windsurfrules\", \".windsurf\"],\n mcpConfigPath: () =>\n join(homedir(), \".codeium\", \"windsurf\", \"mcp_config.json\"),\n infoFilePath: (dir) => join(dir, \".windsurfrules\"),\n cliBinary: null,\n registrationCommand: \"npx glasstrace mcp add --agent windsurf\",\n },\n];\n\n/**\n * Checks whether a path exists and is accessible, following symlinks.\n * Returns false on permission errors or missing paths.\n *\n * @param mode - The access mode to check (defaults to R_OK for marker detection).\n */\nasync function pathExists(\n path: string,\n mode: number = constants.R_OK,\n): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Finds the git root directory by walking up from the given path.\n * Returns the starting directory if no `.git` is found.\n */\nasync function findGitRoot(startDir: string): Promise<string> {\n let current = resolve(startDir);\n\n while (true) {\n if (await pathExists(join(current, \".git\"), constants.F_OK)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root without finding .git\n break;\n }\n current = parent;\n }\n\n return resolve(startDir);\n}\n\n/**\n * Returns true if a CLI binary is available on PATH.\n * Uses `which` on Unix and `where` on Windows, via execFile (no shell injection).\n */\nfunction isCliAvailable(binary: string): Promise<boolean> {\n return new Promise((resolve) => {\n const command = process.platform === \"win32\" ? \"where\" : \"which\";\n execFile(command, [binary], (error) => {\n resolve(error === null);\n });\n });\n}\n\n/**\n * Detects AI coding agents present in a project by scanning for marker\n * files and directories. Walks up from projectRoot to the git root to\n * support monorepo layouts.\n *\n * Always includes a \"generic\" fallback entry.\n *\n * @param projectRoot - Absolute or relative path to the project directory.\n * @returns Array of detected agents, with generic always last.\n * @throws If projectRoot does not exist or is not a directory.\n */\nexport async function detectAgents(\n projectRoot: string,\n): Promise<DetectedAgent[]> {\n const resolvedRoot = resolve(projectRoot);\n\n // Validate projectRoot exists and is a directory\n let rootStat;\n try {\n rootStat = await stat(resolvedRoot);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n throw new Error(\n `projectRoot does not exist: ${resolvedRoot}` +\n (code ? ` (${code})` : \"\"),\n );\n }\n\n if (!rootStat.isDirectory()) {\n throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);\n }\n\n const gitRoot = await findGitRoot(resolvedRoot);\n\n // Collect unique directories to search: projectRoot and every ancestor up to gitRoot\n const searchDirs: string[] = [];\n let current = resolvedRoot;\n while (true) {\n searchDirs.push(current);\n if (current === gitRoot) {\n break;\n }\n const parent = dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n\n const detected: DetectedAgent[] = [];\n const seenAgents = new Set<AgentName>();\n\n for (const rule of AGENT_RULES) {\n let foundDir: string | null = null;\n\n // Check each search directory for markers\n for (const dir of searchDirs) {\n let markerFound = false;\n for (const marker of rule.markers) {\n if (await pathExists(join(dir, marker))) {\n markerFound = true;\n break;\n }\n }\n if (markerFound) {\n foundDir = dir;\n break;\n }\n }\n\n if (foundDir === null) {\n continue;\n }\n\n if (seenAgents.has(rule.name)) {\n continue;\n }\n seenAgents.add(rule.name);\n\n // Determine info file path — only include if the file actually exists\n let infoFilePath = rule.infoFilePath(foundDir);\n if (infoFilePath !== null && !(await pathExists(infoFilePath))) {\n infoFilePath = null;\n }\n\n const cliAvailable = rule.cliBinary\n ? await isCliAvailable(rule.cliBinary)\n : false;\n\n detected.push({\n name: rule.name,\n mcpConfigPath: rule.mcpConfigPath(foundDir),\n infoFilePath,\n cliAvailable,\n registrationCommand: rule.registrationCommand,\n });\n }\n\n // Always include generic fallback\n detected.push({\n name: \"generic\",\n mcpConfigPath: join(resolvedRoot, \".glasstrace\", \"mcp.json\"),\n infoFilePath: null,\n cliAvailable: false,\n registrationCommand: null,\n });\n\n return detected;\n}\n","import type { DetectedAgent } from \"./detect.js\";\n\n/**\n * Generates the MCP server configuration content for a given agent.\n *\n * The output is the full file content suitable for writing to the agent's\n * MCP config file. Auth tokens are intentionally included here because\n * MCP config files are local-only and required for server authentication.\n *\n * @param agent - The detected agent to generate config for.\n * @param endpoint - The Glasstrace MCP endpoint URL.\n * @param anonKey - The anonymous API key for authentication.\n * @returns The formatted configuration string.\n * @throws If endpoint or anonKey is empty.\n */\nexport function generateMcpConfig(\n agent: DetectedAgent,\n endpoint: string,\n anonKey: string,\n): string {\n if (!endpoint || endpoint.trim() === \"\") {\n throw new Error(\"endpoint must not be empty\");\n }\n if (!anonKey || anonKey.trim() === \"\") {\n throw new Error(\"anonKey must not be empty\");\n }\n\n switch (agent.name) {\n case \"claude\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n type: \"http\",\n url: endpoint,\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"codex\": {\n // Escape TOML special characters in the endpoint value\n const safeEndpoint = endpoint\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"');\n return [\n \"[mcp_servers.glasstrace]\",\n `url = \"${safeEndpoint}\"`,\n `bearer_token_env_var = \"GLASSTRACE_API_KEY\"`,\n \"\",\n ].join(\"\\n\");\n }\n\n case \"gemini\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n httpUrl: endpoint,\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"cursor\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n url: endpoint,\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"windsurf\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n serverUrl: endpoint,\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n },\n },\n },\n null,\n 2,\n );\n\n case \"generic\":\n return JSON.stringify(\n {\n mcpServers: {\n glasstrace: {\n url: endpoint,\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n },\n },\n },\n null,\n 2,\n );\n }\n}\n\n/**\n * Marker pair used to delimit the Glasstrace section in agent info files.\n */\ninterface MarkerPair {\n start: string;\n end: string;\n}\n\nfunction htmlMarkers(): MarkerPair {\n return {\n start: \"<!-- glasstrace:mcp:start -->\",\n end: \"<!-- glasstrace:mcp:end -->\",\n };\n}\n\nfunction hashMarkers(): MarkerPair {\n return {\n start: \"# glasstrace:mcp:start\",\n end: \"# glasstrace:mcp:end\",\n };\n}\n\n/**\n * Generates informational content for an agent's instruction file.\n *\n * This content is designed to be appended to or inserted into agent-specific\n * instruction files (CLAUDE.md, .cursorrules, codex.md). It contains ONLY\n * the endpoint URL, tool descriptions, and setup instructions. Auth tokens\n * are NEVER included in this output.\n *\n * @param agent - The detected agent to generate info for.\n * @param endpoint - The Glasstrace MCP endpoint URL.\n * @returns The formatted info section string, or empty string for agents without a supported info file format.\n * @throws If endpoint is empty.\n */\nexport function generateInfoSection(\n agent: DetectedAgent,\n endpoint: string,\n): string {\n if (!endpoint || endpoint.trim() === \"\") {\n throw new Error(\"endpoint must not be empty\");\n }\n\n const content = [\n \"\",\n \"## Glasstrace MCP Integration\",\n \"\",\n `Glasstrace is configured as an MCP server at: ${endpoint}`,\n \"\",\n \"Available tools:\",\n \"- `get_latest_error` - Get the most recent error trace from the current session\",\n \"- `get_trace` - Get a specific trace by ID or URL pattern\",\n \"- `get_root_cause` - Get the full span tree and root cause analysis for an error\",\n \"- `get_test_suggestions` - Get test suggestions based on recent errors\",\n \"- `get_session_timeline` - Get the timeline of all traces in the current session\",\n \"\",\n \"To reconfigure, run: `npx glasstrace mcp add`\",\n \"\",\n ].join(\"\\n\");\n\n switch (agent.name) {\n case \"claude\": {\n const m = htmlMarkers();\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"codex\": {\n const m = htmlMarkers();\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"cursor\": {\n const m = hashMarkers();\n return `${m.start}\\n${content}${m.end}\\n`;\n }\n\n case \"gemini\":\n case \"windsurf\":\n case \"generic\":\n return \"\";\n }\n}\n","import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join } from \"node:path\";\nimport type { DetectedAgent } from \"./detect.js\";\n\n/** HTML comment markers used in markdown files (.md). */\nconst HTML_START = \"<!-- glasstrace:mcp:start -->\";\nconst HTML_END = \"<!-- glasstrace:mcp:end -->\";\n\n/** Hash-prefixed markers used in plain text files (.cursorrules). */\nconst HASH_START = \"# glasstrace:mcp:start\";\nconst HASH_END = \"# glasstrace:mcp:end\";\n\n/**\n * Determines whether an error is a filesystem permission or read-only error.\n * Covers EACCES (permission denied), EPERM (operation not permitted), and\n * EROFS (read-only filesystem) to handle containerized/mounted environments.\n */\nfunction isPermissionError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException).code;\n return code === \"EACCES\" || code === \"EPERM\" || code === \"EROFS\";\n}\n\n/**\n * Writes MCP configuration content to an agent's config file path.\n *\n * Creates parent directories as needed and sets file permissions to 0o600\n * (owner read/write only) since config files may contain auth tokens.\n *\n * Fails gracefully: logs a warning to stderr on permission errors instead\n * of throwing.\n *\n * @param agent - The detected agent whose config path to write to.\n * @param content - The full configuration file content.\n * @param projectRoot - The project root (reserved for future use).\n */\nexport async function writeMcpConfig(\n agent: DetectedAgent,\n content: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n projectRoot: string,\n): Promise<void> {\n if (agent.mcpConfigPath === null) {\n return;\n }\n\n const configPath = agent.mcpConfigPath;\n const parentDir = dirname(configPath);\n\n try {\n await mkdir(parentDir, { recursive: true });\n } catch (err: unknown) {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot create directory ${parentDir}: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n\n try {\n await writeFile(configPath, content, { mode: 0o600 });\n } catch (err: unknown) {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot write config file ${configPath}: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n\n // Ensure permissions are set even if the file already existed\n // (writeFile mode only applies to newly created files on some platforms)\n try {\n await chmod(configPath, 0o600);\n } catch {\n // Best-effort; the writeFile mode should have handled this\n }\n}\n\n/**\n * Finds existing marker boundaries in file content.\n *\n * Searches for both HTML comment and hash-prefixed marker formats,\n * since an existing file might use either convention.\n *\n * @returns The start and end indices (line-level) and the matched markers,\n * or null if no complete marker pair is found.\n */\nfunction findMarkerBoundaries(\n lines: string[],\n): { startIdx: number; endIdx: number } | null {\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 (trimmed === HTML_START || trimmed === HASH_START) {\n startIdx = i;\n } else if (trimmed === HTML_END || trimmed === HASH_END) {\n if (startIdx !== -1) {\n endIdx = i;\n break;\n }\n }\n }\n\n if (startIdx === -1 || endIdx === -1) {\n return null;\n }\n\n return { startIdx, endIdx };\n}\n\n/**\n * Injects an informational section into an agent's instruction file.\n *\n * Uses marker comments to enable idempotent updates:\n * - If the file contains marker pairs, replaces content between them.\n * - If the file exists but has no markers, appends the section.\n * - If the file does not exist, creates it with the section content.\n *\n * Fails gracefully: logs a warning to stderr on read-only files instead\n * of throwing.\n *\n * @param agent - The detected agent whose info file to update.\n * @param content - The section content (including markers).\n * @param projectRoot - The project root (reserved for future use).\n */\nexport async function injectInfoSection(\n agent: DetectedAgent,\n content: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n projectRoot: string,\n): Promise<void> {\n if (agent.infoFilePath === null) {\n return;\n }\n\n // Empty content means nothing to inject (e.g., agents without info sections)\n if (content === \"\") {\n return;\n }\n\n const filePath = agent.infoFilePath;\n\n let existingContent: string | null = null;\n try {\n existingContent = await readFile(filePath, \"utf-8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot read info file ${filePath}: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n }\n\n // File does not exist — create with section content\n if (existingContent === null) {\n try {\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, content, \"utf-8\");\n } catch (err: unknown) {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot write info file ${filePath}: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n return;\n }\n\n // File exists — check for markers\n const lines = existingContent.split(\"\\n\");\n const boundaries = findMarkerBoundaries(lines);\n\n let newContent: string;\n if (boundaries !== null) {\n // Replace everything from start marker through end marker (inclusive)\n const before = lines.slice(0, boundaries.startIdx);\n const after = lines.slice(boundaries.endIdx + 1);\n // content already includes markers and trailing newline\n const contentWithoutTrailingNewline = content.endsWith(\"\\n\")\n ? content.slice(0, -1)\n : content;\n newContent = [...before, contentWithoutTrailingNewline, ...after].join(\"\\n\");\n } else {\n // No markers found — append with a blank line separator\n const separator = existingContent.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n newContent = existingContent + separator + content;\n }\n\n try {\n await writeFile(filePath, newContent, \"utf-8\");\n } catch (err: unknown) {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot write info file ${filePath}: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n}\n\n/**\n * Ensures that the given paths are listed in the project's `.gitignore`.\n *\n * Only adds entries for paths that are not already present. Creates the\n * `.gitignore` file if it does not exist. Skips absolute paths (e.g.,\n * Windsurf's global config) since those are outside the project tree.\n *\n * Fails gracefully: logs a warning to stderr on permission errors.\n *\n * @param paths - Relative paths to ensure are gitignored.\n * @param projectRoot - The project root directory.\n */\nexport async function updateGitignore(\n paths: string[],\n projectRoot: string,\n): Promise<void> {\n const gitignorePath = join(projectRoot, \".gitignore\");\n\n // Filter out absolute paths — they reference locations outside the project\n // Uses isAbsolute() to handle both POSIX and Windows path formats\n const relativePaths = paths.filter((p) => !isAbsolute(p));\n\n if (relativePaths.length === 0) {\n return;\n }\n\n let existingContent = \"\";\n try {\n existingContent = await readFile(gitignorePath, \"utf-8\");\n } catch (err: unknown) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot read .gitignore: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n }\n\n // Parse existing entries, trimming whitespace for comparison\n const existingLines = existingContent\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line !== \"\");\n\n const existingSet = new Set(existingLines);\n\n // Normalize entries: trim whitespace, convert backslashes to forward slashes\n // (git ignore patterns use / as separator; backslash is an escape character),\n // drop empties, and deduplicate against existing entries.\n const toAdd = relativePaths\n .map((p) => p.trim().replace(/\\\\/g, \"/\"))\n .filter((p) => p !== \"\" && !existingSet.has(p));\n\n if (toAdd.length === 0) {\n return;\n }\n\n // Ensure file ends with newline before appending\n let updatedContent = existingContent;\n if (updatedContent.length > 0 && !updatedContent.endsWith(\"\\n\")) {\n updatedContent += \"\\n\";\n }\n\n updatedContent += toAdd.join(\"\\n\") + \"\\n\";\n\n try {\n await writeFile(gitignorePath, updatedContent, \"utf-8\");\n } catch (err: unknown) {\n if (isPermissionError(err)) {\n process.stderr.write(\n `Warning: cannot write .gitignore: permission denied\\n`,\n );\n return;\n }\n throw err;\n }\n}\n","import type { DetectedAgent } from \"../agent-detection/detect.js\";\n\n/** Glasstrace MCP endpoint for agent configuration. */\nexport const MCP_ENDPOINT = \"https://api.glasstrace.dev/mcp\";\n\n/** Maps internal agent name to a human-readable display name. */\nexport function formatAgentName(name: DetectedAgent[\"name\"]): string {\n const displayNames: Record<DetectedAgent[\"name\"], string> = {\n claude: \"Claude Code\",\n codex: \"Codex\",\n gemini: \"Gemini\",\n cursor: \"Cursor\",\n windsurf: \"Windsurf\",\n generic: \"Generic\",\n };\n return displayNames[name];\n}\n"],"mappings":"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","names":["wrapResult","modified","join","path","resolve","dirname","join"]}
package/dist/cli/init.cjs CHANGED
@@ -64,31 +64,34 @@ async function scaffoldNextConfig(projectRoot) {
64
64
  }
65
65
  }
66
66
  if (configPath === void 0 || configName === void 0) {
67
- return false;
67
+ return null;
68
68
  }
69
69
  const existing = fs.readFileSync(configPath, "utf-8");
70
+ if (existing.trim().length === 0) {
71
+ return { modified: false, reason: "empty-file" };
72
+ }
70
73
  if (existing.includes("withGlasstraceConfig")) {
71
- return false;
74
+ return { modified: false, reason: "already-wrapped" };
72
75
  }
73
76
  const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
74
77
  if (isESM) {
75
78
  const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
76
79
  const wrapResult2 = wrapExport(existing);
77
80
  if (!wrapResult2.wrapped) {
78
- return false;
81
+ return { modified: false, reason: "no-export" };
79
82
  }
80
83
  const modified2 = importLine + "\n" + wrapResult2.content;
81
84
  fs.writeFileSync(configPath, modified2, "utf-8");
82
- return true;
85
+ return { modified: true };
83
86
  }
84
87
  const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
85
88
  const wrapResult = wrapCJSExport(existing);
86
89
  if (!wrapResult.wrapped) {
87
- return false;
90
+ return { modified: false, reason: "no-export" };
88
91
  }
89
92
  const modified = requireLine + "\n" + wrapResult.content;
90
93
  fs.writeFileSync(configPath, modified, "utf-8");
91
- return true;
94
+ return { modified: true };
92
95
  }
93
96
  function wrapExport(content) {
94
97
  const marker = "export default";
@@ -14608,6 +14611,14 @@ var init_zod = __esm({
14608
14611
  });
14609
14612
 
14610
14613
  // ../protocol/dist/index.js
14614
+ function randomHex(byteCount) {
14615
+ const bytes = new Uint8Array(byteCount);
14616
+ crypto.getRandomValues(bytes);
14617
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
14618
+ }
14619
+ function createAnonApiKey() {
14620
+ return AnonApiKeySchema.parse(`gt_anon_${randomHex(24)}`);
14621
+ }
14611
14622
  function createBuildHash(hash2) {
14612
14623
  return BuildHashSchema.parse(hash2);
14613
14624
  }
@@ -14761,6 +14772,44 @@ async function readAnonKey(projectRoot) {
14761
14772
  }
14762
14773
  return null;
14763
14774
  }
14775
+ async function getOrCreateAnonKey(projectRoot) {
14776
+ const root = projectRoot ?? process.cwd();
14777
+ const dirPath = (0, import_node_path.join)(root, GLASSTRACE_DIR);
14778
+ const keyPath = (0, import_node_path.join)(dirPath, ANON_KEY_FILE);
14779
+ const existingKey = await readAnonKey(root);
14780
+ if (existingKey !== null) {
14781
+ return existingKey;
14782
+ }
14783
+ const cached2 = ephemeralKeyCache.get(root);
14784
+ if (cached2 !== void 0) {
14785
+ return cached2;
14786
+ }
14787
+ const newKey = createAnonApiKey();
14788
+ try {
14789
+ await (0, import_promises.mkdir)(dirPath, { recursive: true, mode: 448 });
14790
+ await (0, import_promises.writeFile)(keyPath, newKey, { flag: "wx", mode: 384 });
14791
+ return newKey;
14792
+ } catch (err) {
14793
+ const code = err.code;
14794
+ if (code === "EEXIST") {
14795
+ const winnerKey = await readAnonKey(root);
14796
+ if (winnerKey !== null) {
14797
+ return winnerKey;
14798
+ }
14799
+ try {
14800
+ await (0, import_promises.writeFile)(keyPath, newKey, { mode: 384 });
14801
+ await (0, import_promises.chmod)(keyPath, 384);
14802
+ return newKey;
14803
+ } catch {
14804
+ }
14805
+ }
14806
+ ephemeralKeyCache.set(root, newKey);
14807
+ console.warn(
14808
+ `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`
14809
+ );
14810
+ return newKey;
14811
+ }
14812
+ }
14764
14813
  var import_promises, import_node_path, GLASSTRACE_DIR, ANON_KEY_FILE, ephemeralKeyCache;
14765
14814
  var init_anon_key = __esm({
14766
14815
  "src/anon-key.ts"() {
@@ -15052,8 +15101,11 @@ function generateInfoSection(agent, endpoint) {
15052
15101
  `Glasstrace is configured as an MCP server at: ${endpoint}`,
15053
15102
  "",
15054
15103
  "Available tools:",
15055
- "- `glasstrace_submit_trace` - Submit trace data for debugging analysis",
15056
- "- `glasstrace_get_config` - Retrieve current SDK configuration",
15104
+ "- `get_latest_error` - Get the most recent error trace from the current session",
15105
+ "- `get_trace` - Get a specific trace by ID or URL pattern",
15106
+ "- `get_root_cause` - Get the full span tree and root cause analysis for an error",
15107
+ "- `get_test_suggestions` - Get test suggestions based on recent errors",
15108
+ "- `get_session_timeline` - Get the timeline of all traces in the current session",
15057
15109
  "",
15058
15110
  "To reconfigure, run: `npx glasstrace mcp add`",
15059
15111
  ""
@@ -15272,11 +15324,7 @@ var init_inject = __esm({
15272
15324
  }
15273
15325
  });
15274
15326
 
15275
- // src/cli/mcp-add.ts
15276
- var mcp_add_exports = {};
15277
- __export(mcp_add_exports, {
15278
- mcpAdd: () => mcpAdd
15279
- });
15327
+ // src/cli/constants.ts
15280
15328
  function formatAgentName(name) {
15281
15329
  const displayNames = {
15282
15330
  claude: "Claude Code",
@@ -15288,6 +15336,19 @@ function formatAgentName(name) {
15288
15336
  };
15289
15337
  return displayNames[name];
15290
15338
  }
15339
+ var MCP_ENDPOINT;
15340
+ var init_constants = __esm({
15341
+ "src/cli/constants.ts"() {
15342
+ "use strict";
15343
+ MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
15344
+ }
15345
+ });
15346
+
15347
+ // src/cli/mcp-add.ts
15348
+ var mcp_add_exports = {};
15349
+ __export(mcp_add_exports, {
15350
+ mcpAdd: () => mcpAdd
15351
+ });
15291
15352
  async function registerViaCli(agent, anonKey) {
15292
15353
  if (!agent.cliAvailable) {
15293
15354
  return false;
@@ -15495,7 +15556,7 @@ async function mcpAdd(options) {
15495
15556
  }
15496
15557
  return { exitCode: 0, results, messages };
15497
15558
  }
15498
- var import_node_child_process2, fs3, path3, import_node_util, execFileAsync, MCP_ENDPOINT;
15559
+ var import_node_child_process2, fs3, path3, import_node_util, execFileAsync;
15499
15560
  var init_mcp_add = __esm({
15500
15561
  "src/cli/mcp-add.ts"() {
15501
15562
  "use strict";
@@ -15508,14 +15569,15 @@ var init_mcp_add = __esm({
15508
15569
  init_configs();
15509
15570
  init_inject();
15510
15571
  init_scaffolder();
15572
+ init_constants();
15511
15573
  execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
15512
- MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
15513
15574
  }
15514
15575
  });
15515
15576
 
15516
15577
  // src/cli/init.ts
15517
15578
  var init_exports = {};
15518
15579
  __export(init_exports, {
15580
+ meetsNodeVersion: () => meetsNodeVersion,
15519
15581
  runInit: () => runInit
15520
15582
  });
15521
15583
  module.exports = __toCommonJS(init_exports);
@@ -15698,17 +15760,10 @@ init_anon_key();
15698
15760
  init_detect();
15699
15761
  init_configs();
15700
15762
  init_inject();
15701
- var MCP_ENDPOINT2 = "https://api.glasstrace.dev/mcp";
15702
- function formatAgentName2(name) {
15703
- const displayNames = {
15704
- claude: "Claude Code",
15705
- codex: "Codex",
15706
- gemini: "Gemini",
15707
- cursor: "Cursor",
15708
- windsurf: "Windsurf",
15709
- generic: "Generic"
15710
- };
15711
- return displayNames[name];
15763
+ init_constants();
15764
+ function meetsNodeVersion(minMajor) {
15765
+ const [major] = process.versions.node.split(".").map(Number);
15766
+ return major >= minMajor;
15712
15767
  }
15713
15768
  async function promptYesNo(question, defaultValue) {
15714
15769
  if (!process.stdin.isTTY) {
@@ -15764,18 +15819,17 @@ async function runInit(options) {
15764
15819
  return { exitCode: 1, summary, warnings, errors };
15765
15820
  }
15766
15821
  try {
15767
- const wrapped = await scaffoldNextConfig(projectRoot);
15768
- if (wrapped) {
15822
+ const configResult = await scaffoldNextConfig(projectRoot);
15823
+ if (configResult?.modified) {
15769
15824
  summary.push("Wrapped next.config with withGlasstraceConfig()");
15825
+ } else if (configResult === null) {
15826
+ warnings.push("No next.config.* found. You may need to create one for Next.js projects.");
15827
+ } else if (configResult.reason === "already-wrapped") {
15828
+ summary.push("Skipped next.config (already contains withGlasstraceConfig)");
15829
+ } else if (configResult.reason === "empty-file") {
15830
+ warnings.push("next.config is empty \u2014 add a Next.js configuration export to enable wrapping");
15770
15831
  } else {
15771
- const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
15772
- (name) => fs4.existsSync(path4.join(projectRoot, name))
15773
- );
15774
- if (hasNextConfig) {
15775
- summary.push("Skipped next.config (already contains withGlasstraceConfig)");
15776
- } else {
15777
- warnings.push("No next.config.* found. You may need to create one for Next.js projects.");
15778
- }
15832
+ warnings.push("next.config has no recognizable export pattern \u2014 add withGlasstraceConfig() manually");
15779
15833
  }
15780
15834
  } catch (err) {
15781
15835
  errors.push(`Failed to modify next.config: ${err instanceof Error ? err.message : String(err)}`);
@@ -15806,10 +15860,30 @@ async function runInit(options) {
15806
15860
  const ciEnv = process.env["CI"];
15807
15861
  const isCI = typeof ciEnv === "string" && ciEnv.trim() !== "" && ciEnv.toLowerCase() !== "false" && ciEnv.trim() !== "0" || process.env["GITHUB_ACTIONS"] === "true";
15808
15862
  try {
15809
- const anonKey = await readAnonKey(projectRoot);
15810
- if (anonKey !== null) {
15811
- let anyConfigWritten = false;
15812
- if (isCI) {
15863
+ const anonKey = await getOrCreateAnonKey(projectRoot);
15864
+ let anyConfigWritten = false;
15865
+ if (isCI) {
15866
+ const genericAgent = {
15867
+ name: "generic",
15868
+ mcpConfigPath: path4.join(projectRoot, ".glasstrace", "mcp.json"),
15869
+ infoFilePath: null,
15870
+ cliAvailable: false,
15871
+ registrationCommand: null
15872
+ };
15873
+ const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
15874
+ await writeMcpConfig(genericAgent, genericConfig, projectRoot);
15875
+ if (genericAgent.mcpConfigPath !== null && fs4.existsSync(genericAgent.mcpConfigPath)) {
15876
+ anyConfigWritten = true;
15877
+ summary.push("Created .glasstrace/mcp.json (CI mode)");
15878
+ }
15879
+ } else {
15880
+ let agents;
15881
+ try {
15882
+ agents = await detectAgents(projectRoot);
15883
+ } catch (detectErr) {
15884
+ warnings.push(
15885
+ `Agent detection failed: ${detectErr instanceof Error ? detectErr.message : String(detectErr)}. Writing generic config only.`
15886
+ );
15813
15887
  const genericAgent = {
15814
15888
  name: "generic",
15815
15889
  mcpConfigPath: path4.join(projectRoot, ".glasstrace", "mcp.json"),
@@ -15817,75 +15891,51 @@ async function runInit(options) {
15817
15891
  cliAvailable: false,
15818
15892
  registrationCommand: null
15819
15893
  };
15820
- const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT2, anonKey);
15894
+ const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
15821
15895
  await writeMcpConfig(genericAgent, genericConfig, projectRoot);
15822
15896
  if (genericAgent.mcpConfigPath !== null && fs4.existsSync(genericAgent.mcpConfigPath)) {
15823
15897
  anyConfigWritten = true;
15824
- summary.push("Created .glasstrace/mcp.json (CI mode)");
15825
15898
  }
15826
- } else {
15827
- let agents;
15899
+ agents = [];
15900
+ }
15901
+ const configuredNames = [];
15902
+ for (const agent of agents) {
15828
15903
  try {
15829
- agents = await detectAgents(projectRoot);
15830
- } catch (detectErr) {
15904
+ const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
15905
+ await writeMcpConfig(agent, configContent, projectRoot);
15906
+ const configExists = agent.mcpConfigPath !== null && fs4.existsSync(agent.mcpConfigPath);
15907
+ if (!configExists) {
15908
+ continue;
15909
+ }
15910
+ anyConfigWritten = true;
15911
+ const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
15912
+ if (infoContent !== "") {
15913
+ await injectInfoSection(agent, infoContent, projectRoot);
15914
+ }
15915
+ if (agent.name !== "generic") {
15916
+ configuredNames.push(formatAgentName(agent.name));
15917
+ }
15918
+ } catch (agentErr) {
15831
15919
  warnings.push(
15832
- `Agent detection failed: ${detectErr instanceof Error ? detectErr.message : String(detectErr)}. Writing generic config only.`
15920
+ `Failed to configure MCP for ${agent.name}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`
15833
15921
  );
15834
- const genericAgent = {
15835
- name: "generic",
15836
- mcpConfigPath: path4.join(projectRoot, ".glasstrace", "mcp.json"),
15837
- infoFilePath: null,
15838
- cliAvailable: false,
15839
- registrationCommand: null
15840
- };
15841
- const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT2, anonKey);
15842
- await writeMcpConfig(genericAgent, genericConfig, projectRoot);
15843
- if (genericAgent.mcpConfigPath !== null && fs4.existsSync(genericAgent.mcpConfigPath)) {
15844
- anyConfigWritten = true;
15845
- }
15846
- agents = [];
15847
- }
15848
- const configuredNames = [];
15849
- for (const agent of agents) {
15850
- try {
15851
- const configContent = generateMcpConfig(agent, MCP_ENDPOINT2, anonKey);
15852
- await writeMcpConfig(agent, configContent, projectRoot);
15853
- const configExists = agent.mcpConfigPath !== null && fs4.existsSync(agent.mcpConfigPath);
15854
- if (!configExists) {
15855
- continue;
15856
- }
15857
- anyConfigWritten = true;
15858
- const infoContent = generateInfoSection(agent, MCP_ENDPOINT2);
15859
- if (infoContent !== "") {
15860
- await injectInfoSection(agent, infoContent, projectRoot);
15861
- }
15862
- if (agent.name !== "generic") {
15863
- configuredNames.push(formatAgentName2(agent.name));
15864
- }
15865
- } catch (agentErr) {
15866
- warnings.push(
15867
- `Failed to configure MCP for ${agent.name}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`
15868
- );
15869
- }
15870
- }
15871
- if (configuredNames.length > 0) {
15872
- summary.push(`Configured MCP for: ${configuredNames.join(", ")}`);
15873
- } else if (anyConfigWritten) {
15874
- summary.push("Created .glasstrace/mcp.json (generic config)");
15875
15922
  }
15876
15923
  }
15877
- await updateGitignore(
15878
- [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
15879
- projectRoot
15880
- );
15881
- if (anyConfigWritten) {
15882
- const markerCreated = await scaffoldMcpMarker(projectRoot, anonKey);
15883
- if (markerCreated) {
15884
- summary.push("Created .glasstrace/mcp-connected marker");
15885
- }
15924
+ if (configuredNames.length > 0) {
15925
+ summary.push(`Configured MCP for: ${configuredNames.join(", ")}`);
15926
+ } else if (anyConfigWritten) {
15927
+ summary.push("Created .glasstrace/mcp.json (generic config)");
15928
+ }
15929
+ }
15930
+ await updateGitignore(
15931
+ [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
15932
+ projectRoot
15933
+ );
15934
+ if (anyConfigWritten) {
15935
+ const markerCreated = await scaffoldMcpMarker(projectRoot, anonKey);
15936
+ if (markerCreated) {
15937
+ summary.push("Created .glasstrace/mcp-connected marker");
15886
15938
  }
15887
- } else {
15888
- warnings.push("No anonymous key found. Skipping MCP auto-configuration. Run init again after the SDK has generated a key.");
15889
15939
  }
15890
15940
  } catch (mcpErr) {
15891
15941
  warnings.push(
@@ -15943,6 +15993,13 @@ var scriptPath = typeof process !== "undefined" && process.argv[1] !== void 0 ?
15943
15993
  var scriptBasename = scriptPath !== void 0 ? path4.basename(scriptPath) : void 0;
15944
15994
  var isDirectExecution = scriptPath !== void 0 && (scriptPath.endsWith("/cli/init.js") || scriptPath.endsWith("/cli/init.ts") || scriptBasename === "glasstrace");
15945
15995
  if (isDirectExecution) {
15996
+ if (!meetsNodeVersion(20)) {
15997
+ process.stderr.write(
15998
+ `Error: @glasstrace/sdk requires Node.js >= 20. Current version: ${process.version}
15999
+ `
16000
+ );
16001
+ process.exit(1);
16002
+ }
15946
16003
  const subcommand = process.argv[2];
15947
16004
  if (subcommand === "mcp") {
15948
16005
  if (process.argv[3] === "add") {
@@ -16022,6 +16079,7 @@ Usage:
16022
16079
  }
16023
16080
  // Annotate the CommonJS export names for ESM import in node:
16024
16081
  0 && (module.exports = {
16082
+ meetsNodeVersion,
16025
16083
  runInit
16026
16084
  });
16027
16085
  //# sourceMappingURL=init.cjs.map