@inspecto-dev/plugin 0.2.0-alpha.1 → 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 +428 -101
- 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 +433 -102
- package/dist/index.js.map +1 -1
- package/dist/legacy/rspack/index.cjs +399 -97
- package/dist/legacy/rspack/index.cjs.map +1 -1
- package/dist/legacy/rspack/index.js +404 -98
- 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 +428 -101
- package/dist/rollup.cjs.map +1 -1
- package/dist/rollup.js +433 -102
- package/dist/rollup.js.map +1 -1
- package/dist/rspack.cjs +428 -101
- package/dist/rspack.cjs.map +1 -1
- package/dist/rspack.js +433 -102
- package/dist/rspack.js.map +1 -1
- package/dist/vite.cjs +428 -101
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +433 -102
- package/dist/vite.js.map +1 -1
- package/dist/webpack.cjs +428 -101
- package/dist/webpack.cjs.map +1 -1
- package/dist/webpack.js +433 -102
- package/dist/webpack.js.map +1 -1
- package/package.json +19 -3
package/dist/rollup.js
CHANGED
|
@@ -41,6 +41,10 @@ function shouldTransform(filePath, options) {
|
|
|
41
41
|
if (filePath.includes("node_modules")) return false;
|
|
42
42
|
if (filePath.startsWith("\0")) return false;
|
|
43
43
|
if (/[/\\](dist|build|\.next|\.nuxt)[/\\]/.test(filePath)) return false;
|
|
44
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
45
|
+
if (ext && !["js", "jsx", "ts", "tsx", "mjs", "mts", "vue"].includes(ext)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
44
48
|
return true;
|
|
45
49
|
}
|
|
46
50
|
function buildEscapeTagsSet(escapeTags) {
|
|
@@ -205,7 +209,10 @@ function transformVue(options) {
|
|
|
205
209
|
const loc = node.loc;
|
|
206
210
|
if (!loc) return;
|
|
207
211
|
const { line, column } = loc.start;
|
|
208
|
-
const
|
|
212
|
+
const templateStartLoc = descriptor.template.loc.start;
|
|
213
|
+
const absoluteLine = templateStartLoc.line + line - 1;
|
|
214
|
+
const absoluteColumn = line === 1 ? templateStartLoc.column + column - 1 : column;
|
|
215
|
+
const attrValue = formatAttrValue(normalizedPath, absoluteLine, absoluteColumn);
|
|
209
216
|
const tagNameEnd = loc.start.offset + tagName.length + 1;
|
|
210
217
|
const absoluteOffset = templateBlockStart + tagNameEnd;
|
|
211
218
|
ms.appendLeft(absoluteOffset, ` ${attributeName}="${attrValue}"`);
|
|
@@ -265,9 +272,11 @@ import http from "http";
|
|
|
265
272
|
import fs3 from "fs";
|
|
266
273
|
import path6 from "path";
|
|
267
274
|
import os2 from "os";
|
|
275
|
+
import crypto from "crypto";
|
|
268
276
|
import { execSync, execFileSync } from "child_process";
|
|
269
277
|
import portfinder from "portfinder";
|
|
270
278
|
import { launchIDE } from "launch-ide";
|
|
279
|
+
import { INSPECTO_API_PATHS } from "@inspecto-dev/types";
|
|
271
280
|
|
|
272
281
|
// src/server/snippet.ts
|
|
273
282
|
import * as fs from "fs";
|
|
@@ -382,9 +391,84 @@ import fs2 from "fs";
|
|
|
382
391
|
import path5 from "path";
|
|
383
392
|
import os from "os";
|
|
384
393
|
import { createDefu } from "defu";
|
|
385
|
-
import {
|
|
394
|
+
import {
|
|
395
|
+
DEFAULT_PROVIDER_MODE,
|
|
396
|
+
VALID_MODES,
|
|
397
|
+
DEFAULT_INTENTS
|
|
398
|
+
} from "@inspecto-dev/types";
|
|
399
|
+
|
|
400
|
+
// src/utils/logger.ts
|
|
401
|
+
var LOG_LEVELS = {
|
|
402
|
+
silent: 0,
|
|
403
|
+
error: 1,
|
|
404
|
+
warn: 2,
|
|
405
|
+
info: 3
|
|
406
|
+
};
|
|
407
|
+
function isDebugEnabled(namespace) {
|
|
408
|
+
if (typeof process === "undefined" || !process.env) return false;
|
|
409
|
+
const debugEnv = process.env.DEBUG;
|
|
410
|
+
if (!debugEnv) return false;
|
|
411
|
+
const namespaces = debugEnv.split(",").map((s) => s.trim());
|
|
412
|
+
for (const ns of namespaces) {
|
|
413
|
+
if (ns === "*") return true;
|
|
414
|
+
if (ns.endsWith("*")) {
|
|
415
|
+
const prefix = ns.slice(0, -1);
|
|
416
|
+
if (namespace.startsWith(prefix)) return true;
|
|
417
|
+
} else if (ns === namespace) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
var globalLevel = "warn";
|
|
424
|
+
var registeredLoggers = /* @__PURE__ */ new Set();
|
|
425
|
+
function setLoggerGlobalLevel(level) {
|
|
426
|
+
globalLevel = level;
|
|
427
|
+
for (const logger of registeredLoggers) {
|
|
428
|
+
if (logger.setLevel) {
|
|
429
|
+
logger.setLevel(level);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function createLogger(namespace, options) {
|
|
434
|
+
let currentLevel = options?.logLevel ?? globalLevel;
|
|
435
|
+
let numericLevel = LOG_LEVELS[currentLevel] ?? 2;
|
|
436
|
+
const debugEnabled = isDebugEnabled(namespace);
|
|
437
|
+
const logger = {
|
|
438
|
+
setLevel(level) {
|
|
439
|
+
currentLevel = level;
|
|
440
|
+
numericLevel = LOG_LEVELS[level] ?? 2;
|
|
441
|
+
},
|
|
442
|
+
info(msg, ...args) {
|
|
443
|
+
if (numericLevel >= LOG_LEVELS.info) {
|
|
444
|
+
console.log(`\x1B[36m[inspecto]\x1B[0m ${msg}`, ...args);
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
warn(msg, ...args) {
|
|
448
|
+
if (numericLevel >= LOG_LEVELS.warn) {
|
|
449
|
+
console.warn(`\x1B[33m[inspecto] WARN:\x1B[0m ${msg}`, ...args);
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
error(msg, ...args) {
|
|
453
|
+
if (numericLevel >= LOG_LEVELS.error) {
|
|
454
|
+
console.error(`\x1B[31m[inspecto] ERROR:\x1B[0m ${msg}`, ...args);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
debug(msg, ...args) {
|
|
458
|
+
if (debugEnabled) {
|
|
459
|
+
console.log(`\x1B[90m[${namespace}]\x1B[0m ${msg}`, ...args);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
registeredLoggers.add(logger);
|
|
464
|
+
return logger;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/config.ts
|
|
468
|
+
var configLogger = createLogger("inspecto:config");
|
|
386
469
|
var loadedConfig = null;
|
|
387
470
|
var loadedPrompts = null;
|
|
471
|
+
var globalLogLevel = "warn";
|
|
388
472
|
var isWatching = false;
|
|
389
473
|
var arrayReplaceMerge = createDefu((obj, key, val) => {
|
|
390
474
|
if (Array.isArray(val)) {
|
|
@@ -392,6 +476,15 @@ var arrayReplaceMerge = createDefu((obj, key, val) => {
|
|
|
392
476
|
return true;
|
|
393
477
|
}
|
|
394
478
|
});
|
|
479
|
+
function setGlobalLogLevel(level) {
|
|
480
|
+
if (level) {
|
|
481
|
+
globalLogLevel = level;
|
|
482
|
+
setLoggerGlobalLevel(level);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function getGlobalLogLevel() {
|
|
486
|
+
return globalLogLevel;
|
|
487
|
+
}
|
|
395
488
|
function resolveConfigRoots(cwd, gitRoot) {
|
|
396
489
|
const roots = [];
|
|
397
490
|
let current = cwd;
|
|
@@ -421,7 +514,7 @@ function loadUserConfigSync(force = false, cwd = process.cwd(), gitRoot) {
|
|
|
421
514
|
layers.push(readJsonSafely(path5.join(root, ".inspecto", "settings.json")));
|
|
422
515
|
}
|
|
423
516
|
layers.push(readJsonSafely(path5.join(os.homedir(), ".inspecto", "settings.json")));
|
|
424
|
-
layers.push({
|
|
517
|
+
layers.push({});
|
|
425
518
|
const validLayers = layers.filter((l) => l !== null);
|
|
426
519
|
loadedConfig = arrayReplaceMerge(...validLayers);
|
|
427
520
|
return loadedConfig;
|
|
@@ -464,50 +557,147 @@ function readJsonSafely(filePath) {
|
|
|
464
557
|
}
|
|
465
558
|
} catch (e) {
|
|
466
559
|
if (e instanceof SyntaxError) {
|
|
467
|
-
|
|
560
|
+
configLogger.warn(`Failed to parse config at ${filePath}: Invalid JSON`);
|
|
468
561
|
} else {
|
|
469
|
-
|
|
562
|
+
configLogger.warn(`Failed to read config at ${filePath}:`, e);
|
|
470
563
|
}
|
|
471
564
|
}
|
|
472
565
|
return null;
|
|
473
566
|
}
|
|
474
|
-
function resolveTargetTool(config) {
|
|
475
|
-
|
|
476
|
-
|
|
567
|
+
function resolveTargetTool(config, ide = "vscode") {
|
|
568
|
+
const defaultProvider = config["provider.default"];
|
|
569
|
+
if (defaultProvider) {
|
|
570
|
+
const tool = defaultProvider.split(".")[0];
|
|
571
|
+
return tool;
|
|
477
572
|
}
|
|
478
|
-
|
|
479
|
-
return Object.keys(config.providers)[0];
|
|
480
|
-
}
|
|
481
|
-
return "github-copilot";
|
|
573
|
+
return "copilot";
|
|
482
574
|
}
|
|
483
|
-
function
|
|
575
|
+
function resolveProviderMode(tool, ide, config) {
|
|
484
576
|
let requestedType = void 0;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
577
|
+
const defaultProvider = config["provider.default"];
|
|
578
|
+
if (defaultProvider && defaultProvider.startsWith(`${tool}.`)) {
|
|
579
|
+
const mode = defaultProvider.split(".")[1];
|
|
580
|
+
if (mode === "extension") requestedType = "extension";
|
|
581
|
+
if (mode === "cli") requestedType = "cli";
|
|
490
582
|
}
|
|
491
|
-
requestedType = requestedType ??
|
|
492
|
-
const valid = VALID_MODES[tool] || [
|
|
583
|
+
requestedType = requestedType ?? DEFAULT_PROVIDER_MODE[tool];
|
|
584
|
+
const valid = VALID_MODES[tool] || [DEFAULT_PROVIDER_MODE[tool]];
|
|
493
585
|
return requestedType && valid.includes(requestedType) ? requestedType : valid[0];
|
|
494
586
|
}
|
|
495
587
|
function extractToolOverrides(ide, config) {
|
|
496
588
|
const result = {};
|
|
497
|
-
if (!config
|
|
498
|
-
for (const [
|
|
499
|
-
if (!
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
589
|
+
if (!config) return result;
|
|
590
|
+
for (const [key, value] of Object.entries(config)) {
|
|
591
|
+
if (!key.startsWith("provider.")) continue;
|
|
592
|
+
if (key === "provider.default") continue;
|
|
593
|
+
const toolIndex = 1;
|
|
594
|
+
const modeIndex = 2;
|
|
595
|
+
const propIndex = 3;
|
|
596
|
+
const parts = key.split(".");
|
|
597
|
+
if (parts.length >= propIndex + 1) {
|
|
598
|
+
const tool = parts[toolIndex];
|
|
599
|
+
const mode = parts[modeIndex];
|
|
600
|
+
const prop = parts[propIndex];
|
|
601
|
+
if (!result[tool]) {
|
|
602
|
+
result[tool] = { type: mode };
|
|
603
|
+
}
|
|
604
|
+
const overrides = result[tool];
|
|
605
|
+
if (prop === "bin") overrides.binaryPath = value;
|
|
606
|
+
if (prop === "args") overrides.args = value;
|
|
607
|
+
if (prop === "cwd") overrides.cwd = value;
|
|
608
|
+
if (prop === "coldStartDelay") overrides.coldStartDelay = value;
|
|
609
|
+
}
|
|
508
610
|
}
|
|
509
611
|
return result;
|
|
510
612
|
}
|
|
613
|
+
function resolveIntents(serverPrompts) {
|
|
614
|
+
const baseMap = /* @__PURE__ */ new Map();
|
|
615
|
+
for (const intent of DEFAULT_INTENTS) {
|
|
616
|
+
if (intent.id) baseMap.set(intent.id, { ...intent });
|
|
617
|
+
}
|
|
618
|
+
const defaults = () => ensureOpenInEditorLast(Array.from(baseMap.values()));
|
|
619
|
+
if (!serverPrompts) return defaults();
|
|
620
|
+
const isReplace = !Array.isArray(serverPrompts) && typeof serverPrompts === "object" && serverPrompts.$replace === true;
|
|
621
|
+
const promptsArray = Array.isArray(serverPrompts) ? serverPrompts : isReplace ? serverPrompts.items : [];
|
|
622
|
+
if (!promptsArray || promptsArray.length === 0) return defaults();
|
|
623
|
+
if (isReplace) {
|
|
624
|
+
const result = [];
|
|
625
|
+
for (const item of promptsArray) {
|
|
626
|
+
if (typeof item === "string") {
|
|
627
|
+
if (baseMap.has(item)) {
|
|
628
|
+
result.push(baseMap.get(item));
|
|
629
|
+
} else {
|
|
630
|
+
configLogger.warn(
|
|
631
|
+
`Unknown built-in intent id: "${item}". Available: ${[...baseMap.keys()].join(", ")}`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
} else if (typeof item === "object") {
|
|
635
|
+
if (!item.id) {
|
|
636
|
+
configLogger.warn('Intent object missing required "id" field, skipping.');
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (item.enabled === false) {
|
|
640
|
+
configLogger.warn(
|
|
641
|
+
`Intent "${item.id}" is listed in $replace but has enabled:false \u2014 it will be excluded.`
|
|
642
|
+
);
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (item.isAction && item.id !== "open-in-editor") {
|
|
646
|
+
configLogger.warn(
|
|
647
|
+
`isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
648
|
+
);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
result.push(baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return ensureOpenInEditorLast(result);
|
|
655
|
+
}
|
|
656
|
+
const merged = Array.from(baseMap.values());
|
|
657
|
+
for (const item of promptsArray) {
|
|
658
|
+
if (typeof item === "string") {
|
|
659
|
+
if (!baseMap.has(item)) {
|
|
660
|
+
configLogger.warn(
|
|
661
|
+
`Unknown built-in intent id: "${item}". In append mode, strings have no effect on ordering \u2014 use $replace to control order.`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (typeof item === "object") {
|
|
667
|
+
if (!item.id) {
|
|
668
|
+
configLogger.warn('Intent object missing required "id" field, skipping.');
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (item.isAction && item.id !== "open-in-editor") {
|
|
672
|
+
configLogger.warn(
|
|
673
|
+
`isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
674
|
+
);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const existingIdx = merged.findIndex((i) => i.id === item.id);
|
|
678
|
+
if (existingIdx !== -1) {
|
|
679
|
+
if (item.enabled === false) {
|
|
680
|
+
merged.splice(existingIdx, 1);
|
|
681
|
+
} else {
|
|
682
|
+
merged[existingIdx] = { ...merged[existingIdx], ...item };
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
if (item.enabled !== false) {
|
|
686
|
+
merged.push(item);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return ensureOpenInEditorLast(merged);
|
|
692
|
+
}
|
|
693
|
+
function ensureOpenInEditorLast(intents) {
|
|
694
|
+
const idx = intents.findIndex((i) => i.id === "open-in-editor");
|
|
695
|
+
if (idx === -1 || idx === intents.length - 1) return intents;
|
|
696
|
+
const result = [...intents];
|
|
697
|
+
const item = result.splice(idx, 1)[0];
|
|
698
|
+
result.push(item);
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
511
701
|
var watchers = [];
|
|
512
702
|
function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
|
|
513
703
|
if (isWatching) return;
|
|
@@ -542,6 +732,19 @@ function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
|
|
|
542
732
|
}
|
|
543
733
|
|
|
544
734
|
// src/server/index.ts
|
|
735
|
+
var serverLogger = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
|
|
736
|
+
var payloadTickets = /* @__PURE__ */ new Map();
|
|
737
|
+
function createTicket(payload) {
|
|
738
|
+
const ticketId = crypto.randomUUID();
|
|
739
|
+
payloadTickets.set(ticketId, JSON.stringify(payload));
|
|
740
|
+
setTimeout(
|
|
741
|
+
() => {
|
|
742
|
+
payloadTickets.delete(ticketId);
|
|
743
|
+
},
|
|
744
|
+
5 * 60 * 1e3
|
|
745
|
+
);
|
|
746
|
+
return ticketId;
|
|
747
|
+
}
|
|
545
748
|
var serverState = {
|
|
546
749
|
port: null,
|
|
547
750
|
running: false,
|
|
@@ -553,8 +756,11 @@ var serverInstance = null;
|
|
|
553
756
|
function resolveProjectRoot() {
|
|
554
757
|
let gitRoot;
|
|
555
758
|
try {
|
|
759
|
+
serverLogger.info("Resolving project root...");
|
|
556
760
|
gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
557
|
-
|
|
761
|
+
serverLogger.info("Resolved project root: " + gitRoot);
|
|
762
|
+
} catch (e) {
|
|
763
|
+
serverLogger.error("Failed to resolve project root:", e);
|
|
558
764
|
gitRoot = process.cwd();
|
|
559
765
|
}
|
|
560
766
|
let current = gitRoot;
|
|
@@ -576,7 +782,7 @@ function launchURI(uri) {
|
|
|
576
782
|
execFileSync("xdg-open", [uri]);
|
|
577
783
|
}
|
|
578
784
|
} catch (e) {
|
|
579
|
-
|
|
785
|
+
serverLogger.error("Failed to launch URI via execFileSync, falling back to launchIDE:", e);
|
|
580
786
|
launchIDE({ file: uri });
|
|
581
787
|
}
|
|
582
788
|
}
|
|
@@ -591,7 +797,7 @@ async function startServer() {
|
|
|
591
797
|
const port = await portfinder.getPortPromise();
|
|
592
798
|
watchConfig(
|
|
593
799
|
() => {
|
|
594
|
-
|
|
800
|
+
serverLogger.info("user config reloaded.");
|
|
595
801
|
},
|
|
596
802
|
serverState.cwd,
|
|
597
803
|
serverState.configRoot
|
|
@@ -607,7 +813,7 @@ async function startServer() {
|
|
|
607
813
|
}
|
|
608
814
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
609
815
|
handleRequest(url, req, res).catch((err) => {
|
|
610
|
-
|
|
816
|
+
serverLogger.error("server error:", err);
|
|
611
817
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
612
818
|
res.end(JSON.stringify({ success: false, error: String(err) }));
|
|
613
819
|
});
|
|
@@ -620,22 +826,41 @@ async function startServer() {
|
|
|
620
826
|
serverInstance.once("error", reject);
|
|
621
827
|
});
|
|
622
828
|
serverInstance.on("error", (err) => {
|
|
623
|
-
|
|
829
|
+
serverLogger.error("persistent server error:", err);
|
|
624
830
|
});
|
|
625
831
|
serverState.port = port;
|
|
626
832
|
serverState.running = true;
|
|
627
|
-
const portFile = path6.join(os2.tmpdir(), "inspecto.port");
|
|
833
|
+
const portFile = path6.join(os2.tmpdir(), "inspecto.port.json");
|
|
628
834
|
try {
|
|
629
|
-
|
|
630
|
-
|
|
835
|
+
let portData = {};
|
|
836
|
+
if (fs3.existsSync(portFile)) {
|
|
837
|
+
try {
|
|
838
|
+
portData = JSON.parse(fs3.readFileSync(portFile, "utf-8"));
|
|
839
|
+
} catch (e) {
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const rootHash = crypto.createHash("md5").update(serverState.projectRoot).digest("hex");
|
|
843
|
+
portData[rootHash] = port;
|
|
844
|
+
fs3.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
|
|
845
|
+
} catch (e) {
|
|
846
|
+
serverLogger.warn("Failed to write port file:", e);
|
|
631
847
|
}
|
|
632
848
|
process.once("exit", () => {
|
|
633
849
|
try {
|
|
634
|
-
fs3.
|
|
850
|
+
if (fs3.existsSync(portFile)) {
|
|
851
|
+
const portData = JSON.parse(fs3.readFileSync(portFile, "utf-8"));
|
|
852
|
+
const rootHash = crypto.createHash("md5").update(serverState.projectRoot).digest("hex");
|
|
853
|
+
delete portData[rootHash];
|
|
854
|
+
if (Object.keys(portData).length === 0) {
|
|
855
|
+
fs3.unlinkSync(portFile);
|
|
856
|
+
} else {
|
|
857
|
+
fs3.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
|
|
858
|
+
}
|
|
859
|
+
}
|
|
635
860
|
} catch {
|
|
636
861
|
}
|
|
637
862
|
});
|
|
638
|
-
|
|
863
|
+
serverLogger.info(`server running at http://127.0.0.1:${port}`);
|
|
639
864
|
return port;
|
|
640
865
|
}
|
|
641
866
|
async function readBody(req) {
|
|
@@ -648,66 +873,63 @@ async function readBody(req) {
|
|
|
648
873
|
}
|
|
649
874
|
async function handleRequest(url, req, res) {
|
|
650
875
|
const pathname = url.pathname;
|
|
651
|
-
if (pathname === "/health" && req.method === "GET") {
|
|
876
|
+
if ((pathname === "/health" || pathname === INSPECTO_API_PATHS.HEALTH) && req.method === "GET") {
|
|
652
877
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
653
878
|
res.end(JSON.stringify({ ok: true, port: serverState.port }));
|
|
654
879
|
return;
|
|
655
880
|
}
|
|
656
|
-
if (pathname ===
|
|
881
|
+
if (pathname === INSPECTO_API_PATHS.CLIENT_CONFIG && req.method === "GET") {
|
|
657
882
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
658
883
|
const promptsConfig = await loadPromptsConfig(false, serverState.cwd, serverState.configRoot);
|
|
659
884
|
const effectiveIde = userConfig.ide ?? "vscode";
|
|
660
885
|
let info;
|
|
661
886
|
if (!serverState.ideInfo) {
|
|
662
|
-
const fallbackTargets = userConfig.providers ? Object.keys(userConfig.providers) : ["claude-code", "gemini", "coco", "codex"];
|
|
663
887
|
info = {
|
|
664
|
-
ide: effectiveIde
|
|
665
|
-
providers: fallbackTargets.reduce(
|
|
666
|
-
(acc, target) => {
|
|
667
|
-
acc[target] = {
|
|
668
|
-
mode: resolveToolMode(target, effectiveIde, userConfig),
|
|
669
|
-
installed: false
|
|
670
|
-
};
|
|
671
|
-
return acc;
|
|
672
|
-
},
|
|
673
|
-
{}
|
|
674
|
-
)
|
|
888
|
+
ide: effectiveIde
|
|
675
889
|
};
|
|
676
890
|
} else {
|
|
677
891
|
const { scheme: _scheme, ...rest } = serverState.ideInfo;
|
|
678
892
|
info = rest;
|
|
679
893
|
}
|
|
680
|
-
const resolvedProviders = { ...info.providers };
|
|
681
|
-
for (const tool in resolvedProviders) {
|
|
682
|
-
resolvedProviders[tool].mode = resolveToolMode(tool, info.ide, userConfig);
|
|
683
|
-
}
|
|
684
894
|
const config = {
|
|
685
895
|
...info,
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
896
|
+
prompts: resolveIntents(promptsConfig),
|
|
897
|
+
hotKeys: userConfig["inspector.hotKey"] ?? "alt",
|
|
898
|
+
theme: userConfig["inspector.theme"] ?? "auto",
|
|
899
|
+
includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
|
|
900
|
+
autoSend: userConfig["prompt.autoSend"] ?? false
|
|
691
901
|
};
|
|
902
|
+
delete config.providers;
|
|
692
903
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
693
904
|
res.end(JSON.stringify(config));
|
|
694
905
|
return;
|
|
695
906
|
}
|
|
696
|
-
if (pathname ===
|
|
907
|
+
if (pathname === INSPECTO_API_PATHS.IDE_INFO && req.method === "POST") {
|
|
697
908
|
try {
|
|
698
909
|
const body = JSON.parse(await readBody(req));
|
|
699
|
-
|
|
700
|
-
|
|
910
|
+
const ideWorkspace = body.workspaceRoot || "";
|
|
911
|
+
const serverProjectRoot = serverState.projectRoot || "";
|
|
912
|
+
const isSameProject = !ideWorkspace || !serverProjectRoot || ideWorkspace === serverProjectRoot || serverProjectRoot.startsWith(ideWorkspace);
|
|
913
|
+
if (isSameProject) {
|
|
914
|
+
serverState.ideInfo = body;
|
|
915
|
+
serverLogger.debug(
|
|
916
|
+
`Accepted IDE info from matched workspace (ide-${body.ide} / schema-${body.scheme})`
|
|
917
|
+
);
|
|
918
|
+
} else {
|
|
919
|
+
serverLogger.debug(
|
|
920
|
+
`Ignored IDE info from unrelated workspace (IDE Workspace: ${ideWorkspace}, Server: ${serverProjectRoot}, Scheme: ${body.scheme}, IDE: ${body.ide})`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
701
923
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
702
924
|
res.end(JSON.stringify({ success: true }));
|
|
703
925
|
} catch (e) {
|
|
704
|
-
|
|
926
|
+
serverLogger.error(`Error parsing ${INSPECTO_API_PATHS.IDE_INFO} POST request:`, e);
|
|
705
927
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
706
928
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
707
929
|
}
|
|
708
930
|
return;
|
|
709
931
|
}
|
|
710
|
-
if (pathname ===
|
|
932
|
+
if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
|
|
711
933
|
let body;
|
|
712
934
|
try {
|
|
713
935
|
body = JSON.parse(await readBody(req));
|
|
@@ -716,28 +938,98 @@ async function handleRequest(url, req, res) {
|
|
|
716
938
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
717
939
|
return;
|
|
718
940
|
}
|
|
719
|
-
const absolutePath = path6.isAbsolute(body.file) ? body.file : path6.resolve(serverState.cwd, body.file);
|
|
941
|
+
const absolutePath = path6.isAbsolute(body.file) ? path6.resolve(body.file) : path6.resolve(serverState.cwd, body.file);
|
|
942
|
+
const relativeToRoot = path6.relative(serverState.projectRoot, absolutePath);
|
|
943
|
+
if (relativeToRoot.startsWith("..") || path6.isAbsolute(relativeToRoot)) {
|
|
944
|
+
serverLogger.warn(`Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}`);
|
|
945
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
946
|
+
res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
720
949
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
950
|
+
const configuredIde = userConfig.ide;
|
|
951
|
+
const activeIde = serverState.ideInfo?.ide;
|
|
952
|
+
const activeIdeScheme = serverState.ideInfo?.scheme;
|
|
953
|
+
const rawEditorHint = configuredIde || activeIde || activeIdeScheme || "code";
|
|
954
|
+
if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
|
|
955
|
+
serverLogger.warn(
|
|
956
|
+
`Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
let editorHint = rawEditorHint;
|
|
960
|
+
if (rawEditorHint === "vscode") editorHint = "code";
|
|
961
|
+
else if (rawEditorHint === "vscode-insiders") editorHint = "code-insiders";
|
|
962
|
+
else if (rawEditorHint === "vscodium") editorHint = "codium";
|
|
963
|
+
else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
|
|
964
|
+
serverLogger.debug(
|
|
965
|
+
`IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
|
|
966
|
+
);
|
|
967
|
+
const VSCODE_FAMILY_SCHEMES = [
|
|
968
|
+
"vscode",
|
|
969
|
+
"vscode-insiders",
|
|
970
|
+
"cursor",
|
|
971
|
+
"windsurf",
|
|
972
|
+
"trae",
|
|
973
|
+
"trae-cn",
|
|
974
|
+
"vscodium",
|
|
975
|
+
"codebuddy",
|
|
976
|
+
"codebuddy-cn",
|
|
977
|
+
"antigravity"
|
|
978
|
+
];
|
|
979
|
+
if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
|
|
980
|
+
const uri = `${rawEditorHint}://file${absolutePath}:${body.line}:${body.column}`;
|
|
981
|
+
serverLogger.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
|
|
982
|
+
try {
|
|
983
|
+
if (process.platform === "darwin") {
|
|
984
|
+
execFileSync("open", [uri]);
|
|
985
|
+
} else if (process.platform === "win32") {
|
|
986
|
+
execFileSync("cmd", ["/c", "start", '""', uri]);
|
|
987
|
+
} else {
|
|
988
|
+
execFileSync("xdg-open", [uri]);
|
|
989
|
+
}
|
|
990
|
+
} catch (e) {
|
|
991
|
+
serverLogger.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
|
|
992
|
+
launchIDE({
|
|
993
|
+
file: absolutePath,
|
|
994
|
+
line: body.line,
|
|
995
|
+
column: body.column,
|
|
996
|
+
editor: editorHint,
|
|
997
|
+
type: process.platform === "darwin" ? "open" : "exec"
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
launchIDE({
|
|
1002
|
+
file: absolutePath,
|
|
1003
|
+
line: body.line,
|
|
1004
|
+
column: body.column,
|
|
1005
|
+
editor: editorHint,
|
|
1006
|
+
type: process.platform === "darwin" ? "open" : "exec"
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
730
1009
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
731
1010
|
res.end(JSON.stringify({ success: true }));
|
|
732
1011
|
return;
|
|
733
1012
|
}
|
|
734
|
-
if (pathname ===
|
|
1013
|
+
if (pathname === INSPECTO_API_PATHS.PROJECT_SNIPPET && req.method === "GET") {
|
|
735
1014
|
const file = url.searchParams.get("file") ?? "";
|
|
736
1015
|
const line = parseInt(url.searchParams.get("line") ?? "1", 10);
|
|
737
1016
|
const column = parseInt(url.searchParams.get("column") ?? "1", 10);
|
|
738
1017
|
const maxLines = parseInt(url.searchParams.get("maxLines") ?? "100", 10);
|
|
739
1018
|
try {
|
|
740
|
-
const absolutePath = path6.isAbsolute(file) ? file : path6.resolve(serverState.cwd, file);
|
|
1019
|
+
const absolutePath = path6.isAbsolute(file) ? path6.resolve(file) : path6.resolve(serverState.cwd, file);
|
|
1020
|
+
const relativeToRoot = path6.relative(serverState.projectRoot, absolutePath);
|
|
1021
|
+
if (relativeToRoot.startsWith("..") || path6.isAbsolute(relativeToRoot)) {
|
|
1022
|
+
serverLogger.warn(`Security: Blocked path traversal attempt in PROJECT_SNIPPET: ${file}`);
|
|
1023
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1024
|
+
res.end(
|
|
1025
|
+
JSON.stringify({
|
|
1026
|
+
success: false,
|
|
1027
|
+
error: "Access denied: File is outside of project workspace",
|
|
1028
|
+
errorCode: "FORBIDDEN"
|
|
1029
|
+
})
|
|
1030
|
+
);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
741
1033
|
const result = await extractSnippet({ file: absolutePath, line, column, maxLines });
|
|
742
1034
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
743
1035
|
res.end(JSON.stringify(result));
|
|
@@ -749,7 +1041,7 @@ async function handleRequest(url, req, res) {
|
|
|
749
1041
|
}
|
|
750
1042
|
return;
|
|
751
1043
|
}
|
|
752
|
-
if (pathname ===
|
|
1044
|
+
if (pathname === INSPECTO_API_PATHS.AI_DISPATCH && req.method === "POST") {
|
|
753
1045
|
try {
|
|
754
1046
|
const rawBody = await readBody(req);
|
|
755
1047
|
const body = JSON.parse(rawBody);
|
|
@@ -757,19 +1049,30 @@ async function handleRequest(url, req, res) {
|
|
|
757
1049
|
res.writeHead(result.success ? 200 : 500, { "Content-Type": "application/json" });
|
|
758
1050
|
res.end(JSON.stringify(result));
|
|
759
1051
|
} catch (e) {
|
|
760
|
-
|
|
1052
|
+
serverLogger.error(`Error parsing ${INSPECTO_API_PATHS.AI_DISPATCH} request:`, e);
|
|
761
1053
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
762
1054
|
res.end(JSON.stringify({ success: false, error: String(e), errorCode: "INTERNAL_ERROR" }));
|
|
763
1055
|
}
|
|
764
1056
|
return;
|
|
765
1057
|
}
|
|
1058
|
+
if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
|
|
1059
|
+
const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
|
|
1060
|
+
const payloadStr = payloadTickets.get(ticketId);
|
|
1061
|
+
if (!payloadStr) {
|
|
1062
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1063
|
+
res.end(JSON.stringify({ success: false, error: "Ticket not found or expired" }));
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1067
|
+
res.end(payloadStr);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
766
1070
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
767
1071
|
res.end(JSON.stringify({ error: "not found" }));
|
|
768
1072
|
}
|
|
769
1073
|
async function dispatchToAi(req) {
|
|
770
1074
|
const { location, snippet, prompt } = req;
|
|
771
1075
|
const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
|
|
772
|
-
const ide = userConfig.ide ?? "vscode";
|
|
773
1076
|
const resolvedTarget = resolveTargetTool(userConfig);
|
|
774
1077
|
const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
|
|
775
1078
|
|
|
@@ -777,22 +1080,45 @@ async function dispatchToAi(req) {
|
|
|
777
1080
|
${snippet}
|
|
778
1081
|
\`\`\`
|
|
779
1082
|
`;
|
|
1083
|
+
const ideReportedMode = serverState.ideInfo?.providers[resolvedTarget]?.mode;
|
|
1084
|
+
const configuredIde = userConfig.ide;
|
|
1085
|
+
const activeIde = serverState.ideInfo?.ide;
|
|
1086
|
+
const activeIdeScheme = serverState.ideInfo?.scheme;
|
|
1087
|
+
const finalIde = configuredIde || activeIdeScheme || activeIde || "vscode";
|
|
1088
|
+
if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
|
|
1089
|
+
serverLogger.warn(
|
|
1090
|
+
`dispatchToAi: Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
const mode = resolveProviderMode(resolvedTarget, finalIde, userConfig);
|
|
1094
|
+
const overrides = extractToolOverrides(finalIde, userConfig)[resolvedTarget] || {};
|
|
1095
|
+
overrides.type = mode;
|
|
1096
|
+
const fullPayload = {
|
|
1097
|
+
ide: finalIde,
|
|
1098
|
+
target: resolvedTarget,
|
|
1099
|
+
targetType: mode,
|
|
1100
|
+
prompt: formattedPrompt,
|
|
1101
|
+
filePath: location.file,
|
|
1102
|
+
line: location.line,
|
|
1103
|
+
column: location.column,
|
|
1104
|
+
snippet,
|
|
1105
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : void 0,
|
|
1106
|
+
autoSend: userConfig["prompt.autoSend"] !== void 0 ? Boolean(userConfig["prompt.autoSend"]) : void 0
|
|
1107
|
+
};
|
|
1108
|
+
const ticketId = createTicket(fullPayload);
|
|
780
1109
|
const params = new URLSearchParams();
|
|
1110
|
+
params.set("ticket", ticketId);
|
|
781
1111
|
params.set("target", resolvedTarget);
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
params.set("overrides", JSON.stringify(overrides));
|
|
785
|
-
}
|
|
786
|
-
params.set("prompt", formattedPrompt);
|
|
787
|
-
params.set("file", location.file);
|
|
788
|
-
params.set("line", String(location.line));
|
|
789
|
-
params.set("col", String(location.column));
|
|
790
|
-
params.set("snippet", snippet);
|
|
791
|
-
const scheme = serverState.ideInfo?.scheme || "vscode";
|
|
792
|
-
const uri = `${scheme}://inspecto.inspecto/send?${params.toString()}`;
|
|
793
|
-
console.log(`[inspecto] dispatchToAi: Generated URI: ${uri}`);
|
|
1112
|
+
const uri = `${finalIde}://inspecto.inspecto/send?${params.toString()}`;
|
|
1113
|
+
serverLogger.debug(`dispatchToAi: Generated URI: ${uri}`);
|
|
794
1114
|
launchURI(uri);
|
|
795
|
-
return {
|
|
1115
|
+
return {
|
|
1116
|
+
success: true,
|
|
1117
|
+
fallbackPayload: {
|
|
1118
|
+
prompt: formattedPrompt,
|
|
1119
|
+
file: location.file
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
796
1122
|
}
|
|
797
1123
|
|
|
798
1124
|
// src/injectors/utils.ts
|
|
@@ -848,9 +1174,11 @@ if (typeof window !== 'undefined') {
|
|
|
848
1174
|
}
|
|
849
1175
|
function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
|
|
850
1176
|
const inspectoClientPath = resolveClientModule2();
|
|
851
|
-
|
|
852
|
-
compiler
|
|
853
|
-
|
|
1177
|
+
if (compiler.webpack && compiler.webpack.EntryPlugin) {
|
|
1178
|
+
new compiler.webpack.EntryPlugin(compiler.context, inspectoClientPath, {
|
|
1179
|
+
name: void 0
|
|
1180
|
+
}).apply(compiler);
|
|
1181
|
+
}
|
|
854
1182
|
compiler.hooks.compilation.tap("inspecto-overlay", (compilation) => {
|
|
855
1183
|
const HtmlWebpackPlugin = compiler.options.plugins.find(
|
|
856
1184
|
(p) => p && p.constructor && p.constructor.name === "HtmlWebpackPlugin"
|
|
@@ -932,7 +1260,8 @@ var DEFAULT_OPTIONS = {
|
|
|
932
1260
|
exclude: [],
|
|
933
1261
|
escapeTags: [],
|
|
934
1262
|
pathType: "absolute",
|
|
935
|
-
attributeName: "data-inspecto"
|
|
1263
|
+
attributeName: "data-inspecto",
|
|
1264
|
+
logLevel: "warn"
|
|
936
1265
|
};
|
|
937
1266
|
var DEFAULT_PORT = 5678;
|
|
938
1267
|
var getCleanId = (id) => id.split("?")[0];
|
|
@@ -941,6 +1270,8 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
|
|
|
941
1270
|
...DEFAULT_OPTIONS,
|
|
942
1271
|
...userOptions
|
|
943
1272
|
};
|
|
1273
|
+
setGlobalLogLevel(options.logLevel);
|
|
1274
|
+
const pluginLogger = createLogger("inspecto:plugin", { logLevel: options.logLevel });
|
|
944
1275
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
945
1276
|
let projectRoot = process.cwd();
|
|
946
1277
|
let serverPort = null;
|
|
@@ -956,7 +1287,7 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
|
|
|
956
1287
|
buildStart() {
|
|
957
1288
|
if (isProduction) return;
|
|
958
1289
|
projectRoot = serverState.cwd || process.cwd();
|
|
959
|
-
ensureServer().catch(
|
|
1290
|
+
ensureServer().catch((err) => pluginLogger.error("Failed to start server:", err));
|
|
960
1291
|
},
|
|
961
1292
|
buildEnd() {
|
|
962
1293
|
},
|