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