@glasstrace/sdk 0.14.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ERGEG4ZQ.js → chunk-2LDBR3F3.js} +16 -3
- package/dist/chunk-2LDBR3F3.js.map +1 -0
- package/dist/chunk-A2AZL6MZ.js +309 -0
- package/dist/chunk-A2AZL6MZ.js.map +1 -0
- package/dist/{chunk-ARAOZCZT.js → chunk-ROFOJQWN.js} +118 -16
- package/dist/chunk-ROFOJQWN.js.map +1 -0
- package/dist/{chunk-WV3NIPWJ.js → chunk-ZNOD6FC7.js} +18 -276
- package/dist/chunk-ZNOD6FC7.js.map +1 -0
- package/dist/cli/init.cjs +458 -115
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +33 -1
- package/dist/cli/init.d.ts +33 -1
- package/dist/cli/init.js +144 -42
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +4 -2
- package/dist/cli/mcp-add.js.map +1 -1
- package/dist/cli/uninit.cjs +181 -60
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.d.cts +38 -8
- package/dist/cli/uninit.d.ts +38 -8
- package/dist/cli/uninit.js +6 -3
- package/dist/cli/validate.cjs +135 -0
- package/dist/cli/validate.cjs.map +1 -0
- package/dist/cli/validate.d.cts +60 -0
- package/dist/cli/validate.d.ts +60 -0
- package/dist/cli/validate.js +103 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/index.cjs +123 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -5
- package/dist/index.d.ts +45 -5
- package/dist/index.js +109 -46
- package/dist/index.js.map +1 -1
- package/dist/{source-map-uploader-W6VPGY26.js → source-map-uploader-3GWUQDTS.js} +6 -2
- package/package.json +6 -4
- package/dist/chunk-ARAOZCZT.js.map +0 -1
- package/dist/chunk-ERGEG4ZQ.js.map +0 -1
- package/dist/chunk-WV3NIPWJ.js.map +0 -1
- /package/dist/{source-map-uploader-W6VPGY26.js.map → source-map-uploader-3GWUQDTS.js.map} +0 -0
|
@@ -1,287 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NEXT_CONFIG_NAMES
|
|
3
|
-
} from "./chunk-BL3YDC6V.js";
|
|
4
1
|
import {
|
|
5
2
|
init_esm_shims
|
|
6
3
|
} from "./chunk-BGZ7J74D.js";
|
|
7
4
|
|
|
8
|
-
// src/cli/scaffolder.ts
|
|
9
|
-
init_esm_shims();
|
|
10
|
-
import { createHash } from "crypto";
|
|
11
|
-
import * as fs from "fs";
|
|
12
|
-
import * as path from "path";
|
|
13
|
-
function identityFingerprint(token) {
|
|
14
|
-
return `sha256:${createHash("sha256").update(token).digest("hex")}`;
|
|
15
|
-
}
|
|
16
|
-
function hasRegisterGlasstraceCall(content) {
|
|
17
|
-
return content.split("\n").some((line) => {
|
|
18
|
-
const uncommented = line.replace(/\/\/.*$/, "");
|
|
19
|
-
return /\bregisterGlasstrace\s*\(/.test(uncommented);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function injectRegisterGlasstrace(content) {
|
|
23
|
-
if (hasRegisterGlasstraceCall(content)) {
|
|
24
|
-
return { injected: false, content };
|
|
25
|
-
}
|
|
26
|
-
const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
|
|
27
|
-
const match = registerFnRegex.exec(content);
|
|
28
|
-
if (!match) {
|
|
29
|
-
return { injected: false, content };
|
|
30
|
-
}
|
|
31
|
-
const afterBrace = content.slice(match.index + match[0].length);
|
|
32
|
-
const indentMatch = /\n([ \t]+)/.exec(afterBrace);
|
|
33
|
-
const indent = indentMatch ? indentMatch[1] : " ";
|
|
34
|
-
const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
|
|
35
|
-
const hasGlasstraceImport = content.includes("@glasstrace/sdk");
|
|
36
|
-
const insertPoint = match.index + match[0].length;
|
|
37
|
-
const callInjection = `
|
|
38
|
-
${indent}// Glasstrace must be registered before other instrumentation
|
|
39
|
-
${indent}registerGlasstrace();
|
|
40
|
-
`;
|
|
41
|
-
let modified;
|
|
42
|
-
if (hasGlasstraceImport) {
|
|
43
|
-
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
|
|
44
|
-
const importMatch = importRegex.exec(content);
|
|
45
|
-
if (importMatch) {
|
|
46
|
-
const specifiers = importMatch[1];
|
|
47
|
-
const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
|
|
48
|
-
if (alreadyImported) {
|
|
49
|
-
modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
50
|
-
} else {
|
|
51
|
-
const existingImports = specifiers.trimEnd();
|
|
52
|
-
const separator = existingImports.endsWith(",") ? " " : ", ";
|
|
53
|
-
const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
|
|
54
|
-
modified = content.replace(importMatch[0], updatedImport);
|
|
55
|
-
const newMatch = registerFnRegex.exec(modified);
|
|
56
|
-
if (newMatch) {
|
|
57
|
-
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
58
|
-
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
modified = importLine + content;
|
|
63
|
-
const newMatch = registerFnRegex.exec(modified);
|
|
64
|
-
if (newMatch) {
|
|
65
|
-
const newInsertPoint = newMatch.index + newMatch[0].length;
|
|
66
|
-
modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
|
|
71
|
-
}
|
|
72
|
-
return { injected: true, content: modified };
|
|
73
|
-
}
|
|
74
|
-
async function scaffoldInstrumentation(projectRoot) {
|
|
75
|
-
const filePath = path.join(projectRoot, "instrumentation.ts");
|
|
76
|
-
if (!fs.existsSync(filePath)) {
|
|
77
|
-
const content = `import { registerGlasstrace } from "@glasstrace/sdk";
|
|
78
|
-
|
|
79
|
-
export async function register() {
|
|
80
|
-
// Glasstrace must be registered before Prisma instrumentation
|
|
81
|
-
// to ensure all ORM spans are captured correctly.
|
|
82
|
-
// If you use @prisma/instrumentation, import it after this call.
|
|
83
|
-
registerGlasstrace();
|
|
84
|
-
}
|
|
85
|
-
`;
|
|
86
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
87
|
-
return { action: "created" };
|
|
88
|
-
}
|
|
89
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
90
|
-
if (hasRegisterGlasstraceCall(existing)) {
|
|
91
|
-
return { action: "already-registered" };
|
|
92
|
-
}
|
|
93
|
-
const result = injectRegisterGlasstrace(existing);
|
|
94
|
-
if (result.injected) {
|
|
95
|
-
fs.writeFileSync(filePath, result.content, "utf-8");
|
|
96
|
-
return { action: "injected" };
|
|
97
|
-
}
|
|
98
|
-
return { action: "unrecognized" };
|
|
99
|
-
}
|
|
100
|
-
async function scaffoldNextConfig(projectRoot) {
|
|
101
|
-
let configPath;
|
|
102
|
-
let configName;
|
|
103
|
-
for (const name of NEXT_CONFIG_NAMES) {
|
|
104
|
-
const candidate = path.join(projectRoot, name);
|
|
105
|
-
if (fs.existsSync(candidate)) {
|
|
106
|
-
configPath = candidate;
|
|
107
|
-
configName = name;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (configPath === void 0 || configName === void 0) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const existing = fs.readFileSync(configPath, "utf-8");
|
|
115
|
-
if (existing.trim().length === 0) {
|
|
116
|
-
return { modified: false, reason: "empty-file" };
|
|
117
|
-
}
|
|
118
|
-
if (existing.includes("withGlasstraceConfig")) {
|
|
119
|
-
return { modified: false, reason: "already-wrapped" };
|
|
120
|
-
}
|
|
121
|
-
const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
|
|
122
|
-
if (isESM) {
|
|
123
|
-
const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
|
|
124
|
-
const wrapResult2 = wrapExport(existing);
|
|
125
|
-
if (!wrapResult2.wrapped) {
|
|
126
|
-
return { modified: false, reason: "no-export" };
|
|
127
|
-
}
|
|
128
|
-
const modified2 = importLine + "\n" + wrapResult2.content;
|
|
129
|
-
fs.writeFileSync(configPath, modified2, "utf-8");
|
|
130
|
-
return { modified: true };
|
|
131
|
-
}
|
|
132
|
-
const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
|
|
133
|
-
const wrapResult = wrapCJSExport(existing);
|
|
134
|
-
if (!wrapResult.wrapped) {
|
|
135
|
-
return { modified: false, reason: "no-export" };
|
|
136
|
-
}
|
|
137
|
-
const modified = requireLine + "\n" + wrapResult.content;
|
|
138
|
-
fs.writeFileSync(configPath, modified, "utf-8");
|
|
139
|
-
return { modified: true };
|
|
140
|
-
}
|
|
141
|
-
function wrapExport(content) {
|
|
142
|
-
const marker = "export default";
|
|
143
|
-
const idx = content.lastIndexOf(marker);
|
|
144
|
-
if (idx === -1) {
|
|
145
|
-
return { content, wrapped: false };
|
|
146
|
-
}
|
|
147
|
-
const preamble = content.slice(0, idx);
|
|
148
|
-
const exprRaw = content.slice(idx + marker.length);
|
|
149
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
150
|
-
if (expr.length === 0) {
|
|
151
|
-
return { content, wrapped: false };
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
content: preamble + `export default withGlasstraceConfig(${expr});
|
|
155
|
-
`,
|
|
156
|
-
wrapped: true
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function wrapCJSExport(content) {
|
|
160
|
-
const cjsMarker = "module.exports";
|
|
161
|
-
const cjsIdx = content.lastIndexOf(cjsMarker);
|
|
162
|
-
if (cjsIdx === -1) {
|
|
163
|
-
return { content, wrapped: false };
|
|
164
|
-
}
|
|
165
|
-
const preamble = content.slice(0, cjsIdx);
|
|
166
|
-
const afterMarker = content.slice(cjsIdx + cjsMarker.length);
|
|
167
|
-
const eqMatch = /^\s*=\s*/.exec(afterMarker);
|
|
168
|
-
if (!eqMatch) {
|
|
169
|
-
return { content, wrapped: false };
|
|
170
|
-
}
|
|
171
|
-
const exprRaw = afterMarker.slice(eqMatch[0].length);
|
|
172
|
-
const expr = exprRaw.trim().replace(/;?\s*$/, "");
|
|
173
|
-
if (expr.length === 0) {
|
|
174
|
-
return { content, wrapped: false };
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
content: preamble + `module.exports = withGlasstraceConfig(${expr});
|
|
178
|
-
`,
|
|
179
|
-
wrapped: true
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
async function scaffoldEnvLocal(projectRoot) {
|
|
183
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
184
|
-
if (fs.existsSync(filePath)) {
|
|
185
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
186
|
-
if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
190
|
-
fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
async function addCoverageMapEnv(projectRoot) {
|
|
197
|
-
const filePath = path.join(projectRoot, ".env.local");
|
|
198
|
-
if (!fs.existsSync(filePath)) {
|
|
199
|
-
fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
203
|
-
const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
|
|
204
|
-
const keyMatch = keyRegex.exec(existing);
|
|
205
|
-
if (keyMatch) {
|
|
206
|
-
const currentValue = keyMatch[2].trim();
|
|
207
|
-
if (currentValue === "true") {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
|
|
211
|
-
fs.writeFileSync(filePath, updated, "utf-8");
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
215
|
-
fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
async function scaffoldGitignore(projectRoot) {
|
|
219
|
-
const filePath = path.join(projectRoot, ".gitignore");
|
|
220
|
-
if (fs.existsSync(filePath)) {
|
|
221
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
222
|
-
const lines = existing.split("\n").map((l) => l.trim());
|
|
223
|
-
if (lines.includes(".glasstrace/")) {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
227
|
-
fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
async function scaffoldMcpMarker(projectRoot, anonKey) {
|
|
234
|
-
const dirPath = path.join(projectRoot, ".glasstrace");
|
|
235
|
-
const markerPath = path.join(dirPath, "mcp-connected");
|
|
236
|
-
const keyHash = identityFingerprint(anonKey);
|
|
237
|
-
if (fs.existsSync(markerPath)) {
|
|
238
|
-
try {
|
|
239
|
-
const existing = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
|
|
240
|
-
if (existing.keyHash === keyHash) {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
} catch {
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
247
|
-
const marker = JSON.stringify(
|
|
248
|
-
{ keyHash, configuredAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
249
|
-
null,
|
|
250
|
-
2
|
|
251
|
-
);
|
|
252
|
-
fs.writeFileSync(markerPath, marker, { mode: 384 });
|
|
253
|
-
fs.chmodSync(markerPath, 384);
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
5
|
// src/agent-detection/detect.ts
|
|
258
6
|
init_esm_shims();
|
|
259
7
|
import { execFile } from "child_process";
|
|
260
8
|
import { access, stat } from "fs/promises";
|
|
261
|
-
import { dirname, join
|
|
9
|
+
import { dirname, join, resolve } from "path";
|
|
262
10
|
import { homedir } from "os";
|
|
263
11
|
import { constants } from "fs";
|
|
264
12
|
var AGENT_RULES = [
|
|
265
13
|
{
|
|
266
14
|
name: "claude",
|
|
267
15
|
markers: [".claude", "CLAUDE.md"],
|
|
268
|
-
mcpConfigPath: (dir) =>
|
|
269
|
-
infoFilePath: (dir) =>
|
|
16
|
+
mcpConfigPath: (dir) => join(dir, ".mcp.json"),
|
|
17
|
+
infoFilePath: (dir) => join(dir, "CLAUDE.md"),
|
|
270
18
|
cliBinary: "claude",
|
|
271
19
|
registrationCommand: "npx glasstrace mcp add --agent claude"
|
|
272
20
|
},
|
|
273
21
|
{
|
|
274
22
|
name: "codex",
|
|
275
23
|
markers: ["codex.md", ".codex"],
|
|
276
|
-
mcpConfigPath: (dir) =>
|
|
277
|
-
infoFilePath: (dir) =>
|
|
24
|
+
mcpConfigPath: (dir) => join(dir, ".codex", "config.toml"),
|
|
25
|
+
infoFilePath: (dir) => join(dir, "codex.md"),
|
|
278
26
|
cliBinary: "codex",
|
|
279
27
|
registrationCommand: "npx glasstrace mcp add --agent codex"
|
|
280
28
|
},
|
|
281
29
|
{
|
|
282
30
|
name: "gemini",
|
|
283
31
|
markers: [".gemini"],
|
|
284
|
-
mcpConfigPath: (dir) =>
|
|
32
|
+
mcpConfigPath: (dir) => join(dir, ".gemini", "settings.json"),
|
|
285
33
|
infoFilePath: () => null,
|
|
286
34
|
cliBinary: "gemini",
|
|
287
35
|
registrationCommand: "npx glasstrace mcp add --agent gemini"
|
|
@@ -289,23 +37,23 @@ var AGENT_RULES = [
|
|
|
289
37
|
{
|
|
290
38
|
name: "cursor",
|
|
291
39
|
markers: [".cursor", ".cursorrules"],
|
|
292
|
-
mcpConfigPath: (dir) =>
|
|
293
|
-
infoFilePath: (dir) =>
|
|
40
|
+
mcpConfigPath: (dir) => join(dir, ".cursor", "mcp.json"),
|
|
41
|
+
infoFilePath: (dir) => join(dir, ".cursorrules"),
|
|
294
42
|
cliBinary: null,
|
|
295
43
|
registrationCommand: "npx glasstrace mcp add --agent cursor"
|
|
296
44
|
},
|
|
297
45
|
{
|
|
298
46
|
name: "windsurf",
|
|
299
47
|
markers: [".windsurfrules", ".windsurf"],
|
|
300
|
-
mcpConfigPath: () =>
|
|
301
|
-
infoFilePath: (dir) =>
|
|
48
|
+
mcpConfigPath: () => join(homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
49
|
+
infoFilePath: (dir) => join(dir, ".windsurfrules"),
|
|
302
50
|
cliBinary: null,
|
|
303
51
|
registrationCommand: "npx glasstrace mcp add --agent windsurf"
|
|
304
52
|
}
|
|
305
53
|
];
|
|
306
|
-
async function pathExists(
|
|
54
|
+
async function pathExists(path, mode = constants.R_OK) {
|
|
307
55
|
try {
|
|
308
|
-
await access(
|
|
56
|
+
await access(path, mode);
|
|
309
57
|
return true;
|
|
310
58
|
} catch {
|
|
311
59
|
return false;
|
|
@@ -314,7 +62,7 @@ async function pathExists(path2, mode = constants.R_OK) {
|
|
|
314
62
|
async function findGitRoot(startDir) {
|
|
315
63
|
let current = resolve(startDir);
|
|
316
64
|
while (true) {
|
|
317
|
-
if (await pathExists(
|
|
65
|
+
if (await pathExists(join(current, ".git"), constants.F_OK)) {
|
|
318
66
|
return current;
|
|
319
67
|
}
|
|
320
68
|
const parent = dirname(current);
|
|
@@ -368,7 +116,7 @@ async function detectAgents(projectRoot) {
|
|
|
368
116
|
for (const dir of searchDirs) {
|
|
369
117
|
let markerFound = false;
|
|
370
118
|
for (const marker of rule.markers) {
|
|
371
|
-
if (await pathExists(
|
|
119
|
+
if (await pathExists(join(dir, marker))) {
|
|
372
120
|
markerFound = true;
|
|
373
121
|
break;
|
|
374
122
|
}
|
|
@@ -400,7 +148,7 @@ async function detectAgents(projectRoot) {
|
|
|
400
148
|
}
|
|
401
149
|
detected.push({
|
|
402
150
|
name: "generic",
|
|
403
|
-
mcpConfigPath:
|
|
151
|
+
mcpConfigPath: join(resolvedRoot, ".glasstrace", "mcp.json"),
|
|
404
152
|
infoFilePath: null,
|
|
405
153
|
cliAvailable: false,
|
|
406
154
|
registrationCommand: null
|
|
@@ -574,7 +322,7 @@ ${content}${m.end}
|
|
|
574
322
|
// src/agent-detection/inject.ts
|
|
575
323
|
init_esm_shims();
|
|
576
324
|
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
577
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
325
|
+
import { dirname as dirname2, isAbsolute, join as join2 } from "path";
|
|
578
326
|
var HTML_START = "<!-- glasstrace:mcp:start -->";
|
|
579
327
|
var HTML_END = "<!-- glasstrace:mcp:end -->";
|
|
580
328
|
var HASH_START = "# glasstrace:mcp:start";
|
|
@@ -703,7 +451,7 @@ async function injectInfoSection(agent, content, projectRoot) {
|
|
|
703
451
|
}
|
|
704
452
|
}
|
|
705
453
|
async function updateGitignore(paths, projectRoot) {
|
|
706
|
-
const gitignorePath =
|
|
454
|
+
const gitignorePath = join2(projectRoot, ".gitignore");
|
|
707
455
|
const relativePaths = paths.filter((p) => !isAbsolute(p));
|
|
708
456
|
if (relativePaths.length === 0) {
|
|
709
457
|
return;
|
|
@@ -750,12 +498,6 @@ async function updateGitignore(paths, projectRoot) {
|
|
|
750
498
|
}
|
|
751
499
|
|
|
752
500
|
export {
|
|
753
|
-
scaffoldInstrumentation,
|
|
754
|
-
scaffoldNextConfig,
|
|
755
|
-
scaffoldEnvLocal,
|
|
756
|
-
addCoverageMapEnv,
|
|
757
|
-
scaffoldGitignore,
|
|
758
|
-
scaffoldMcpMarker,
|
|
759
501
|
detectAgents,
|
|
760
502
|
generateMcpConfig,
|
|
761
503
|
generateInfoSection,
|
|
@@ -763,4 +505,4 @@ export {
|
|
|
763
505
|
injectInfoSection,
|
|
764
506
|
updateGitignore
|
|
765
507
|
};
|
|
766
|
-
//# sourceMappingURL=chunk-
|
|
508
|
+
//# sourceMappingURL=chunk-ZNOD6FC7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent-detection/detect.ts","../src/agent-detection/configs.ts","../src/agent-detection/inject.ts"],"sourcesContent":["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 basic string special characters in the endpoint value.\n // TOML requires backslashes, quotes, and control characters to be escaped.\n const safeEndpoint = endpoint\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\");\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 default: {\n const _exhaustive: never = agent.name;\n throw new Error(`Unknown agent: ${_exhaustive}`);\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 default: {\n const _exhaustive: never = agent.name;\n throw new Error(`Unknown agent: ${_exhaustive}`);\n }\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"],"mappings":"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","names":["resolve","dirname","join"]}
|