@glasstrace/sdk 1.10.1 → 1.11.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/README.md +43 -5
- package/dist/{chunk-WQF7ZQOM.js → chunk-DQFGNX3H.js} +13 -8
- package/dist/{chunk-WQF7ZQOM.js.map → chunk-DQFGNX3H.js.map} +1 -1
- package/dist/{chunk-UMGZJYC4.js → chunk-FQ4SEG6Y.js} +8 -3
- package/dist/chunk-FQ4SEG6Y.js.map +1 -0
- package/dist/chunk-KOYZJN6G.js +651 -0
- package/dist/chunk-KOYZJN6G.js.map +1 -0
- package/dist/{chunk-ZBQQXVHD.js → chunk-YIEXKQYP.js} +2 -67
- package/dist/chunk-YIEXKQYP.js.map +1 -0
- package/dist/cli/init.cjs +460 -127
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +29 -16
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs +346 -98
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +32 -14
- package/dist/cli/mcp-add.js.map +1 -1
- package/dist/cli/status.cjs +6 -1
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.js +7 -2
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/uninit.cjs +6 -1
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.js +2 -2
- package/dist/cli/upgrade-instructions.cjs +390 -113
- package/dist/cli/upgrade-instructions.cjs.map +1 -1
- package/dist/cli/upgrade-instructions.js +70 -18
- package/dist/cli/upgrade-instructions.js.map +1 -1
- package/dist/index.cjs +11 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/node-entry.cjs +11 -6
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-TJ46YOGJ.js +0 -355
- package/dist/chunk-TJ46YOGJ.js.map +0 -1
- package/dist/chunk-UMGZJYC4.js.map +0 -1
- package/dist/chunk-ZBQQXVHD.js.map +0 -1
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findMarkerBoundaries
|
|
3
|
+
} from "./chunk-YIEXKQYP.js";
|
|
4
|
+
|
|
5
|
+
// src/agent-detection/detect.ts
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { access, stat } from "node:fs/promises";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { constants } from "node:fs";
|
|
11
|
+
var AGENT_RULES = [
|
|
12
|
+
{
|
|
13
|
+
name: "claude",
|
|
14
|
+
markers: [".claude", "CLAUDE.md"],
|
|
15
|
+
mcpConfigPath: (dir) => join(dir, ".mcp.json"),
|
|
16
|
+
infoFilePath: (dir) => join(dir, "CLAUDE.md"),
|
|
17
|
+
cliBinary: "claude",
|
|
18
|
+
registrationCommand: "npx glasstrace mcp add --agent claude"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "codex",
|
|
22
|
+
// Codex 2026 default discovery is `AGENTS.override.md` → `AGENTS.md` →
|
|
23
|
+
// opt-in `project_doc_fallback_filenames`; `codex.md` is NOT in the
|
|
24
|
+
// default fallback list. Detection requires Codex-specific markers
|
|
25
|
+
// (`codex.md` legacy, `.codex/` config dir) — `AGENTS.md` is NOT
|
|
26
|
+
// included as a marker because the SDK now writes `AGENTS.md`
|
|
27
|
+
// broadly via the multi-target dispatcher's companion writes; if
|
|
28
|
+
// `AGENTS.md` were a Codex marker, every project with the SDK's
|
|
29
|
+
// own companion AGENTS.md would re-classify as Codex on subsequent
|
|
30
|
+
// detect runs and trigger unintended `.codex/config.toml` writes
|
|
31
|
+
// (Codex P1 + Copilot P1 review of Wave 18 PR #274). The canonical
|
|
32
|
+
// write destination remains AGENTS.md regardless of which marker
|
|
33
|
+
// classified the project.
|
|
34
|
+
markers: ["codex.md", ".codex"],
|
|
35
|
+
mcpConfigPath: (dir) => join(dir, ".codex", "config.toml"),
|
|
36
|
+
infoFilePath: (dir) => join(dir, "AGENTS.md"),
|
|
37
|
+
cliBinary: "codex",
|
|
38
|
+
registrationCommand: "npx glasstrace mcp add --agent codex"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "gemini",
|
|
42
|
+
markers: [".gemini", "GEMINI.md"],
|
|
43
|
+
mcpConfigPath: (dir) => join(dir, ".gemini", "settings.json"),
|
|
44
|
+
infoFilePath: (dir) => join(dir, "GEMINI.md"),
|
|
45
|
+
cliBinary: "gemini",
|
|
46
|
+
registrationCommand: "npx glasstrace mcp add --agent gemini"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "cursor",
|
|
50
|
+
// `.cursor/rules/*.mdc` is the current canonical format per Cursor's
|
|
51
|
+
// 2026 docs. `.cursorrules` (single file) is supported-but-deprecated
|
|
52
|
+
// and stays as a transitional fallback that the multi-target write
|
|
53
|
+
// helper writes unconditionally alongside the .mdc canonical.
|
|
54
|
+
markers: [".cursor", ".cursorrules"],
|
|
55
|
+
mcpConfigPath: (dir) => join(dir, ".cursor", "mcp.json"),
|
|
56
|
+
infoFilePath: (dir) => join(dir, ".cursor", "rules", "glasstrace.mdc"),
|
|
57
|
+
cliBinary: null,
|
|
58
|
+
registrationCommand: "npx glasstrace mcp add --agent cursor"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "windsurf",
|
|
62
|
+
// Windsurf's current canonical workspace-rules format is
|
|
63
|
+
// `.windsurf/rules/*.md`. AGENTS.md is a parallel cross-tool
|
|
64
|
+
// mechanism Windsurf also reads BUT is NOT included as a Windsurf
|
|
65
|
+
// detection marker — the SDK writes `AGENTS.md` broadly via the
|
|
66
|
+
// multi-target dispatcher's companion writes, so treating
|
|
67
|
+
// `AGENTS.md` as a Windsurf marker would re-classify every
|
|
68
|
+
// SDK-managed project as Windsurf and cause `glasstrace uninit`
|
|
69
|
+
// to mutate the global `~/.codeium/windsurf/mcp_config.json` for
|
|
70
|
+
// non-Windsurf projects (Codex P1 + Copilot P1 review of Wave 18
|
|
71
|
+
// PR #274). The single-file `.windsurfrules` is the deprecated
|
|
72
|
+
// legacy form — recognized as a marker so legacy projects classify
|
|
73
|
+
// correctly, but the SDK no longer writes to it.
|
|
74
|
+
markers: [".windsurf", ".windsurfrules"],
|
|
75
|
+
mcpConfigPath: () => join(homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
76
|
+
infoFilePath: (dir) => join(dir, ".windsurf", "rules", "glasstrace.md"),
|
|
77
|
+
cliBinary: null,
|
|
78
|
+
registrationCommand: "npx glasstrace mcp add --agent windsurf"
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
async function pathExists(path, mode = constants.R_OK) {
|
|
82
|
+
try {
|
|
83
|
+
await access(path, mode);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function findGitRoot(startDir) {
|
|
90
|
+
let current = resolve(startDir);
|
|
91
|
+
while (true) {
|
|
92
|
+
if (await pathExists(join(current, ".git"), constants.F_OK)) {
|
|
93
|
+
return current;
|
|
94
|
+
}
|
|
95
|
+
const parent = dirname(current);
|
|
96
|
+
if (parent === current) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
current = parent;
|
|
100
|
+
}
|
|
101
|
+
return resolve(startDir);
|
|
102
|
+
}
|
|
103
|
+
function isCliAvailable(binary) {
|
|
104
|
+
return new Promise((resolve2) => {
|
|
105
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
106
|
+
execFile(command, [binary], (error) => {
|
|
107
|
+
resolve2(error === null);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function detectAgents(projectRoot) {
|
|
112
|
+
const resolvedRoot = resolve(projectRoot);
|
|
113
|
+
let rootStat;
|
|
114
|
+
try {
|
|
115
|
+
rootStat = await stat(resolvedRoot);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const code = err.code;
|
|
118
|
+
throw new Error(
|
|
119
|
+
`projectRoot does not exist: ${resolvedRoot}` + (code ? ` (${code})` : "")
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (!rootStat.isDirectory()) {
|
|
123
|
+
throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);
|
|
124
|
+
}
|
|
125
|
+
const gitRoot = await findGitRoot(resolvedRoot);
|
|
126
|
+
const searchDirs = [];
|
|
127
|
+
let current = resolvedRoot;
|
|
128
|
+
while (true) {
|
|
129
|
+
searchDirs.push(current);
|
|
130
|
+
if (current === gitRoot) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
const parent = dirname(current);
|
|
134
|
+
if (parent === current) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
current = parent;
|
|
138
|
+
}
|
|
139
|
+
const detected = [];
|
|
140
|
+
const seenAgents = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const rule of AGENT_RULES) {
|
|
142
|
+
let foundDir = null;
|
|
143
|
+
for (const dir of searchDirs) {
|
|
144
|
+
let markerFound = false;
|
|
145
|
+
for (const marker of rule.markers) {
|
|
146
|
+
if (await pathExists(join(dir, marker))) {
|
|
147
|
+
markerFound = true;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (markerFound) {
|
|
152
|
+
foundDir = dir;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (foundDir === null) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (seenAgents.has(rule.name)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
seenAgents.add(rule.name);
|
|
163
|
+
const infoFilePath = rule.infoFilePath(foundDir);
|
|
164
|
+
const cliAvailable = rule.cliBinary ? await isCliAvailable(rule.cliBinary) : false;
|
|
165
|
+
detected.push({
|
|
166
|
+
name: rule.name,
|
|
167
|
+
mcpConfigPath: rule.mcpConfigPath(foundDir),
|
|
168
|
+
infoFilePath,
|
|
169
|
+
cliAvailable,
|
|
170
|
+
registrationCommand: rule.registrationCommand
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
detected.push({
|
|
174
|
+
name: "generic",
|
|
175
|
+
mcpConfigPath: join(resolvedRoot, ".glasstrace", "mcp.json"),
|
|
176
|
+
infoFilePath: join(resolvedRoot, "AGENTS.md"),
|
|
177
|
+
cliAvailable: false,
|
|
178
|
+
registrationCommand: null
|
|
179
|
+
});
|
|
180
|
+
return detected;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/agent-detection/agent-instruction-text.ts
|
|
184
|
+
function buildAgentInstructionBody() {
|
|
185
|
+
return [
|
|
186
|
+
"",
|
|
187
|
+
"## Glasstrace MCP \u2014 Runtime Debugging Evidence",
|
|
188
|
+
"",
|
|
189
|
+
"Glasstrace is connected via MCP (`glasstrace`). It captures runtime traces from this app and exposes them through tools that disambiguate bugs source alone cannot resolve.",
|
|
190
|
+
"",
|
|
191
|
+
"### Call Glasstrace FIRST when:",
|
|
192
|
+
"- A value passed at runtime is suspect: wrong role, locale, timezone, template, tenant id, or any enum/config that branches downstream.",
|
|
193
|
+
"- The bug is in a side-effect operation: email sent, calendar event created, webhook dispatched, queued job, `after()` callback.",
|
|
194
|
+
"- The user reports a symptom but you do not yet know which code path actually executed.",
|
|
195
|
+
"- You need to confirm a route or procedure ran in the relevant time window before doing a source dive.",
|
|
196
|
+
"",
|
|
197
|
+
"### SKIP Glasstrace when:",
|
|
198
|
+
"- The bug is statically obvious from source (N+1 query, missing await, redundant query, type error, missing null check).",
|
|
199
|
+
"- The change is a refactor whose correctness does not depend on runtime behavior.",
|
|
200
|
+
"- You already have a precise traceId from another source.",
|
|
201
|
+
"",
|
|
202
|
+
"### Workflow",
|
|
203
|
+
"1. Start with `find_trace_candidates`. Pass whatever route or procedure name is natural \u2014 the server normalizes vocabulary and, on miss, returns close matches and a sample of routes actually present in the window.",
|
|
204
|
+
"2. Take the highest-confidence candidate's `suggestedFollowups` and pass them straight to `get_trace` or `get_root_cause`.",
|
|
205
|
+
"3. For side-effect bugs, read `sideEffectSummary` in the `get_trace` / `get_root_cause` response. The allowlisted fields (`templateKey`, `providerOperation`, `role`, `locale`, `timezone`, `status`, `phase`) are the ones that disambiguate payload bugs.",
|
|
206
|
+
"4. If a tool returns empty, READ the response's empty-result envelope before pivoting to source \u2014 each field disambiguates a different reason for the empty result:",
|
|
207
|
+
" - `closeMatches` / `recentRoutesSample` \u2014 your filter vocabulary doesn't match server-side names; the server returns the closest known names + a sample of routes actually present.",
|
|
208
|
+
' - `windowActivity` \u2014 load-bearing four-way distinguisher. `totalTracesInWindow === 0` AND `totalTracesInTenantEver > 0` means "your time window missed the activity"; `totalTracesInTenantEver === 0` means "this tenant has never produced traces" (SDK not registered, or never hit); `captureConfigBlocksRequest === true` means "the SDK\'s capture config dropped this route"; otherwise the empty result is a vocabulary miss \u2014 see `closeMatches`.',
|
|
209
|
+
" - `humanReadable` \u2014 prose guidance written for the agent.",
|
|
210
|
+
" - `recoveryActions` \u2014 concrete next-call shapes.",
|
|
211
|
+
" - `diagnosticValue` / `recommendedNextStep` \u2014 whether to keep searching or stop.",
|
|
212
|
+
" Empty results carry `notAbsenceProof: true` \u2014 they are never proof the bug did not occur.",
|
|
213
|
+
"",
|
|
214
|
+
"### Tools",
|
|
215
|
+
"- `find_trace_candidates` \u2014 discovery, vocabulary-tolerant filter",
|
|
216
|
+
"- `get_trace` \u2014 exact trace by `traceId`",
|
|
217
|
+
"- `get_root_cause` \u2014 root-cause analysis for a `traceId`",
|
|
218
|
+
"- `get_session_timeline` \u2014 events for a session",
|
|
219
|
+
"- `get_latest_error` / `get_error_list` \u2014 recent server errors",
|
|
220
|
+
"",
|
|
221
|
+
"Side-effect evidence is allowlisted and compact by design. Fields you don't see may have been omitted by policy, not absent at runtime.",
|
|
222
|
+
""
|
|
223
|
+
].join("\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/agent-detection/configs.ts
|
|
227
|
+
function generateMcpConfig(agent, endpoint, bearer) {
|
|
228
|
+
if (!endpoint || endpoint.trim() === "") {
|
|
229
|
+
throw new Error("endpoint must not be empty");
|
|
230
|
+
}
|
|
231
|
+
if (!bearer || bearer.trim() === "") {
|
|
232
|
+
throw new Error("bearer must not be empty");
|
|
233
|
+
}
|
|
234
|
+
switch (agent.name) {
|
|
235
|
+
case "claude":
|
|
236
|
+
return JSON.stringify(
|
|
237
|
+
{
|
|
238
|
+
mcpServers: {
|
|
239
|
+
glasstrace: {
|
|
240
|
+
type: "http",
|
|
241
|
+
url: endpoint,
|
|
242
|
+
headers: {
|
|
243
|
+
Authorization: `Bearer ${bearer}`
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
null,
|
|
249
|
+
2
|
|
250
|
+
);
|
|
251
|
+
case "codex": {
|
|
252
|
+
const safeEndpoint = endpoint.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
253
|
+
return [
|
|
254
|
+
"[mcp_servers.glasstrace]",
|
|
255
|
+
`url = "${safeEndpoint}"`,
|
|
256
|
+
`bearer_token_env_var = "GLASSTRACE_API_KEY"`,
|
|
257
|
+
""
|
|
258
|
+
].join("\n");
|
|
259
|
+
}
|
|
260
|
+
case "gemini":
|
|
261
|
+
return JSON.stringify(
|
|
262
|
+
{
|
|
263
|
+
mcpServers: {
|
|
264
|
+
glasstrace: {
|
|
265
|
+
httpUrl: endpoint,
|
|
266
|
+
headers: {
|
|
267
|
+
Authorization: `Bearer ${bearer}`
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
null,
|
|
273
|
+
2
|
|
274
|
+
);
|
|
275
|
+
case "cursor":
|
|
276
|
+
return JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
mcpServers: {
|
|
279
|
+
glasstrace: {
|
|
280
|
+
type: "http",
|
|
281
|
+
url: endpoint,
|
|
282
|
+
headers: {
|
|
283
|
+
Authorization: `Bearer ${bearer}`
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
null,
|
|
289
|
+
2
|
|
290
|
+
);
|
|
291
|
+
case "windsurf":
|
|
292
|
+
return JSON.stringify(
|
|
293
|
+
{
|
|
294
|
+
mcpServers: {
|
|
295
|
+
glasstrace: {
|
|
296
|
+
type: "http",
|
|
297
|
+
url: endpoint,
|
|
298
|
+
headers: {
|
|
299
|
+
Authorization: `Bearer ${bearer}`
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
null,
|
|
305
|
+
2
|
|
306
|
+
);
|
|
307
|
+
case "generic":
|
|
308
|
+
return JSON.stringify(
|
|
309
|
+
{
|
|
310
|
+
mcpServers: {
|
|
311
|
+
glasstrace: {
|
|
312
|
+
type: "http",
|
|
313
|
+
url: endpoint,
|
|
314
|
+
headers: {
|
|
315
|
+
Authorization: `Bearer ${bearer}`
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
null,
|
|
321
|
+
2
|
|
322
|
+
);
|
|
323
|
+
default: {
|
|
324
|
+
const _exhaustive = agent.name;
|
|
325
|
+
throw new Error(`Unknown agent: ${_exhaustive}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
var SDK_VERSION_STAMP_PATTERN = /^[A-Za-z0-9.+-]+$/;
|
|
330
|
+
function htmlMarkers(sdkVersion) {
|
|
331
|
+
return {
|
|
332
|
+
start: `<!-- glasstrace:mcp:start v=${sdkVersion} -->`,
|
|
333
|
+
end: "<!-- glasstrace:mcp:end -->"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function hashMarkers(sdkVersion) {
|
|
337
|
+
return {
|
|
338
|
+
start: `# glasstrace:mcp:start v=${sdkVersion}`,
|
|
339
|
+
end: "# glasstrace:mcp:end"
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function generateInfoSection(agent, endpoint, sdkVersion) {
|
|
343
|
+
if (!endpoint || endpoint.trim() === "") {
|
|
344
|
+
throw new Error("endpoint must not be empty");
|
|
345
|
+
}
|
|
346
|
+
if (!sdkVersion || sdkVersion.trim() === "") {
|
|
347
|
+
throw new Error("sdkVersion must not be empty");
|
|
348
|
+
}
|
|
349
|
+
if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
"sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const content = buildAgentInstructionBody();
|
|
355
|
+
switch (agent.name) {
|
|
356
|
+
case "claude":
|
|
357
|
+
case "codex":
|
|
358
|
+
case "gemini":
|
|
359
|
+
case "windsurf":
|
|
360
|
+
case "generic": {
|
|
361
|
+
const m = htmlMarkers(sdkVersion);
|
|
362
|
+
return `${m.start}
|
|
363
|
+
${content}${m.end}
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
case "cursor": {
|
|
367
|
+
const m = htmlMarkers(sdkVersion);
|
|
368
|
+
return `${m.start}
|
|
369
|
+
${content}${m.end}
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
default: {
|
|
373
|
+
const _exhaustive = agent.name;
|
|
374
|
+
throw new Error(`Unknown agent: ${_exhaustive}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function generateInfoSectionForCursorrulesLegacy(endpoint, sdkVersion) {
|
|
379
|
+
if (!endpoint || endpoint.trim() === "") {
|
|
380
|
+
throw new Error("endpoint must not be empty");
|
|
381
|
+
}
|
|
382
|
+
if (!sdkVersion || sdkVersion.trim() === "") {
|
|
383
|
+
throw new Error("sdkVersion must not be empty");
|
|
384
|
+
}
|
|
385
|
+
if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
"sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const content = buildAgentInstructionBody();
|
|
391
|
+
const m = hashMarkers(sdkVersion);
|
|
392
|
+
return `${m.start}
|
|
393
|
+
${content}${m.end}
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
function generateInfoSectionForCursorMdc(endpoint, sdkVersion) {
|
|
397
|
+
if (!endpoint || endpoint.trim() === "") {
|
|
398
|
+
throw new Error("endpoint must not be empty");
|
|
399
|
+
}
|
|
400
|
+
if (!sdkVersion || sdkVersion.trim() === "") {
|
|
401
|
+
throw new Error("sdkVersion must not be empty");
|
|
402
|
+
}
|
|
403
|
+
if (!SDK_VERSION_STAMP_PATTERN.test(sdkVersion)) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
"sdkVersion must match [A-Za-z0-9.+\\-]+ (semver-shaped, no whitespace, no angle brackets)"
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const content = buildAgentInstructionBody();
|
|
409
|
+
const m = htmlMarkers(sdkVersion);
|
|
410
|
+
return [
|
|
411
|
+
"---",
|
|
412
|
+
"description: Glasstrace MCP runtime debugging tools \u2014 runtime evidence the agent reads when source alone cannot resolve a bug",
|
|
413
|
+
"alwaysApply: true",
|
|
414
|
+
"---",
|
|
415
|
+
"",
|
|
416
|
+
`${m.start}
|
|
417
|
+
${content}${m.end}
|
|
418
|
+
`
|
|
419
|
+
].join("\n");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/agent-detection/inject-all-targets.ts
|
|
423
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
424
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
425
|
+
async function injectAllTargets(agents, endpoint, sdkVersion, projectRoot) {
|
|
426
|
+
const writtenAgentsMd = /* @__PURE__ */ new Set();
|
|
427
|
+
for (const agent of agents) {
|
|
428
|
+
const targets = computeTargets(agent, projectRoot);
|
|
429
|
+
for (const target of targets) {
|
|
430
|
+
if (target.isAgentsMdCompanion) {
|
|
431
|
+
if (writtenAgentsMd.has(target.path)) {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
writtenAgentsMd.add(target.path);
|
|
435
|
+
}
|
|
436
|
+
let createContent;
|
|
437
|
+
let managedSectionOnly;
|
|
438
|
+
if (target.kind === "cursor-mdc") {
|
|
439
|
+
createContent = generateInfoSectionForCursorMdc(endpoint, sdkVersion);
|
|
440
|
+
managedSectionOnly = generateInfoSection(agent, endpoint, sdkVersion);
|
|
441
|
+
} else if (target.kind === "cursorrules-legacy") {
|
|
442
|
+
createContent = generateInfoSectionForCursorrulesLegacy(
|
|
443
|
+
endpoint,
|
|
444
|
+
sdkVersion
|
|
445
|
+
);
|
|
446
|
+
managedSectionOnly = createContent;
|
|
447
|
+
} else {
|
|
448
|
+
createContent = generateInfoSection(agent, endpoint, sdkVersion);
|
|
449
|
+
managedSectionOnly = createContent;
|
|
450
|
+
}
|
|
451
|
+
if (managedSectionOnly === "") continue;
|
|
452
|
+
await writeManagedSectionToTarget(
|
|
453
|
+
target.path,
|
|
454
|
+
createContent,
|
|
455
|
+
managedSectionOnly
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function foundDirFromAgent(agent) {
|
|
461
|
+
if (agent.infoFilePath === null) return null;
|
|
462
|
+
switch (agent.name) {
|
|
463
|
+
case "claude":
|
|
464
|
+
case "codex":
|
|
465
|
+
case "gemini":
|
|
466
|
+
case "generic":
|
|
467
|
+
return dirname2(agent.infoFilePath);
|
|
468
|
+
case "cursor":
|
|
469
|
+
return dirname2(dirname2(dirname2(agent.infoFilePath)));
|
|
470
|
+
case "windsurf":
|
|
471
|
+
return dirname2(dirname2(dirname2(agent.infoFilePath)));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function computeTargets(agent, projectRoot) {
|
|
475
|
+
const targets = [];
|
|
476
|
+
const foundDir = foundDirFromAgent(agent) ?? projectRoot;
|
|
477
|
+
switch (agent.name) {
|
|
478
|
+
case "claude": {
|
|
479
|
+
if (agent.infoFilePath) {
|
|
480
|
+
targets.push({
|
|
481
|
+
path: agent.infoFilePath,
|
|
482
|
+
kind: "primary",
|
|
483
|
+
isAgentsMdCompanion: false
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
targets.push({
|
|
487
|
+
path: join2(foundDir, "AGENTS.md"),
|
|
488
|
+
kind: "agents-md-companion",
|
|
489
|
+
isAgentsMdCompanion: true
|
|
490
|
+
});
|
|
491
|
+
return targets;
|
|
492
|
+
}
|
|
493
|
+
case "codex": {
|
|
494
|
+
if (agent.infoFilePath) {
|
|
495
|
+
targets.push({
|
|
496
|
+
path: agent.infoFilePath,
|
|
497
|
+
kind: "primary",
|
|
498
|
+
isAgentsMdCompanion: true
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return targets;
|
|
502
|
+
}
|
|
503
|
+
case "gemini": {
|
|
504
|
+
if (agent.infoFilePath) {
|
|
505
|
+
targets.push({
|
|
506
|
+
path: agent.infoFilePath,
|
|
507
|
+
kind: "primary",
|
|
508
|
+
isAgentsMdCompanion: false
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
targets.push({
|
|
512
|
+
path: join2(foundDir, "AGENTS.md"),
|
|
513
|
+
kind: "agents-md-companion",
|
|
514
|
+
isAgentsMdCompanion: true
|
|
515
|
+
});
|
|
516
|
+
return targets;
|
|
517
|
+
}
|
|
518
|
+
case "cursor": {
|
|
519
|
+
if (agent.infoFilePath) {
|
|
520
|
+
targets.push({
|
|
521
|
+
path: agent.infoFilePath,
|
|
522
|
+
kind: "cursor-mdc",
|
|
523
|
+
isAgentsMdCompanion: false
|
|
524
|
+
});
|
|
525
|
+
targets.push({
|
|
526
|
+
path: join2(foundDir, ".cursorrules"),
|
|
527
|
+
kind: "cursorrules-legacy",
|
|
528
|
+
isAgentsMdCompanion: false
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
targets.push({
|
|
532
|
+
path: join2(foundDir, "AGENTS.md"),
|
|
533
|
+
kind: "agents-md-companion",
|
|
534
|
+
isAgentsMdCompanion: true
|
|
535
|
+
});
|
|
536
|
+
return targets;
|
|
537
|
+
}
|
|
538
|
+
case "windsurf": {
|
|
539
|
+
if (agent.infoFilePath) {
|
|
540
|
+
targets.push({
|
|
541
|
+
path: agent.infoFilePath,
|
|
542
|
+
kind: "primary",
|
|
543
|
+
isAgentsMdCompanion: false
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
targets.push({
|
|
547
|
+
path: join2(foundDir, "AGENTS.md"),
|
|
548
|
+
kind: "agents-md-companion",
|
|
549
|
+
isAgentsMdCompanion: true
|
|
550
|
+
});
|
|
551
|
+
return targets;
|
|
552
|
+
}
|
|
553
|
+
case "generic": {
|
|
554
|
+
if (agent.infoFilePath) {
|
|
555
|
+
targets.push({
|
|
556
|
+
path: agent.infoFilePath,
|
|
557
|
+
kind: "primary",
|
|
558
|
+
isAgentsMdCompanion: true
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return targets;
|
|
562
|
+
}
|
|
563
|
+
default: {
|
|
564
|
+
const _exhaustive = agent.name;
|
|
565
|
+
throw new Error(`Unknown agent: ${_exhaustive}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function writeManagedSectionToTarget(filePath, createContent, managedSectionOnly) {
|
|
570
|
+
let existingContent = null;
|
|
571
|
+
try {
|
|
572
|
+
existingContent = await readFile(filePath, "utf-8");
|
|
573
|
+
} catch (err) {
|
|
574
|
+
const code = err.code;
|
|
575
|
+
if (code !== "ENOENT") {
|
|
576
|
+
emitTargetWarning(filePath, "read", err);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (existingContent === null) {
|
|
581
|
+
try {
|
|
582
|
+
await mkdir(dirname2(filePath), { recursive: true });
|
|
583
|
+
await writeFile(filePath, createContent, "utf-8");
|
|
584
|
+
} catch (err) {
|
|
585
|
+
emitTargetWarning(filePath, "write", err);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const lines = existingContent.split("\n");
|
|
591
|
+
const boundaries = findMarkerBoundaries(lines);
|
|
592
|
+
let newContent;
|
|
593
|
+
if (boundaries !== null) {
|
|
594
|
+
const before = lines.slice(0, boundaries.startIdx);
|
|
595
|
+
const after = lines.slice(boundaries.endIdx + 1);
|
|
596
|
+
const contentWithoutTrailingNewline = managedSectionOnly.endsWith("\n") ? managedSectionOnly.slice(0, -1) : managedSectionOnly;
|
|
597
|
+
newContent = [...before, contentWithoutTrailingNewline, ...after].join(
|
|
598
|
+
"\n"
|
|
599
|
+
);
|
|
600
|
+
} else {
|
|
601
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
602
|
+
newContent = existingContent + separator + managedSectionOnly;
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await writeFile(filePath, newContent, "utf-8");
|
|
606
|
+
} catch (err) {
|
|
607
|
+
emitTargetWarning(filePath, "write", err);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function emitTargetWarning(filePath, op, err) {
|
|
611
|
+
const code = err.code;
|
|
612
|
+
let qualifier;
|
|
613
|
+
switch (code) {
|
|
614
|
+
case "EACCES":
|
|
615
|
+
case "EPERM":
|
|
616
|
+
qualifier = "permission denied";
|
|
617
|
+
break;
|
|
618
|
+
case "EROFS":
|
|
619
|
+
qualifier = "filesystem read-only";
|
|
620
|
+
break;
|
|
621
|
+
case "ENOSPC":
|
|
622
|
+
qualifier = "disk full";
|
|
623
|
+
break;
|
|
624
|
+
case "ENAMETOOLONG":
|
|
625
|
+
qualifier = "path too long";
|
|
626
|
+
break;
|
|
627
|
+
case "ENOTDIR":
|
|
628
|
+
qualifier = "not a directory";
|
|
629
|
+
break;
|
|
630
|
+
case "EISDIR":
|
|
631
|
+
qualifier = "is a directory";
|
|
632
|
+
break;
|
|
633
|
+
default:
|
|
634
|
+
qualifier = "I/O error";
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
process.stderr.write(
|
|
639
|
+
`Warning: cannot ${op} info file ${filePath}: ${qualifier}
|
|
640
|
+
`
|
|
641
|
+
);
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export {
|
|
647
|
+
detectAgents,
|
|
648
|
+
generateMcpConfig,
|
|
649
|
+
injectAllTargets
|
|
650
|
+
};
|
|
651
|
+
//# sourceMappingURL=chunk-KOYZJN6G.js.map
|