@inspecto-dev/plugin 0.2.0-alpha.0 → 0.2.0-alpha.2
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 +19 -0
- package/dist/index.cjs +431 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +436 -103
- package/dist/index.js.map +1 -1
- package/dist/legacy/rspack/index.cjs +402 -98
- package/dist/legacy/rspack/index.cjs.map +1 -1
- package/dist/legacy/rspack/index.js +407 -99
- package/dist/legacy/rspack/index.js.map +1 -1
- package/dist/legacy/rspack/loader.cjs +8 -1
- package/dist/legacy/rspack/loader.cjs.map +1 -1
- package/dist/legacy/rspack/loader.js +8 -1
- package/dist/legacy/rspack/loader.js.map +1 -1
- package/dist/legacy/webpack4/index.cjs +977 -0
- package/dist/legacy/webpack4/index.cjs.map +1 -0
- package/dist/legacy/webpack4/index.d.cts +8 -0
- package/dist/legacy/webpack4/index.d.ts +8 -0
- package/dist/legacy/webpack4/index.js +953 -0
- package/dist/legacy/webpack4/index.js.map +1 -0
- package/dist/legacy/webpack4/loader.cjs +347 -0
- package/dist/legacy/webpack4/loader.cjs.map +1 -0
- package/dist/legacy/webpack4/loader.d.cts +3 -0
- package/dist/legacy/webpack4/loader.d.ts +3 -0
- package/dist/legacy/webpack4/loader.js +314 -0
- package/dist/legacy/webpack4/loader.js.map +1 -0
- package/dist/rollup.cjs +431 -102
- package/dist/rollup.cjs.map +1 -1
- package/dist/rollup.js +436 -103
- package/dist/rollup.js.map +1 -1
- package/dist/rspack.cjs +431 -102
- package/dist/rspack.cjs.map +1 -1
- package/dist/rspack.js +436 -103
- package/dist/rspack.js.map +1 -1
- package/dist/vite.cjs +431 -102
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +436 -103
- package/dist/vite.js.map +1 -1
- package/dist/webpack.cjs +431 -102
- package/dist/webpack.cjs.map +1 -1
- package/dist/webpack.js +436 -103
- package/dist/webpack.js.map +1 -1
- package/package.json +19 -3
|
@@ -45,9 +45,11 @@ var import_node_http = __toESM(require("http"), 1);
|
|
|
45
45
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
46
46
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
47
47
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
48
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
48
49
|
var import_node_child_process = require("child_process");
|
|
49
50
|
var import_portfinder = __toESM(require("portfinder"), 1);
|
|
50
51
|
var import_launch_ide = require("launch-ide");
|
|
52
|
+
var import_types2 = require("@inspecto-dev/types");
|
|
51
53
|
|
|
52
54
|
// src/server/snippet.ts
|
|
53
55
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -163,8 +165,71 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
163
165
|
var import_node_os = __toESM(require("os"), 1);
|
|
164
166
|
var import_defu = require("defu");
|
|
165
167
|
var import_types = require("@inspecto-dev/types");
|
|
168
|
+
|
|
169
|
+
// src/utils/logger.ts
|
|
170
|
+
var LOG_LEVELS = {
|
|
171
|
+
silent: 0,
|
|
172
|
+
error: 1,
|
|
173
|
+
warn: 2,
|
|
174
|
+
info: 3
|
|
175
|
+
};
|
|
176
|
+
function isDebugEnabled(namespace) {
|
|
177
|
+
if (typeof process === "undefined" || !process.env) return false;
|
|
178
|
+
const debugEnv = process.env.DEBUG;
|
|
179
|
+
if (!debugEnv) return false;
|
|
180
|
+
const namespaces = debugEnv.split(",").map((s) => s.trim());
|
|
181
|
+
for (const ns of namespaces) {
|
|
182
|
+
if (ns === "*") return true;
|
|
183
|
+
if (ns.endsWith("*")) {
|
|
184
|
+
const prefix = ns.slice(0, -1);
|
|
185
|
+
if (namespace.startsWith(prefix)) return true;
|
|
186
|
+
} else if (ns === namespace) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
var globalLevel = "warn";
|
|
193
|
+
var registeredLoggers = /* @__PURE__ */ new Set();
|
|
194
|
+
function createLogger(namespace, options) {
|
|
195
|
+
let currentLevel = options?.logLevel ?? globalLevel;
|
|
196
|
+
let numericLevel = LOG_LEVELS[currentLevel] ?? 2;
|
|
197
|
+
const debugEnabled = isDebugEnabled(namespace);
|
|
198
|
+
const logger = {
|
|
199
|
+
setLevel(level) {
|
|
200
|
+
currentLevel = level;
|
|
201
|
+
numericLevel = LOG_LEVELS[level] ?? 2;
|
|
202
|
+
},
|
|
203
|
+
info(msg, ...args) {
|
|
204
|
+
if (numericLevel >= LOG_LEVELS.info) {
|
|
205
|
+
console.log(`\x1B[36m[inspecto]\x1B[0m ${msg}`, ...args);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
warn(msg, ...args) {
|
|
209
|
+
if (numericLevel >= LOG_LEVELS.warn) {
|
|
210
|
+
console.warn(`\x1B[33m[inspecto] WARN:\x1B[0m ${msg}`, ...args);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
error(msg, ...args) {
|
|
214
|
+
if (numericLevel >= LOG_LEVELS.error) {
|
|
215
|
+
console.error(`\x1B[31m[inspecto] ERROR:\x1B[0m ${msg}`, ...args);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
debug(msg, ...args) {
|
|
219
|
+
if (debugEnabled) {
|
|
220
|
+
console.log(`\x1B[90m[${namespace}]\x1B[0m ${msg}`, ...args);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
registeredLoggers.add(logger);
|
|
225
|
+
return logger;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/config.ts
|
|
229
|
+
var configLogger = createLogger("inspecto:config");
|
|
166
230
|
var loadedConfig = null;
|
|
167
231
|
var loadedPrompts = null;
|
|
232
|
+
var globalLogLevel = "warn";
|
|
168
233
|
var isWatching = false;
|
|
169
234
|
var arrayReplaceMerge = (0, import_defu.createDefu)((obj, key, val) => {
|
|
170
235
|
if (Array.isArray(val)) {
|
|
@@ -172,6 +237,9 @@ var arrayReplaceMerge = (0, import_defu.createDefu)((obj, key, val) => {
|
|
|
172
237
|
return true;
|
|
173
238
|
}
|
|
174
239
|
});
|
|
240
|
+
function getGlobalLogLevel() {
|
|
241
|
+
return globalLogLevel;
|
|
242
|
+
}
|
|
175
243
|
function resolveConfigRoots(cwd, gitRoot) {
|
|
176
244
|
const roots = [];
|
|
177
245
|
let current = cwd;
|
|
@@ -201,7 +269,7 @@ function loadUserConfigSync(force = false, cwd = process.cwd(), gitRoot) {
|
|
|
201
269
|
layers.push(readJsonSafely(import_node_path.default.join(root, ".inspecto", "settings.json")));
|
|
202
270
|
}
|
|
203
271
|
layers.push(readJsonSafely(import_node_path.default.join(import_node_os.default.homedir(), ".inspecto", "settings.json")));
|
|
204
|
-
layers.push({
|
|
272
|
+
layers.push({});
|
|
205
273
|
const validLayers = layers.filter((l) => l !== null);
|
|
206
274
|
loadedConfig = arrayReplaceMerge(...validLayers);
|
|
207
275
|
return loadedConfig;
|
|
@@ -244,48 +312,145 @@ function readJsonSafely(filePath) {
|
|
|
244
312
|
}
|
|
245
313
|
} catch (e) {
|
|
246
314
|
if (e instanceof SyntaxError) {
|
|
247
|
-
|
|
315
|
+
configLogger.warn(`Failed to parse config at ${filePath}: Invalid JSON`);
|
|
248
316
|
} else {
|
|
249
|
-
|
|
317
|
+
configLogger.warn(`Failed to read config at ${filePath}:`, e);
|
|
250
318
|
}
|
|
251
319
|
}
|
|
252
320
|
return null;
|
|
253
321
|
}
|
|
254
|
-
function resolveTargetTool(config) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return Object.keys(config.providers)[0];
|
|
322
|
+
function resolveTargetTool(config, ide = "vscode") {
|
|
323
|
+
const defaultProvider = config["provider.default"];
|
|
324
|
+
if (defaultProvider) {
|
|
325
|
+
const tool = defaultProvider.split(".")[0];
|
|
326
|
+
return tool;
|
|
260
327
|
}
|
|
261
|
-
return "
|
|
328
|
+
return "copilot";
|
|
262
329
|
}
|
|
263
|
-
function
|
|
330
|
+
function resolveProviderMode(tool, ide, config) {
|
|
264
331
|
let requestedType = void 0;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
requestedType = requestedType ?? import_types.
|
|
272
|
-
const valid = import_types.VALID_MODES[tool] || [import_types.
|
|
332
|
+
const defaultProvider = config["provider.default"];
|
|
333
|
+
if (defaultProvider && defaultProvider.startsWith(`${tool}.`)) {
|
|
334
|
+
const mode = defaultProvider.split(".")[1];
|
|
335
|
+
if (mode === "extension") requestedType = "extension";
|
|
336
|
+
if (mode === "cli") requestedType = "cli";
|
|
337
|
+
}
|
|
338
|
+
requestedType = requestedType ?? import_types.DEFAULT_PROVIDER_MODE[tool];
|
|
339
|
+
const valid = import_types.VALID_MODES[tool] || [import_types.DEFAULT_PROVIDER_MODE[tool]];
|
|
273
340
|
return requestedType && valid.includes(requestedType) ? requestedType : valid[0];
|
|
274
341
|
}
|
|
275
342
|
function extractToolOverrides(ide, config) {
|
|
276
343
|
const result = {};
|
|
277
|
-
if (!config
|
|
278
|
-
for (const [
|
|
279
|
-
if (!
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
344
|
+
if (!config) return result;
|
|
345
|
+
for (const [key, value] of Object.entries(config)) {
|
|
346
|
+
if (!key.startsWith("provider.")) continue;
|
|
347
|
+
if (key === "provider.default") continue;
|
|
348
|
+
const toolIndex = 1;
|
|
349
|
+
const modeIndex = 2;
|
|
350
|
+
const propIndex = 3;
|
|
351
|
+
const parts = key.split(".");
|
|
352
|
+
if (parts.length >= propIndex + 1) {
|
|
353
|
+
const tool = parts[toolIndex];
|
|
354
|
+
const mode = parts[modeIndex];
|
|
355
|
+
const prop = parts[propIndex];
|
|
356
|
+
if (!result[tool]) {
|
|
357
|
+
result[tool] = { type: mode };
|
|
358
|
+
}
|
|
359
|
+
const overrides = result[tool];
|
|
360
|
+
if (prop === "bin") overrides.binaryPath = value;
|
|
361
|
+
if (prop === "args") overrides.args = value;
|
|
362
|
+
if (prop === "cwd") overrides.cwd = value;
|
|
363
|
+
if (prop === "coldStartDelay") overrides.coldStartDelay = value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
function resolveIntents(serverPrompts) {
|
|
369
|
+
const baseMap = /* @__PURE__ */ new Map();
|
|
370
|
+
for (const intent of import_types.DEFAULT_INTENTS) {
|
|
371
|
+
if (intent.id) baseMap.set(intent.id, { ...intent });
|
|
372
|
+
}
|
|
373
|
+
const defaults = () => ensureOpenInEditorLast(Array.from(baseMap.values()));
|
|
374
|
+
if (!serverPrompts) return defaults();
|
|
375
|
+
const isReplace = !Array.isArray(serverPrompts) && typeof serverPrompts === "object" && serverPrompts.$replace === true;
|
|
376
|
+
const promptsArray = Array.isArray(serverPrompts) ? serverPrompts : isReplace ? serverPrompts.items : [];
|
|
377
|
+
if (!promptsArray || promptsArray.length === 0) return defaults();
|
|
378
|
+
if (isReplace) {
|
|
379
|
+
const result = [];
|
|
380
|
+
for (const item of promptsArray) {
|
|
381
|
+
if (typeof item === "string") {
|
|
382
|
+
if (baseMap.has(item)) {
|
|
383
|
+
result.push(baseMap.get(item));
|
|
384
|
+
} else {
|
|
385
|
+
configLogger.warn(
|
|
386
|
+
`Unknown built-in intent id: "${item}". Available: ${[...baseMap.keys()].join(", ")}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
} else if (typeof item === "object") {
|
|
390
|
+
if (!item.id) {
|
|
391
|
+
configLogger.warn('Intent object missing required "id" field, skipping.');
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (item.enabled === false) {
|
|
395
|
+
configLogger.warn(
|
|
396
|
+
`Intent "${item.id}" is listed in $replace but has enabled:false \u2014 it will be excluded.`
|
|
397
|
+
);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (item.isAction && item.id !== "open-in-editor") {
|
|
401
|
+
configLogger.warn(
|
|
402
|
+
`isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
403
|
+
);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
result.push(baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return ensureOpenInEditorLast(result);
|
|
410
|
+
}
|
|
411
|
+
const merged = Array.from(baseMap.values());
|
|
412
|
+
for (const item of promptsArray) {
|
|
413
|
+
if (typeof item === "string") {
|
|
414
|
+
if (!baseMap.has(item)) {
|
|
415
|
+
configLogger.warn(
|
|
416
|
+
`Unknown built-in intent id: "${item}". In append mode, strings have no effect on ordering \u2014 use $replace to control order.`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (typeof item === "object") {
|
|
422
|
+
if (!item.id) {
|
|
423
|
+
configLogger.warn('Intent object missing required "id" field, skipping.');
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (item.isAction && item.id !== "open-in-editor") {
|
|
427
|
+
configLogger.warn(
|
|
428
|
+
`isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
429
|
+
);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const existingIdx = merged.findIndex((i) => i.id === item.id);
|
|
433
|
+
if (existingIdx !== -1) {
|
|
434
|
+
if (item.enabled === false) {
|
|
435
|
+
merged.splice(existingIdx, 1);
|
|
436
|
+
} else {
|
|
437
|
+
merged[existingIdx] = { ...merged[existingIdx], ...item };
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
if (item.enabled !== false) {
|
|
441
|
+
merged.push(item);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
288
445
|
}
|
|
446
|
+
return ensureOpenInEditorLast(merged);
|
|
447
|
+
}
|
|
448
|
+
function ensureOpenInEditorLast(intents) {
|
|
449
|
+
const idx = intents.findIndex((i) => i.id === "open-in-editor");
|
|
450
|
+
if (idx === -1 || idx === intents.length - 1) return intents;
|
|
451
|
+
const result = [...intents];
|
|
452
|
+
const item = result.splice(idx, 1)[0];
|
|
453
|
+
result.push(item);
|
|
289
454
|
return result;
|
|
290
455
|
}
|
|
291
456
|
var watchers = [];
|
|
@@ -322,6 +487,19 @@ function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
|
|
|
322
487
|
}
|
|
323
488
|
|
|
324
489
|
// src/server/index.ts
|
|
490
|
+
var serverLogger = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
|
|
491
|
+
var payloadTickets = /* @__PURE__ */ new Map();
|
|
492
|
+
function createTicket(payload) {
|
|
493
|
+
const ticketId = import_node_crypto.default.randomUUID();
|
|
494
|
+
payloadTickets.set(ticketId, JSON.stringify(payload));
|
|
495
|
+
setTimeout(
|
|
496
|
+
() => {
|
|
497
|
+
payloadTickets.delete(ticketId);
|
|
498
|
+
},
|
|
499
|
+
5 * 60 * 1e3
|
|
500
|
+
);
|
|
501
|
+
return ticketId;
|
|
502
|
+
}
|
|
325
503
|
var serverState = {
|
|
326
504
|
port: null,
|
|
327
505
|
running: false,
|
|
@@ -333,8 +511,11 @@ var serverInstance = null;
|
|
|
333
511
|
function resolveProjectRoot() {
|
|
334
512
|
let gitRoot;
|
|
335
513
|
try {
|
|
514
|
+
serverLogger.info("Resolving project root...");
|
|
336
515
|
gitRoot = (0, import_node_child_process.execSync)("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
337
|
-
|
|
516
|
+
serverLogger.info("Resolved project root: " + gitRoot);
|
|
517
|
+
} catch (e) {
|
|
518
|
+
serverLogger.error("Failed to resolve project root:", e);
|
|
338
519
|
gitRoot = process.cwd();
|
|
339
520
|
}
|
|
340
521
|
let current = gitRoot;
|
|
@@ -356,7 +537,7 @@ function launchURI(uri) {
|
|
|
356
537
|
(0, import_node_child_process.execFileSync)("xdg-open", [uri]);
|
|
357
538
|
}
|
|
358
539
|
} catch (e) {
|
|
359
|
-
|
|
540
|
+
serverLogger.error("Failed to launch URI via execFileSync, falling back to launchIDE:", e);
|
|
360
541
|
(0, import_launch_ide.launchIDE)({ file: uri });
|
|
361
542
|
}
|
|
362
543
|
}
|
|
@@ -371,7 +552,7 @@ async function startServer() {
|
|
|
371
552
|
const port = await import_portfinder.default.getPortPromise();
|
|
372
553
|
watchConfig(
|
|
373
554
|
() => {
|
|
374
|
-
|
|
555
|
+
serverLogger.info("user config reloaded.");
|
|
375
556
|
},
|
|
376
557
|
serverState.cwd,
|
|
377
558
|
serverState.configRoot
|
|
@@ -387,7 +568,7 @@ async function startServer() {
|
|
|
387
568
|
}
|
|
388
569
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
389
570
|
handleRequest(url, req, res).catch((err) => {
|
|
390
|
-
|
|
571
|
+
serverLogger.error("server error:", err);
|
|
391
572
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
392
573
|
res.end(JSON.stringify({ success: false, error: String(err) }));
|
|
393
574
|
});
|
|
@@ -400,22 +581,41 @@ async function startServer() {
|
|
|
400
581
|
serverInstance.once("error", reject);
|
|
401
582
|
});
|
|
402
583
|
serverInstance.on("error", (err) => {
|
|
403
|
-
|
|
584
|
+
serverLogger.error("persistent server error:", err);
|
|
404
585
|
});
|
|
405
586
|
serverState.port = port;
|
|
406
587
|
serverState.running = true;
|
|
407
|
-
const portFile = import_node_path2.default.join(import_node_os2.default.tmpdir(), "inspecto.port");
|
|
588
|
+
const portFile = import_node_path2.default.join(import_node_os2.default.tmpdir(), "inspecto.port.json");
|
|
408
589
|
try {
|
|
409
|
-
|
|
410
|
-
|
|
590
|
+
let portData = {};
|
|
591
|
+
if (import_node_fs2.default.existsSync(portFile)) {
|
|
592
|
+
try {
|
|
593
|
+
portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
|
|
594
|
+
} catch (e) {
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
|
|
598
|
+
portData[rootHash] = port;
|
|
599
|
+
import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
|
|
600
|
+
} catch (e) {
|
|
601
|
+
serverLogger.warn("Failed to write port file:", e);
|
|
411
602
|
}
|
|
412
603
|
process.once("exit", () => {
|
|
413
604
|
try {
|
|
414
|
-
import_node_fs2.default.
|
|
605
|
+
if (import_node_fs2.default.existsSync(portFile)) {
|
|
606
|
+
const portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
|
|
607
|
+
const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
|
|
608
|
+
delete portData[rootHash];
|
|
609
|
+
if (Object.keys(portData).length === 0) {
|
|
610
|
+
import_node_fs2.default.unlinkSync(portFile);
|
|
611
|
+
} else {
|
|
612
|
+
import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
|
|
613
|
+
}
|
|
614
|
+
}
|
|
415
615
|
} catch {
|
|
416
616
|
}
|
|
417
617
|
});
|
|
418
|
-
|
|
618
|
+
serverLogger.info(`server running at http://127.0.0.1:${port}`);
|
|
419
619
|
return port;
|
|
420
620
|
}
|
|
421
621
|
async function readBody(req) {
|
|
@@ -428,66 +628,63 @@ async function readBody(req) {
|
|
|
428
628
|
}
|
|
429
629
|
async function handleRequest(url, req, res) {
|
|
430
630
|
const pathname = url.pathname;
|
|
431
|
-
if (pathname === "/health" && req.method === "GET") {
|
|
631
|
+
if ((pathname === "/health" || pathname === import_types2.INSPECTO_API_PATHS.HEALTH) && req.method === "GET") {
|
|
432
632
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
433
633
|
res.end(JSON.stringify({ ok: true, port: serverState.port }));
|
|
434
634
|
return;
|
|
435
635
|
}
|
|
436
|
-
if (pathname ===
|
|
636
|
+
if (pathname === import_types2.INSPECTO_API_PATHS.CLIENT_CONFIG && req.method === "GET") {
|
|
437
637
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
438
638
|
const promptsConfig = await loadPromptsConfig(false, serverState.cwd, serverState.configRoot);
|
|
439
639
|
const effectiveIde = userConfig.ide ?? "vscode";
|
|
440
640
|
let info;
|
|
441
641
|
if (!serverState.ideInfo) {
|
|
442
|
-
const fallbackTargets = userConfig.providers ? Object.keys(userConfig.providers) : ["claude-code", "gemini", "coco", "codex"];
|
|
443
642
|
info = {
|
|
444
|
-
ide: effectiveIde
|
|
445
|
-
providers: fallbackTargets.reduce(
|
|
446
|
-
(acc, target) => {
|
|
447
|
-
acc[target] = {
|
|
448
|
-
mode: resolveToolMode(target, effectiveIde, userConfig),
|
|
449
|
-
installed: false
|
|
450
|
-
};
|
|
451
|
-
return acc;
|
|
452
|
-
},
|
|
453
|
-
{}
|
|
454
|
-
)
|
|
643
|
+
ide: effectiveIde
|
|
455
644
|
};
|
|
456
645
|
} else {
|
|
457
646
|
const { scheme: _scheme, ...rest } = serverState.ideInfo;
|
|
458
647
|
info = rest;
|
|
459
648
|
}
|
|
460
|
-
const resolvedProviders = { ...info.providers };
|
|
461
|
-
for (const tool in resolvedProviders) {
|
|
462
|
-
resolvedProviders[tool].mode = resolveToolMode(tool, info.ide, userConfig);
|
|
463
|
-
}
|
|
464
649
|
const config = {
|
|
465
650
|
...info,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
651
|
+
prompts: resolveIntents(promptsConfig),
|
|
652
|
+
hotKeys: userConfig["inspector.hotKey"] ?? "alt",
|
|
653
|
+
theme: userConfig["inspector.theme"] ?? "auto",
|
|
654
|
+
includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
|
|
655
|
+
autoSend: userConfig["prompt.autoSend"] ?? false
|
|
471
656
|
};
|
|
657
|
+
delete config.providers;
|
|
472
658
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
473
659
|
res.end(JSON.stringify(config));
|
|
474
660
|
return;
|
|
475
661
|
}
|
|
476
|
-
if (pathname ===
|
|
662
|
+
if (pathname === import_types2.INSPECTO_API_PATHS.IDE_INFO && req.method === "POST") {
|
|
477
663
|
try {
|
|
478
664
|
const body = JSON.parse(await readBody(req));
|
|
479
|
-
|
|
480
|
-
|
|
665
|
+
const ideWorkspace = body.workspaceRoot || "";
|
|
666
|
+
const serverProjectRoot = serverState.projectRoot || "";
|
|
667
|
+
const isSameProject = !ideWorkspace || !serverProjectRoot || ideWorkspace === serverProjectRoot || serverProjectRoot.startsWith(ideWorkspace);
|
|
668
|
+
if (isSameProject) {
|
|
669
|
+
serverState.ideInfo = body;
|
|
670
|
+
serverLogger.debug(
|
|
671
|
+
`Accepted IDE info from matched workspace (ide-${body.ide} / schema-${body.scheme})`
|
|
672
|
+
);
|
|
673
|
+
} else {
|
|
674
|
+
serverLogger.debug(
|
|
675
|
+
`Ignored IDE info from unrelated workspace (IDE Workspace: ${ideWorkspace}, Server: ${serverProjectRoot}, Scheme: ${body.scheme}, IDE: ${body.ide})`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
481
678
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
482
679
|
res.end(JSON.stringify({ success: true }));
|
|
483
680
|
} catch (e) {
|
|
484
|
-
|
|
681
|
+
serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.IDE_INFO} POST request:`, e);
|
|
485
682
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
486
683
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
487
684
|
}
|
|
488
685
|
return;
|
|
489
686
|
}
|
|
490
|
-
if (pathname ===
|
|
687
|
+
if (pathname === import_types2.INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
|
|
491
688
|
let body;
|
|
492
689
|
try {
|
|
493
690
|
body = JSON.parse(await readBody(req));
|
|
@@ -496,28 +693,98 @@ async function handleRequest(url, req, res) {
|
|
|
496
693
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
497
694
|
return;
|
|
498
695
|
}
|
|
499
|
-
const absolutePath = import_node_path2.default.isAbsolute(body.file) ? body.file : import_node_path2.default.resolve(serverState.cwd, body.file);
|
|
696
|
+
const absolutePath = import_node_path2.default.isAbsolute(body.file) ? import_node_path2.default.resolve(body.file) : import_node_path2.default.resolve(serverState.cwd, body.file);
|
|
697
|
+
const relativeToRoot = import_node_path2.default.relative(serverState.projectRoot, absolutePath);
|
|
698
|
+
if (relativeToRoot.startsWith("..") || import_node_path2.default.isAbsolute(relativeToRoot)) {
|
|
699
|
+
serverLogger.warn(`Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}`);
|
|
700
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
701
|
+
res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
500
704
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
705
|
+
const configuredIde = userConfig.ide;
|
|
706
|
+
const activeIde = serverState.ideInfo?.ide;
|
|
707
|
+
const activeIdeScheme = serverState.ideInfo?.scheme;
|
|
708
|
+
const rawEditorHint = configuredIde || activeIde || activeIdeScheme || "code";
|
|
709
|
+
if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
|
|
710
|
+
serverLogger.warn(
|
|
711
|
+
`Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
let editorHint = rawEditorHint;
|
|
715
|
+
if (rawEditorHint === "vscode") editorHint = "code";
|
|
716
|
+
else if (rawEditorHint === "vscode-insiders") editorHint = "code-insiders";
|
|
717
|
+
else if (rawEditorHint === "vscodium") editorHint = "codium";
|
|
718
|
+
else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
|
|
719
|
+
serverLogger.debug(
|
|
720
|
+
`IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
|
|
721
|
+
);
|
|
722
|
+
const VSCODE_FAMILY_SCHEMES = [
|
|
723
|
+
"vscode",
|
|
724
|
+
"vscode-insiders",
|
|
725
|
+
"cursor",
|
|
726
|
+
"windsurf",
|
|
727
|
+
"trae",
|
|
728
|
+
"trae-cn",
|
|
729
|
+
"vscodium",
|
|
730
|
+
"codebuddy",
|
|
731
|
+
"codebuddy-cn",
|
|
732
|
+
"antigravity"
|
|
733
|
+
];
|
|
734
|
+
if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
|
|
735
|
+
const uri = `${rawEditorHint}://file${absolutePath}:${body.line}:${body.column}`;
|
|
736
|
+
serverLogger.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
|
|
737
|
+
try {
|
|
738
|
+
if (process.platform === "darwin") {
|
|
739
|
+
(0, import_node_child_process.execFileSync)("open", [uri]);
|
|
740
|
+
} else if (process.platform === "win32") {
|
|
741
|
+
(0, import_node_child_process.execFileSync)("cmd", ["/c", "start", '""', uri]);
|
|
742
|
+
} else {
|
|
743
|
+
(0, import_node_child_process.execFileSync)("xdg-open", [uri]);
|
|
744
|
+
}
|
|
745
|
+
} catch (e) {
|
|
746
|
+
serverLogger.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
|
|
747
|
+
(0, import_launch_ide.launchIDE)({
|
|
748
|
+
file: absolutePath,
|
|
749
|
+
line: body.line,
|
|
750
|
+
column: body.column,
|
|
751
|
+
editor: editorHint,
|
|
752
|
+
type: process.platform === "darwin" ? "open" : "exec"
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
} else {
|
|
756
|
+
(0, import_launch_ide.launchIDE)({
|
|
757
|
+
file: absolutePath,
|
|
758
|
+
line: body.line,
|
|
759
|
+
column: body.column,
|
|
760
|
+
editor: editorHint,
|
|
761
|
+
type: process.platform === "darwin" ? "open" : "exec"
|
|
762
|
+
});
|
|
763
|
+
}
|
|
510
764
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
511
765
|
res.end(JSON.stringify({ success: true }));
|
|
512
766
|
return;
|
|
513
767
|
}
|
|
514
|
-
if (pathname ===
|
|
768
|
+
if (pathname === import_types2.INSPECTO_API_PATHS.PROJECT_SNIPPET && req.method === "GET") {
|
|
515
769
|
const file = url.searchParams.get("file") ?? "";
|
|
516
770
|
const line = parseInt(url.searchParams.get("line") ?? "1", 10);
|
|
517
771
|
const column = parseInt(url.searchParams.get("column") ?? "1", 10);
|
|
518
772
|
const maxLines = parseInt(url.searchParams.get("maxLines") ?? "100", 10);
|
|
519
773
|
try {
|
|
520
|
-
const absolutePath = import_node_path2.default.isAbsolute(file) ? file : import_node_path2.default.resolve(serverState.cwd, file);
|
|
774
|
+
const absolutePath = import_node_path2.default.isAbsolute(file) ? import_node_path2.default.resolve(file) : import_node_path2.default.resolve(serverState.cwd, file);
|
|
775
|
+
const relativeToRoot = import_node_path2.default.relative(serverState.projectRoot, absolutePath);
|
|
776
|
+
if (relativeToRoot.startsWith("..") || import_node_path2.default.isAbsolute(relativeToRoot)) {
|
|
777
|
+
serverLogger.warn(`Security: Blocked path traversal attempt in PROJECT_SNIPPET: ${file}`);
|
|
778
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
779
|
+
res.end(
|
|
780
|
+
JSON.stringify({
|
|
781
|
+
success: false,
|
|
782
|
+
error: "Access denied: File is outside of project workspace",
|
|
783
|
+
errorCode: "FORBIDDEN"
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
521
788
|
const result = await extractSnippet({ file: absolutePath, line, column, maxLines });
|
|
522
789
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
523
790
|
res.end(JSON.stringify(result));
|
|
@@ -529,7 +796,7 @@ async function handleRequest(url, req, res) {
|
|
|
529
796
|
}
|
|
530
797
|
return;
|
|
531
798
|
}
|
|
532
|
-
if (pathname ===
|
|
799
|
+
if (pathname === import_types2.INSPECTO_API_PATHS.AI_DISPATCH && req.method === "POST") {
|
|
533
800
|
try {
|
|
534
801
|
const rawBody = await readBody(req);
|
|
535
802
|
const body = JSON.parse(rawBody);
|
|
@@ -537,19 +804,30 @@ async function handleRequest(url, req, res) {
|
|
|
537
804
|
res.writeHead(result.success ? 200 : 500, { "Content-Type": "application/json" });
|
|
538
805
|
res.end(JSON.stringify(result));
|
|
539
806
|
} catch (e) {
|
|
540
|
-
|
|
807
|
+
serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.AI_DISPATCH} request:`, e);
|
|
541
808
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
542
809
|
res.end(JSON.stringify({ success: false, error: String(e), errorCode: "INTERNAL_ERROR" }));
|
|
543
810
|
}
|
|
544
811
|
return;
|
|
545
812
|
}
|
|
813
|
+
if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
|
|
814
|
+
const ticketId = pathname.substring(import_types2.INSPECTO_API_PATHS.AI_TICKET.length + 1);
|
|
815
|
+
const payloadStr = payloadTickets.get(ticketId);
|
|
816
|
+
if (!payloadStr) {
|
|
817
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
818
|
+
res.end(JSON.stringify({ success: false, error: "Ticket not found or expired" }));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
822
|
+
res.end(payloadStr);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
546
825
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
547
826
|
res.end(JSON.stringify({ error: "not found" }));
|
|
548
827
|
}
|
|
549
828
|
async function dispatchToAi(req) {
|
|
550
829
|
const { location, snippet, prompt } = req;
|
|
551
830
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
552
|
-
const ide = userConfig.ide ?? "vscode";
|
|
553
831
|
const resolvedTarget = resolveTargetTool(userConfig);
|
|
554
832
|
const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
|
|
555
833
|
|
|
@@ -557,22 +835,45 @@ async function dispatchToAi(req) {
|
|
|
557
835
|
${snippet}
|
|
558
836
|
\`\`\`
|
|
559
837
|
`;
|
|
838
|
+
const ideReportedMode = serverState.ideInfo?.providers[resolvedTarget]?.mode;
|
|
839
|
+
const configuredIde = userConfig.ide;
|
|
840
|
+
const activeIde = serverState.ideInfo?.ide;
|
|
841
|
+
const activeIdeScheme = serverState.ideInfo?.scheme;
|
|
842
|
+
const finalIde = configuredIde || activeIdeScheme || activeIde || "vscode";
|
|
843
|
+
if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
|
|
844
|
+
serverLogger.warn(
|
|
845
|
+
`dispatchToAi: Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const mode = resolveProviderMode(resolvedTarget, finalIde, userConfig);
|
|
849
|
+
const overrides = extractToolOverrides(finalIde, userConfig)[resolvedTarget] || {};
|
|
850
|
+
overrides.type = mode;
|
|
851
|
+
const fullPayload = {
|
|
852
|
+
ide: finalIde,
|
|
853
|
+
target: resolvedTarget,
|
|
854
|
+
targetType: mode,
|
|
855
|
+
prompt: formattedPrompt,
|
|
856
|
+
filePath: location.file,
|
|
857
|
+
line: location.line,
|
|
858
|
+
column: location.column,
|
|
859
|
+
snippet,
|
|
860
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : void 0,
|
|
861
|
+
autoSend: userConfig["prompt.autoSend"] !== void 0 ? Boolean(userConfig["prompt.autoSend"]) : void 0
|
|
862
|
+
};
|
|
863
|
+
const ticketId = createTicket(fullPayload);
|
|
560
864
|
const params = new URLSearchParams();
|
|
865
|
+
params.set("ticket", ticketId);
|
|
561
866
|
params.set("target", resolvedTarget);
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
params.set("overrides", JSON.stringify(overrides));
|
|
565
|
-
}
|
|
566
|
-
params.set("prompt", formattedPrompt);
|
|
567
|
-
params.set("file", location.file);
|
|
568
|
-
params.set("line", String(location.line));
|
|
569
|
-
params.set("col", String(location.column));
|
|
570
|
-
params.set("snippet", snippet);
|
|
571
|
-
const scheme = serverState.ideInfo?.scheme || "vscode";
|
|
572
|
-
const uri = `${scheme}://inspecto.inspecto/send?${params.toString()}`;
|
|
573
|
-
console.log(`[inspecto] dispatchToAi: Generated URI: ${uri}`);
|
|
867
|
+
const uri = `${finalIde}://inspecto.inspecto/send?${params.toString()}`;
|
|
868
|
+
serverLogger.debug(`dispatchToAi: Generated URI: ${uri}`);
|
|
574
869
|
launchURI(uri);
|
|
575
|
-
return {
|
|
870
|
+
return {
|
|
871
|
+
success: true,
|
|
872
|
+
fallbackPayload: {
|
|
873
|
+
prompt: formattedPrompt,
|
|
874
|
+
file: location.file
|
|
875
|
+
}
|
|
876
|
+
};
|
|
576
877
|
}
|
|
577
878
|
|
|
578
879
|
// src/injectors/utils.ts
|
|
@@ -584,7 +885,9 @@ var resolveClientModule = () => {
|
|
|
584
885
|
try {
|
|
585
886
|
return require.resolve("@inspecto-dev/core");
|
|
586
887
|
} catch {
|
|
587
|
-
console.warn(
|
|
888
|
+
console.warn(
|
|
889
|
+
"[inspecto] Could not resolve @inspecto-dev/core \u2014 falling back to bare specifier"
|
|
890
|
+
);
|
|
588
891
|
return "@inspecto-dev/core";
|
|
589
892
|
}
|
|
590
893
|
}
|
|
@@ -612,7 +915,8 @@ var DEFAULT_OPTIONS = {
|
|
|
612
915
|
exclude: [],
|
|
613
916
|
escapeTags: [],
|
|
614
917
|
pathType: "absolute",
|
|
615
|
-
attributeName: "data-inspecto"
|
|
918
|
+
attributeName: "data-inspecto",
|
|
919
|
+
logLevel: "warn"
|
|
616
920
|
};
|
|
617
921
|
var serverPort = null;
|
|
618
922
|
var ensureServer = async () => {
|