@glasstrace/sdk 0.2.1 → 0.4.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.
- package/dist/adapters/drizzle.cjs +729 -3
- package/dist/adapters/drizzle.cjs.map +1 -1
- package/dist/adapters/drizzle.js +4 -1
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/chunk-DQ25VOKK.js +1250 -0
- package/dist/chunk-DQ25VOKK.js.map +1 -0
- package/dist/{chunk-CUFIV225.js → chunk-EC5IINUT.js} +86 -201
- package/dist/chunk-EC5IINUT.js.map +1 -0
- package/dist/chunk-STECO33B.js +675 -0
- package/dist/chunk-STECO33B.js.map +1 -0
- package/dist/chunk-TJ6ETQPH.js +172 -0
- package/dist/chunk-TJ6ETQPH.js.map +1 -0
- package/dist/chunk-WZXVS2EO.js +9 -0
- package/dist/chunk-WZXVS2EO.js.map +1 -0
- package/dist/cli/init.cjs +12481 -10892
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +195 -186
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs +14651 -0
- package/dist/cli/mcp-add.cjs.map +1 -0
- package/dist/cli/mcp-add.d.cts +46 -0
- package/dist/cli/mcp-add.d.ts +46 -0
- package/dist/cli/mcp-add.js +243 -0
- package/dist/cli/mcp-add.js.map +1 -0
- package/dist/esm-POMEQPKL.js +62 -0
- package/dist/esm-POMEQPKL.js.map +1 -0
- package/dist/getMachineId-bsd-TC3JSTY5.js +29 -0
- package/dist/getMachineId-bsd-TC3JSTY5.js.map +1 -0
- package/dist/getMachineId-darwin-2SUKQCE6.js +29 -0
- package/dist/getMachineId-darwin-2SUKQCE6.js.map +1 -0
- package/dist/getMachineId-linux-PNAFHLXH.js +23 -0
- package/dist/getMachineId-linux-PNAFHLXH.js.map +1 -0
- package/dist/getMachineId-unsupported-L2MNYW3W.js +14 -0
- package/dist/getMachineId-unsupported-L2MNYW3W.js.map +1 -0
- package/dist/getMachineId-win-D6D42WOQ.js +31 -0
- package/dist/getMachineId-win-D6D42WOQ.js.map +1 -0
- package/dist/index.cjs +7580 -3456
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +2774 -299
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
- package/dist/chunk-CUFIV225.js.map +0 -1
package/dist/cli/init.js
CHANGED
|
@@ -1,167 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildImportGraph
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-TJ6ETQPH.js";
|
|
5
|
+
import {
|
|
6
|
+
addCoverageMapEnv,
|
|
7
|
+
detectAgents,
|
|
8
|
+
generateInfoSection,
|
|
9
|
+
generateMcpConfig,
|
|
10
|
+
injectInfoSection,
|
|
11
|
+
scaffoldEnvLocal,
|
|
12
|
+
scaffoldGitignore,
|
|
13
|
+
scaffoldInstrumentation,
|
|
14
|
+
scaffoldMcpMarker,
|
|
15
|
+
scaffoldNextConfig,
|
|
16
|
+
updateGitignore,
|
|
17
|
+
writeMcpConfig
|
|
18
|
+
} from "../chunk-STECO33B.js";
|
|
19
|
+
import {
|
|
20
|
+
readAnonKey
|
|
21
|
+
} from "../chunk-EC5IINUT.js";
|
|
5
22
|
import "../chunk-PZ5AY32C.js";
|
|
6
23
|
|
|
7
24
|
// src/cli/init.ts
|
|
8
|
-
import * as fs2 from "fs";
|
|
9
|
-
import * as path2 from "path";
|
|
10
|
-
import * as readline from "readline";
|
|
11
|
-
|
|
12
|
-
// src/cli/scaffolder.ts
|
|
13
25
|
import * as fs from "fs";
|
|
14
26
|
import * as path from "path";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// to ensure all ORM spans are captured correctly.
|
|
26
|
-
// If you use @prisma/instrumentation, import it after this call.
|
|
27
|
-
registerGlasstrace();
|
|
28
|
-
}
|
|
29
|
-
`;
|
|
30
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
async function scaffoldNextConfig(projectRoot) {
|
|
34
|
-
let configPath;
|
|
35
|
-
let configName;
|
|
36
|
-
for (const name of NEXT_CONFIG_NAMES) {
|
|
37
|
-
const candidate = path.join(projectRoot, name);
|
|
38
|
-
if (fs.existsSync(candidate)) {
|
|
39
|
-
configPath = candidate;
|
|
40
|
-
configName = name;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (configPath === void 0 || configName === void 0) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
const existing = fs.readFileSync(configPath, "utf-8");
|
|
48
|
-
if (existing.includes("withGlasstraceConfig")) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
|
|
52
|
-
if (isESM) {
|
|
53
|
-
const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
|
|
54
|
-
const wrapResult2 = wrapExport(existing);
|
|
55
|
-
if (!wrapResult2.wrapped) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
const modified2 = importLine + "\n" + wrapResult2.content;
|
|
59
|
-
fs.writeFileSync(configPath, modified2, "utf-8");
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
|
|
63
|
-
const wrapResult = wrapCJSExport(existing);
|
|
64
|
-
if (!wrapResult.wrapped) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
const modified = requireLine + "\n" + wrapResult.content;
|
|
68
|
-
fs.writeFileSync(configPath, modified, "utf-8");
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
function wrapExport(content) {
|
|
72
|
-
const marker = "export default";
|
|
73
|
-
const idx = content.lastIndexOf(marker);
|
|
74
|
-
if (idx === -1) {
|
|
75
|
-
return { content, wrapped: false };
|
|
76
|
-
}
|
|
77
|
-
const preamble = content.slice(0, idx);
|
|
78
|
-
const exprRaw = content.slice(idx + marker.length);
|
|
79
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
80
|
-
if (expr.length === 0) {
|
|
81
|
-
return { content, wrapped: false };
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
content: preamble + `export default withGlasstraceConfig(${expr});
|
|
85
|
-
`,
|
|
86
|
-
wrapped: true
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function wrapCJSExport(content) {
|
|
90
|
-
const cjsMarker = "module.exports";
|
|
91
|
-
const cjsIdx = content.lastIndexOf(cjsMarker);
|
|
92
|
-
if (cjsIdx === -1) {
|
|
93
|
-
return { content, wrapped: false };
|
|
94
|
-
}
|
|
95
|
-
const preamble = content.slice(0, cjsIdx);
|
|
96
|
-
const afterMarker = content.slice(cjsIdx + cjsMarker.length);
|
|
97
|
-
const eqMatch = /^\s*=\s*/.exec(afterMarker);
|
|
98
|
-
if (!eqMatch) {
|
|
99
|
-
return { content, wrapped: false };
|
|
100
|
-
}
|
|
101
|
-
const exprRaw = afterMarker.slice(eqMatch[0].length);
|
|
102
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
103
|
-
if (expr.length === 0) {
|
|
104
|
-
return { content, wrapped: false };
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
content: preamble + `module.exports = withGlasstraceConfig(${expr});
|
|
108
|
-
`,
|
|
109
|
-
wrapped: true
|
|
27
|
+
import * as readline from "readline";
|
|
28
|
+
var MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
|
|
29
|
+
function formatAgentName(name) {
|
|
30
|
+
const displayNames = {
|
|
31
|
+
claude: "Claude Code",
|
|
32
|
+
codex: "Codex",
|
|
33
|
+
gemini: "Gemini",
|
|
34
|
+
cursor: "Cursor",
|
|
35
|
+
windsurf: "Windsurf",
|
|
36
|
+
generic: "Generic"
|
|
110
37
|
};
|
|
38
|
+
return displayNames[name];
|
|
111
39
|
}
|
|
112
|
-
async function scaffoldEnvLocal(projectRoot) {
|
|
113
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
114
|
-
if (fs.existsSync(filePath)) {
|
|
115
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
116
|
-
if (/^\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
120
|
-
fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_API_KEY=\n", "utf-8");
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
fs.writeFileSync(filePath, "GLASSTRACE_API_KEY=\n", "utf-8");
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
async function addCoverageMapEnv(projectRoot) {
|
|
127
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
128
|
-
if (!fs.existsSync(filePath)) {
|
|
129
|
-
fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
133
|
-
const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
|
|
134
|
-
const keyMatch = keyRegex.exec(existing);
|
|
135
|
-
if (keyMatch) {
|
|
136
|
-
const currentValue = keyMatch[2].trim();
|
|
137
|
-
if (currentValue === "true") {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
|
|
141
|
-
fs.writeFileSync(filePath, updated, "utf-8");
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
145
|
-
fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
async function scaffoldGitignore(projectRoot) {
|
|
149
|
-
const filePath = path.join(projectRoot, ".gitignore");
|
|
150
|
-
if (fs.existsSync(filePath)) {
|
|
151
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
152
|
-
const lines = existing.split("\n").map((l) => l.trim());
|
|
153
|
-
if (lines.includes(".glasstrace/")) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
157
|
-
fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/cli/init.ts
|
|
165
40
|
async function promptYesNo(question, defaultValue) {
|
|
166
41
|
if (!process.stdin.isTTY) {
|
|
167
42
|
return defaultValue;
|
|
@@ -188,13 +63,13 @@ async function runInit(options) {
|
|
|
188
63
|
const summary = [];
|
|
189
64
|
const warnings = [];
|
|
190
65
|
const errors = [];
|
|
191
|
-
const packageJsonPath =
|
|
192
|
-
if (!
|
|
66
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
67
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
193
68
|
errors.push("No package.json found. Run this command from a Node.js project root.");
|
|
194
69
|
return { exitCode: 1, summary, warnings, errors };
|
|
195
70
|
}
|
|
196
|
-
const instrumentationPath =
|
|
197
|
-
const instrumentationExists =
|
|
71
|
+
const instrumentationPath = path.join(projectRoot, "instrumentation.ts");
|
|
72
|
+
const instrumentationExists = fs.existsSync(instrumentationPath);
|
|
198
73
|
let shouldWriteInstrumentation = true;
|
|
199
74
|
if (instrumentationExists && !yes) {
|
|
200
75
|
shouldWriteInstrumentation = await promptYesNo(
|
|
@@ -221,7 +96,7 @@ async function runInit(options) {
|
|
|
221
96
|
summary.push("Wrapped next.config with withGlasstraceConfig()");
|
|
222
97
|
} else {
|
|
223
98
|
const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
|
|
224
|
-
(name) =>
|
|
99
|
+
(name) => fs.existsSync(path.join(projectRoot, name))
|
|
225
100
|
);
|
|
226
101
|
if (hasNextConfig) {
|
|
227
102
|
summary.push("Skipped next.config (already contains withGlasstraceConfig)");
|
|
@@ -236,9 +111,9 @@ async function runInit(options) {
|
|
|
236
111
|
try {
|
|
237
112
|
const envCreated = await scaffoldEnvLocal(projectRoot);
|
|
238
113
|
if (envCreated) {
|
|
239
|
-
summary.push("Updated .env.local with
|
|
114
|
+
summary.push("Updated .env.local with Glasstrace configuration");
|
|
240
115
|
} else {
|
|
241
|
-
summary.push("Skipped .env.local (GLASSTRACE_API_KEY already
|
|
116
|
+
summary.push("Skipped .env.local (GLASSTRACE_API_KEY already configured)");
|
|
242
117
|
}
|
|
243
118
|
} catch (err) {
|
|
244
119
|
errors.push(`Failed to update .env.local: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -255,6 +130,95 @@ async function runInit(options) {
|
|
|
255
130
|
errors.push(`Failed to update .gitignore: ${err instanceof Error ? err.message : String(err)}`);
|
|
256
131
|
return { exitCode: 1, summary, warnings, errors };
|
|
257
132
|
}
|
|
133
|
+
const ciEnv = process.env["CI"];
|
|
134
|
+
const isCI = typeof ciEnv === "string" && ciEnv.trim() !== "" && ciEnv.toLowerCase() !== "false" && ciEnv.trim() !== "0" || process.env["GITHUB_ACTIONS"] === "true";
|
|
135
|
+
try {
|
|
136
|
+
const anonKey = await readAnonKey(projectRoot);
|
|
137
|
+
if (anonKey !== null) {
|
|
138
|
+
let anyConfigWritten = false;
|
|
139
|
+
if (isCI) {
|
|
140
|
+
const genericAgent = {
|
|
141
|
+
name: "generic",
|
|
142
|
+
mcpConfigPath: path.join(projectRoot, ".glasstrace", "mcp.json"),
|
|
143
|
+
infoFilePath: null,
|
|
144
|
+
cliAvailable: false,
|
|
145
|
+
registrationCommand: null
|
|
146
|
+
};
|
|
147
|
+
const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
|
|
148
|
+
await writeMcpConfig(genericAgent, genericConfig, projectRoot);
|
|
149
|
+
if (genericAgent.mcpConfigPath !== null && fs.existsSync(genericAgent.mcpConfigPath)) {
|
|
150
|
+
anyConfigWritten = true;
|
|
151
|
+
summary.push("Created .glasstrace/mcp.json (CI mode)");
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
let agents;
|
|
155
|
+
try {
|
|
156
|
+
agents = await detectAgents(projectRoot);
|
|
157
|
+
} catch (detectErr) {
|
|
158
|
+
warnings.push(
|
|
159
|
+
`Agent detection failed: ${detectErr instanceof Error ? detectErr.message : String(detectErr)}. Writing generic config only.`
|
|
160
|
+
);
|
|
161
|
+
const genericAgent = {
|
|
162
|
+
name: "generic",
|
|
163
|
+
mcpConfigPath: path.join(projectRoot, ".glasstrace", "mcp.json"),
|
|
164
|
+
infoFilePath: null,
|
|
165
|
+
cliAvailable: false,
|
|
166
|
+
registrationCommand: null
|
|
167
|
+
};
|
|
168
|
+
const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
|
|
169
|
+
await writeMcpConfig(genericAgent, genericConfig, projectRoot);
|
|
170
|
+
if (genericAgent.mcpConfigPath !== null && fs.existsSync(genericAgent.mcpConfigPath)) {
|
|
171
|
+
anyConfigWritten = true;
|
|
172
|
+
}
|
|
173
|
+
agents = [];
|
|
174
|
+
}
|
|
175
|
+
const configuredNames = [];
|
|
176
|
+
for (const agent of agents) {
|
|
177
|
+
try {
|
|
178
|
+
const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
|
|
179
|
+
await writeMcpConfig(agent, configContent, projectRoot);
|
|
180
|
+
const configExists = agent.mcpConfigPath !== null && fs.existsSync(agent.mcpConfigPath);
|
|
181
|
+
if (!configExists) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
anyConfigWritten = true;
|
|
185
|
+
const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
|
|
186
|
+
if (infoContent !== "") {
|
|
187
|
+
await injectInfoSection(agent, infoContent, projectRoot);
|
|
188
|
+
}
|
|
189
|
+
if (agent.name !== "generic") {
|
|
190
|
+
configuredNames.push(formatAgentName(agent.name));
|
|
191
|
+
}
|
|
192
|
+
} catch (agentErr) {
|
|
193
|
+
warnings.push(
|
|
194
|
+
`Failed to configure MCP for ${agent.name}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (configuredNames.length > 0) {
|
|
199
|
+
summary.push(`Configured MCP for: ${configuredNames.join(", ")}`);
|
|
200
|
+
} else if (anyConfigWritten) {
|
|
201
|
+
summary.push("Created .glasstrace/mcp.json (generic config)");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
await updateGitignore(
|
|
205
|
+
[".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
|
|
206
|
+
projectRoot
|
|
207
|
+
);
|
|
208
|
+
if (anyConfigWritten) {
|
|
209
|
+
const markerCreated = await scaffoldMcpMarker(projectRoot, anonKey);
|
|
210
|
+
if (markerCreated) {
|
|
211
|
+
summary.push("Created .glasstrace/mcp-connected marker");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
warnings.push("No anonymous key found. Skipping MCP auto-configuration. Run init again after the SDK has generated a key.");
|
|
216
|
+
}
|
|
217
|
+
} catch (mcpErr) {
|
|
218
|
+
warnings.push(
|
|
219
|
+
`MCP auto-configuration failed: ${mcpErr instanceof Error ? mcpErr.message : String(mcpErr)}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
258
222
|
let enableCoverageMap = coverageMap;
|
|
259
223
|
if (!yes && !coverageMap) {
|
|
260
224
|
if (process.stdin.isTTY) {
|
|
@@ -303,40 +267,85 @@ function parseArgs(argv) {
|
|
|
303
267
|
};
|
|
304
268
|
}
|
|
305
269
|
var scriptPath = typeof process !== "undefined" && process.argv[1] !== void 0 ? process.argv[1].replace(/\\/g, "/") : void 0;
|
|
306
|
-
var scriptBasename = scriptPath !== void 0 ?
|
|
270
|
+
var scriptBasename = scriptPath !== void 0 ? path.basename(scriptPath) : void 0;
|
|
307
271
|
var isDirectExecution = scriptPath !== void 0 && (scriptPath.endsWith("/cli/init.js") || scriptPath.endsWith("/cli/init.ts") || scriptBasename === "glasstrace");
|
|
308
272
|
if (isDirectExecution) {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
273
|
+
const subcommand = process.argv[2];
|
|
274
|
+
if (subcommand === "mcp") {
|
|
275
|
+
if (process.argv[3] === "add") {
|
|
276
|
+
const remainingArgs = process.argv.slice(4);
|
|
277
|
+
const force = remainingArgs.includes("--force");
|
|
278
|
+
const dryRun = remainingArgs.includes("--dry-run");
|
|
279
|
+
import("./mcp-add.js").then(({ mcpAdd }) => mcpAdd({ force, dryRun })).then((result) => {
|
|
280
|
+
for (const msg of result.messages) {
|
|
281
|
+
process.stderr.write(msg + "\n");
|
|
282
|
+
}
|
|
283
|
+
process.exit(result.exitCode);
|
|
284
|
+
}).catch((err) => {
|
|
285
|
+
process.stderr.write(
|
|
286
|
+
`Fatal error: ${err instanceof Error ? err.message : String(err)}
|
|
287
|
+
`
|
|
288
|
+
);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
});
|
|
291
|
+
} else {
|
|
292
|
+
process.stderr.write(
|
|
293
|
+
`Unknown mcp subcommand: ${process.argv[3] ?? "(none)"}
|
|
294
|
+
|
|
295
|
+
Usage: glasstrace mcp add [--force] [--dry-run]
|
|
296
|
+
`
|
|
297
|
+
);
|
|
298
|
+
process.exit(1);
|
|
316
299
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
300
|
+
} else if (subcommand === void 0 || subcommand === "init" || subcommand.startsWith("-")) {
|
|
301
|
+
const options = parseArgs(process.argv);
|
|
302
|
+
runInit(options).then((result) => {
|
|
303
|
+
if (result.errors.length > 0) {
|
|
304
|
+
for (const err of result.errors) {
|
|
305
|
+
process.stderr.write(`Error: ${err}
|
|
320
306
|
`);
|
|
307
|
+
}
|
|
321
308
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
for (const line of result.summary) {
|
|
326
|
-
process.stderr.write(` - ${line}
|
|
309
|
+
if (result.warnings.length > 0) {
|
|
310
|
+
for (const warn of result.warnings) {
|
|
311
|
+
process.stderr.write(`Warning: ${warn}
|
|
327
312
|
`);
|
|
313
|
+
}
|
|
328
314
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
process.exit(result.exitCode);
|
|
335
|
-
}).catch((err) => {
|
|
336
|
-
process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}
|
|
315
|
+
if (result.summary.length > 0) {
|
|
316
|
+
process.stderr.write("\nGlasstrace initialized successfully!\n\n");
|
|
317
|
+
for (const line of result.summary) {
|
|
318
|
+
process.stderr.write(` - ${line}
|
|
337
319
|
`);
|
|
320
|
+
}
|
|
321
|
+
process.stderr.write("\nNext steps:\n");
|
|
322
|
+
process.stderr.write(" 1. Start your Next.js dev server\n");
|
|
323
|
+
process.stderr.write(
|
|
324
|
+
" 2. Glasstrace works immediately in anonymous mode\n"
|
|
325
|
+
);
|
|
326
|
+
process.stderr.write(
|
|
327
|
+
" 3. To link to your account, set GLASSTRACE_API_KEY in .env.local\n\n"
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
process.exit(result.exitCode);
|
|
331
|
+
}).catch((err) => {
|
|
332
|
+
process.stderr.write(
|
|
333
|
+
`Fatal error: ${err instanceof Error ? err.message : String(err)}
|
|
334
|
+
`
|
|
335
|
+
);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
process.stderr.write(
|
|
340
|
+
`Unknown command: ${subcommand}
|
|
341
|
+
|
|
342
|
+
Usage:
|
|
343
|
+
glasstrace init [--yes] [--coverage-map]
|
|
344
|
+
glasstrace mcp add [--force] [--dry-run]
|
|
345
|
+
`
|
|
346
|
+
);
|
|
338
347
|
process.exit(1);
|
|
339
|
-
}
|
|
348
|
+
}
|
|
340
349
|
}
|
|
341
350
|
export {
|
|
342
351
|
runInit
|
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/init.ts","../../src/cli/scaffolder.ts"],"sourcesContent":["#!/usr/bin/env node\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as readline from \"node:readline\";\nimport {\n scaffoldInstrumentation,\n scaffoldNextConfig,\n scaffoldEnvLocal,\n scaffoldGitignore,\n addCoverageMapEnv,\n} from \"./scaffolder.js\";\nimport { buildImportGraph } from \"../import-graph.js\";\n\n/** Options for the init command (parsed from CLI args or passed programmatically). */\nexport interface InitOptions {\n projectRoot: string;\n yes: boolean;\n coverageMap: boolean;\n}\n\n/** Result of running the init command. */\nexport interface InitResult {\n exitCode: number;\n summary: string[];\n warnings: string[];\n errors: string[];\n}\n\n/**\n * Prompts the user with a yes/no question. Returns true for yes.\n * In non-interactive mode (no TTY), returns the default value.\n */\nasync function promptYesNo(question: string, defaultValue: boolean): Promise<boolean> {\n if (!process.stdin.isTTY) {\n return defaultValue;\n }\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\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 * Core init logic. Exported for testability — the CLI entry point at the\n * bottom calls this function and translates the result to process.exit().\n */\nexport async function runInit(options: InitOptions): Promise<InitResult> {\n const { projectRoot, yes, coverageMap } = options;\n const summary: string[] = [];\n const warnings: string[] = [];\n const errors: string[] = [];\n\n // Step 1: Detect package.json\n const packageJsonPath = path.join(projectRoot, \"package.json\");\n if (!fs.existsSync(packageJsonPath)) {\n errors.push(\"No package.json found. Run this command from a Node.js project root.\");\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 2 + 3: Generate instrumentation.ts\n const instrumentationPath = path.join(projectRoot, \"instrumentation.ts\");\n const instrumentationExists = fs.existsSync(instrumentationPath);\n let shouldWriteInstrumentation = true;\n\n if (instrumentationExists && !yes) {\n shouldWriteInstrumentation = await promptYesNo(\n \"instrumentation.ts already exists. Overwrite?\",\n false,\n );\n } else if (instrumentationExists && yes) {\n // Non-interactive: never overwrite (idempotent)\n shouldWriteInstrumentation = false;\n }\n\n try {\n const created = await scaffoldInstrumentation(projectRoot, shouldWriteInstrumentation && instrumentationExists);\n if (created) {\n summary.push(\"Created instrumentation.ts\");\n } else if (instrumentationExists) {\n summary.push(\"Skipped instrumentation.ts (already exists)\");\n }\n } catch (err) {\n errors.push(`Failed to write instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 4: Detect and wrap next.config.*\n try {\n const wrapped = await scaffoldNextConfig(projectRoot);\n if (wrapped) {\n summary.push(\"Wrapped next.config with withGlasstraceConfig()\");\n } else {\n // Check if it was skipped because file already wrapped vs not found\n const hasNextConfig = [\"next.config.ts\", \"next.config.js\", \"next.config.mjs\"].some(\n (name) => fs.existsSync(path.join(projectRoot, name)),\n );\n if (hasNextConfig) {\n summary.push(\"Skipped next.config (already contains withGlasstraceConfig)\");\n } else {\n warnings.push(\"No next.config.* found. You may need to create one for Next.js projects.\");\n }\n }\n } catch (err) {\n errors.push(`Failed to modify next.config: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 5: Update .env.local\n try {\n const envCreated = await scaffoldEnvLocal(projectRoot);\n if (envCreated) {\n summary.push(\"Updated .env.local with GLASSTRACE_API_KEY placeholder\");\n } else {\n summary.push(\"Skipped .env.local (GLASSTRACE_API_KEY already present)\");\n }\n } catch (err) {\n errors.push(`Failed to update .env.local: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 6: Update .gitignore\n try {\n const gitignoreUpdated = await scaffoldGitignore(projectRoot);\n if (gitignoreUpdated) {\n summary.push(\"Updated .gitignore with .glasstrace/\");\n } else {\n summary.push(\"Skipped .gitignore (.glasstrace/ already listed)\");\n }\n } catch (err) {\n errors.push(`Failed to update .gitignore: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 7: Coverage map opt-in\n let enableCoverageMap = coverageMap;\n if (!yes && !coverageMap) {\n if (process.stdin.isTTY) {\n enableCoverageMap = await promptYesNo(\n \"Would you like to enable test coverage mapping?\",\n false,\n );\n }\n }\n\n if (enableCoverageMap) {\n try {\n const added = await addCoverageMapEnv(projectRoot);\n if (added) {\n summary.push(\"Added GLASSTRACE_COVERAGE_MAP=true to .env.local\");\n }\n } catch (err) {\n warnings.push(`Failed to add coverage map env: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // Step 8: Run initial import graph scan\n try {\n await buildImportGraph(projectRoot);\n summary.push(\"Completed initial import graph scan\");\n } catch (err) {\n warnings.push(`Import graph scan failed: ${err instanceof Error ? err.message : String(err)}. You can run it later.`);\n }\n }\n\n return { exitCode: 0, summary, warnings, errors };\n}\n\n/**\n * Parses CLI arguments into InitOptions.\n */\nfunction parseArgs(argv: string[]): InitOptions {\n const args = argv.slice(2); // skip node + script path\n let yes = false;\n let coverageMap = false;\n\n for (const arg of args) {\n if (arg === \"--yes\" || arg === \"-y\") {\n yes = true;\n } else if (arg === \"--coverage-map\") {\n coverageMap = true;\n }\n }\n\n // Auto-detect non-interactive\n if (!process.stdin.isTTY) {\n yes = true;\n }\n\n return {\n projectRoot: process.cwd(),\n yes,\n coverageMap,\n };\n}\n\n/**\n * CLI entry point. Only runs when this module is executed directly\n * (not when imported for testing).\n */\nconst scriptPath =\n typeof process !== \"undefined\" && process.argv[1] !== undefined\n ? process.argv[1].replace(/\\\\/g, \"/\")\n : undefined;\n\nconst scriptBasename = scriptPath !== undefined ? path.basename(scriptPath) : undefined;\n\nconst isDirectExecution =\n scriptPath !== undefined &&\n (scriptPath.endsWith(\"/cli/init.js\") ||\n scriptPath.endsWith(\"/cli/init.ts\") ||\n scriptBasename === \"glasstrace\");\n\nif (isDirectExecution) {\n const options = parseArgs(process.argv);\n\n runInit(options)\n .then((result) => {\n if (result.errors.length > 0) {\n for (const err of result.errors) {\n process.stderr.write(`Error: ${err}\\n`);\n }\n }\n if (result.warnings.length > 0) {\n for (const warn of result.warnings) {\n process.stderr.write(`Warning: ${warn}\\n`);\n }\n }\n if (result.summary.length > 0) {\n process.stderr.write(\"\\nGlasstrace initialized successfully!\\n\\n\");\n for (const line of result.summary) {\n process.stderr.write(` - ${line}\\n`);\n }\n process.stderr.write(\"\\nNext steps:\\n\");\n process.stderr.write(\" 1. Set your GLASSTRACE_API_KEY in .env.local\\n\");\n process.stderr.write(\" 2. Start your Next.js dev server\\n\");\n process.stderr.write(\" 3. Glasstrace will begin capturing traces automatically\\n\\n\");\n }\n process.exit(result.exitCode);\n })\n .catch((err: unknown) => {\n process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\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 True if the config file was modified (or created), false if skipped.\n */\nexport async function scaffoldNextConfig(\n projectRoot: string,\n): Promise<boolean> {\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 false;\n }\n\n const existing = fs.readFileSync(configPath, \"utf-8\");\n\n // Already wrapped — skip even in force mode to avoid double-wrapping\n if (existing.includes(\"withGlasstraceConfig\")) {\n return false;\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 false;\n }\n const modified = importLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return 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 false;\n }\n const modified = requireLine + \"\\n\" + wrapResult.content;\n fs.writeFileSync(configPath, modified, \"utf-8\");\n return true;\n}\n\ninterface 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 */\nfunction 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 */\nfunction 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*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=\\n\", \"utf-8\");\n return true;\n }\n\n fs.writeFileSync(filePath, \"GLASSTRACE_API_KEY=\\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"],"mappings":"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","names":["fs","path","wrapResult","modified"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/init.ts"],"sourcesContent":["#!/usr/bin/env node\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as readline from \"node:readline\";\nimport {\n scaffoldInstrumentation,\n scaffoldNextConfig,\n scaffoldEnvLocal,\n scaffoldGitignore,\n scaffoldMcpMarker,\n addCoverageMapEnv,\n} from \"./scaffolder.js\";\nimport { buildImportGraph } from \"../import-graph.js\";\nimport { readAnonKey } from \"../anon-key.js\";\nimport { detectAgents } from \"../agent-detection/detect.js\";\nimport { generateMcpConfig, generateInfoSection } from \"../agent-detection/configs.js\";\nimport { writeMcpConfig, injectInfoSection, updateGitignore } from \"../agent-detection/inject.js\";\nimport type { DetectedAgent } from \"../agent-detection/detect.js\";\n\n/** Glasstrace MCP endpoint for agent configuration. */\nconst MCP_ENDPOINT = \"https://api.glasstrace.dev/mcp\";\n\n/** Maps internal agent name to a human-readable display name. */\nfunction 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\n/** Options for the init command (parsed from CLI args or passed programmatically). */\nexport interface InitOptions {\n projectRoot: string;\n yes: boolean;\n coverageMap: boolean;\n}\n\n/** Result of running the init command. */\nexport interface InitResult {\n exitCode: number;\n summary: string[];\n warnings: string[];\n errors: string[];\n}\n\n/**\n * Prompts the user with a yes/no question. Returns true for yes.\n * In non-interactive mode (no TTY), returns the default value.\n */\nasync function promptYesNo(question: string, defaultValue: boolean): Promise<boolean> {\n if (!process.stdin.isTTY) {\n return defaultValue;\n }\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\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 * Core init logic. Exported for testability — the CLI entry point at the\n * bottom calls this function and translates the result to process.exit().\n */\nexport async function runInit(options: InitOptions): Promise<InitResult> {\n const { projectRoot, yes, coverageMap } = options;\n const summary: string[] = [];\n const warnings: string[] = [];\n const errors: string[] = [];\n\n // Step 1: Detect package.json\n const packageJsonPath = path.join(projectRoot, \"package.json\");\n if (!fs.existsSync(packageJsonPath)) {\n errors.push(\"No package.json found. Run this command from a Node.js project root.\");\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 2 + 3: Generate instrumentation.ts\n const instrumentationPath = path.join(projectRoot, \"instrumentation.ts\");\n const instrumentationExists = fs.existsSync(instrumentationPath);\n let shouldWriteInstrumentation = true;\n\n if (instrumentationExists && !yes) {\n shouldWriteInstrumentation = await promptYesNo(\n \"instrumentation.ts already exists. Overwrite?\",\n false,\n );\n } else if (instrumentationExists && yes) {\n // Non-interactive: never overwrite (idempotent)\n shouldWriteInstrumentation = false;\n }\n\n try {\n const created = await scaffoldInstrumentation(projectRoot, shouldWriteInstrumentation && instrumentationExists);\n if (created) {\n summary.push(\"Created instrumentation.ts\");\n } else if (instrumentationExists) {\n summary.push(\"Skipped instrumentation.ts (already exists)\");\n }\n } catch (err) {\n errors.push(`Failed to write instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 4: Detect and wrap next.config.*\n try {\n const wrapped = await scaffoldNextConfig(projectRoot);\n if (wrapped) {\n summary.push(\"Wrapped next.config with withGlasstraceConfig()\");\n } else {\n // Check if it was skipped because file already wrapped vs not found\n const hasNextConfig = [\"next.config.ts\", \"next.config.js\", \"next.config.mjs\"].some(\n (name) => fs.existsSync(path.join(projectRoot, name)),\n );\n if (hasNextConfig) {\n summary.push(\"Skipped next.config (already contains withGlasstraceConfig)\");\n } else {\n warnings.push(\"No next.config.* found. You may need to create one for Next.js projects.\");\n }\n }\n } catch (err) {\n errors.push(`Failed to modify next.config: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 5: Update .env.local\n try {\n const envCreated = await scaffoldEnvLocal(projectRoot);\n if (envCreated) {\n summary.push(\"Updated .env.local with Glasstrace configuration\");\n } else {\n summary.push(\"Skipped .env.local (GLASSTRACE_API_KEY already configured)\");\n }\n } catch (err) {\n errors.push(`Failed to update .env.local: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 6: Update .gitignore\n try {\n const gitignoreUpdated = await scaffoldGitignore(projectRoot);\n if (gitignoreUpdated) {\n summary.push(\"Updated .gitignore with .glasstrace/\");\n } else {\n summary.push(\"Skipped .gitignore (.glasstrace/ already listed)\");\n }\n } catch (err) {\n errors.push(`Failed to update .gitignore: ${err instanceof Error ? err.message : String(err)}`);\n return { exitCode: 1, summary, warnings, errors };\n }\n\n // Step 7: MCP auto-configuration\n // Use CI env vars (not TTY check) to distinguish automated builds from\n // manual CLI usage. TTY state is unreliable — piped output, test runners,\n // and IDE terminals all report isTTY=false despite being user-initiated.\n // Accept any truthy CI value (GitHub Actions, GitLab, CircleCI, Travis,\n // etc.) and also check GITHUB_ACTIONS specifically.\n const ciEnv = process.env[\"CI\"];\n const isCI =\n (typeof ciEnv === \"string\" &&\n ciEnv.trim() !== \"\" &&\n ciEnv.toLowerCase() !== \"false\" &&\n ciEnv.trim() !== \"0\") ||\n process.env[\"GITHUB_ACTIONS\"] === \"true\";\n\n try {\n const anonKey = await readAnonKey(projectRoot);\n\n if (anonKey !== null) {\n let anyConfigWritten = false;\n\n if (isCI) {\n // Non-interactive: write only the generic .glasstrace/mcp.json\n const genericAgent: DetectedAgent = {\n name: \"generic\",\n mcpConfigPath: path.join(projectRoot, \".glasstrace\", \"mcp.json\"),\n infoFilePath: null,\n cliAvailable: false,\n registrationCommand: null,\n };\n const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);\n await writeMcpConfig(genericAgent, genericConfig, projectRoot);\n if (genericAgent.mcpConfigPath !== null && fs.existsSync(genericAgent.mcpConfigPath)) {\n anyConfigWritten = true;\n summary.push(\"Created .glasstrace/mcp.json (CI mode)\");\n }\n } else {\n // Interactive: detect agents and configure each\n let agents: DetectedAgent[];\n try {\n agents = await detectAgents(projectRoot);\n } catch (detectErr) {\n warnings.push(\n `Agent detection failed: ${detectErr instanceof Error ? detectErr.message : String(detectErr)}. Writing generic config only.`,\n );\n // Fall back to generic-only config\n const genericAgent: DetectedAgent = {\n name: \"generic\",\n mcpConfigPath: path.join(projectRoot, \".glasstrace\", \"mcp.json\"),\n infoFilePath: null,\n cliAvailable: false,\n registrationCommand: null,\n };\n const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);\n await writeMcpConfig(genericAgent, genericConfig, projectRoot);\n if (genericAgent.mcpConfigPath !== null && fs.existsSync(genericAgent.mcpConfigPath)) {\n anyConfigWritten = true;\n }\n agents = [];\n }\n\n const configuredNames: string[] = [];\n\n for (const agent of agents) {\n try {\n const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);\n await writeMcpConfig(agent, configContent, projectRoot);\n\n // Verify the config file was actually written (writeMcpConfig\n // swallows permission errors and returns void)\n const configExists = agent.mcpConfigPath !== null && fs.existsSync(agent.mcpConfigPath);\n if (!configExists) {\n continue;\n }\n\n anyConfigWritten = true;\n\n const infoContent = generateInfoSection(agent, MCP_ENDPOINT);\n if (infoContent !== \"\") {\n await injectInfoSection(agent, infoContent, projectRoot);\n }\n\n if (agent.name !== \"generic\") {\n configuredNames.push(formatAgentName(agent.name));\n }\n } catch (agentErr) {\n warnings.push(\n `Failed to configure MCP for ${agent.name}: ${agentErr instanceof Error ? agentErr.message : String(agentErr)}`,\n );\n }\n }\n\n if (configuredNames.length > 0) {\n summary.push(`Configured MCP for: ${configuredNames.join(\", \")}`);\n } else if (anyConfigWritten) {\n summary.push(\"Created .glasstrace/mcp.json (generic config)\");\n }\n }\n\n // Add MCP config files to .gitignore\n await updateGitignore(\n [\".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\", \".codex/config.toml\"],\n projectRoot,\n );\n\n // Create marker file only if at least one config was successfully written.\n // Without this gate, a failed MCP setup would suppress future nudges,\n // leaving users stuck without MCP configuration.\n if (anyConfigWritten) {\n const markerCreated = await scaffoldMcpMarker(projectRoot, anonKey);\n if (markerCreated) {\n summary.push(\"Created .glasstrace/mcp-connected marker\");\n }\n }\n } else {\n warnings.push(\"No anonymous key found. Skipping MCP auto-configuration. Run init again after the SDK has generated a key.\");\n }\n } catch (mcpErr) {\n warnings.push(\n `MCP auto-configuration failed: ${mcpErr instanceof Error ? mcpErr.message : String(mcpErr)}`,\n );\n }\n\n // Step 8: Coverage map opt-in\n let enableCoverageMap = coverageMap;\n if (!yes && !coverageMap) {\n if (process.stdin.isTTY) {\n enableCoverageMap = await promptYesNo(\n \"Would you like to enable test coverage mapping?\",\n false,\n );\n }\n }\n\n if (enableCoverageMap) {\n try {\n const added = await addCoverageMapEnv(projectRoot);\n if (added) {\n summary.push(\"Added GLASSTRACE_COVERAGE_MAP=true to .env.local\");\n }\n } catch (err) {\n warnings.push(`Failed to add coverage map env: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // Step 9: Run initial import graph scan\n try {\n await buildImportGraph(projectRoot);\n summary.push(\"Completed initial import graph scan\");\n } catch (err) {\n warnings.push(`Import graph scan failed: ${err instanceof Error ? err.message : String(err)}. You can run it later.`);\n }\n }\n\n return { exitCode: 0, summary, warnings, errors };\n}\n\n/**\n * Parses CLI arguments into InitOptions.\n */\nfunction parseArgs(argv: string[]): InitOptions {\n const args = argv.slice(2); // skip node + script path\n let yes = false;\n let coverageMap = false;\n\n for (const arg of args) {\n if (arg === \"--yes\" || arg === \"-y\") {\n yes = true;\n } else if (arg === \"--coverage-map\") {\n coverageMap = true;\n }\n }\n\n // Auto-detect non-interactive\n if (!process.stdin.isTTY) {\n yes = true;\n }\n\n return {\n projectRoot: process.cwd(),\n yes,\n coverageMap,\n };\n}\n\n/**\n * CLI entry point. Only runs when this module is executed directly\n * (not when imported for testing).\n */\nconst scriptPath =\n typeof process !== \"undefined\" && process.argv[1] !== undefined\n ? process.argv[1].replace(/\\\\/g, \"/\")\n : undefined;\n\nconst scriptBasename = scriptPath !== undefined ? path.basename(scriptPath) : undefined;\n\nconst isDirectExecution =\n scriptPath !== undefined &&\n (scriptPath.endsWith(\"/cli/init.js\") ||\n scriptPath.endsWith(\"/cli/init.ts\") ||\n scriptBasename === \"glasstrace\");\n\nif (isDirectExecution) {\n const subcommand = process.argv[2];\n\n if (subcommand === \"mcp\") {\n if (process.argv[3] === \"add\") {\n // Parse --force and --dry-run from remaining args\n const remainingArgs = process.argv.slice(4);\n const force = remainingArgs.includes(\"--force\");\n const dryRun = remainingArgs.includes(\"--dry-run\");\n\n import(\"./mcp-add.js\")\n .then(({ mcpAdd }) => mcpAdd({ force, dryRun }))\n .then((result) => {\n for (const msg of result.messages) {\n process.stderr.write(msg + \"\\n\");\n }\n process.exit(result.exitCode);\n })\n .catch((err: unknown) => {\n process.stderr.write(\n `Fatal error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n } else {\n process.stderr.write(\n `Unknown mcp subcommand: ${process.argv[3] ?? \"(none)\"}\\n\\n` +\n \"Usage: glasstrace mcp add [--force] [--dry-run]\\n\",\n );\n process.exit(1);\n }\n } else if (subcommand === undefined || subcommand === \"init\" || subcommand.startsWith(\"-\")) {\n // Default: run init (handles `glasstrace`, `glasstrace init`, `glasstrace --yes`)\n const options = parseArgs(process.argv);\n\n runInit(options)\n .then((result) => {\n if (result.errors.length > 0) {\n for (const err of result.errors) {\n process.stderr.write(`Error: ${err}\\n`);\n }\n }\n if (result.warnings.length > 0) {\n for (const warn of result.warnings) {\n process.stderr.write(`Warning: ${warn}\\n`);\n }\n }\n if (result.summary.length > 0) {\n process.stderr.write(\"\\nGlasstrace initialized successfully!\\n\\n\");\n for (const line of result.summary) {\n process.stderr.write(` - ${line}\\n`);\n }\n process.stderr.write(\"\\nNext steps:\\n\");\n process.stderr.write(\" 1. Start your Next.js dev server\\n\");\n process.stderr.write(\n \" 2. Glasstrace works immediately in anonymous mode\\n\",\n );\n process.stderr.write(\n \" 3. To link to your account, set GLASSTRACE_API_KEY in .env.local\\n\\n\",\n );\n }\n process.exit(result.exitCode);\n })\n .catch((err: unknown) => {\n process.stderr.write(\n `Fatal error: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n } else {\n process.stderr.write(\n `Unknown command: ${subcommand}\\n\\n` +\n \"Usage:\\n\" +\n \" glasstrace init [--yes] [--coverage-map]\\n\" +\n \" glasstrace mcp add [--force] [--dry-run]\\n\",\n );\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|