@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.
Files changed (44) hide show
  1. package/README.md +19 -0
  2. package/dist/index.cjs +431 -102
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +436 -103
  7. package/dist/index.js.map +1 -1
  8. package/dist/legacy/rspack/index.cjs +402 -98
  9. package/dist/legacy/rspack/index.cjs.map +1 -1
  10. package/dist/legacy/rspack/index.js +407 -99
  11. package/dist/legacy/rspack/index.js.map +1 -1
  12. package/dist/legacy/rspack/loader.cjs +8 -1
  13. package/dist/legacy/rspack/loader.cjs.map +1 -1
  14. package/dist/legacy/rspack/loader.js +8 -1
  15. package/dist/legacy/rspack/loader.js.map +1 -1
  16. package/dist/legacy/webpack4/index.cjs +977 -0
  17. package/dist/legacy/webpack4/index.cjs.map +1 -0
  18. package/dist/legacy/webpack4/index.d.cts +8 -0
  19. package/dist/legacy/webpack4/index.d.ts +8 -0
  20. package/dist/legacy/webpack4/index.js +953 -0
  21. package/dist/legacy/webpack4/index.js.map +1 -0
  22. package/dist/legacy/webpack4/loader.cjs +347 -0
  23. package/dist/legacy/webpack4/loader.cjs.map +1 -0
  24. package/dist/legacy/webpack4/loader.d.cts +3 -0
  25. package/dist/legacy/webpack4/loader.d.ts +3 -0
  26. package/dist/legacy/webpack4/loader.js +314 -0
  27. package/dist/legacy/webpack4/loader.js.map +1 -0
  28. package/dist/rollup.cjs +431 -102
  29. package/dist/rollup.cjs.map +1 -1
  30. package/dist/rollup.js +436 -103
  31. package/dist/rollup.js.map +1 -1
  32. package/dist/rspack.cjs +431 -102
  33. package/dist/rspack.cjs.map +1 -1
  34. package/dist/rspack.js +436 -103
  35. package/dist/rspack.js.map +1 -1
  36. package/dist/vite.cjs +431 -102
  37. package/dist/vite.cjs.map +1 -1
  38. package/dist/vite.js +436 -103
  39. package/dist/vite.js.map +1 -1
  40. package/dist/webpack.cjs +431 -102
  41. package/dist/webpack.cjs.map +1 -1
  42. package/dist/webpack.js +436 -103
  43. package/dist/webpack.js.map +1 -1
  44. package/package.json +19 -3
package/dist/rollup.cjs CHANGED
@@ -81,6 +81,10 @@ function shouldTransform(filePath, options) {
81
81
  if (filePath.includes("node_modules")) return false;
82
82
  if (filePath.startsWith("\0")) return false;
83
83
  if (/[/\\](dist|build|\.next|\.nuxt)[/\\]/.test(filePath)) return false;
84
+ const ext = filePath.split(".").pop()?.toLowerCase();
85
+ if (ext && !["js", "jsx", "ts", "tsx", "mjs", "mts", "vue"].includes(ext)) {
86
+ return false;
87
+ }
84
88
  return true;
85
89
  }
86
90
  function buildEscapeTagsSet(escapeTags) {
@@ -245,7 +249,10 @@ function transformVue(options) {
245
249
  const loc = node.loc;
246
250
  if (!loc) return;
247
251
  const { line, column } = loc.start;
248
- const attrValue = formatAttrValue(normalizedPath, line, column + 1);
252
+ const templateStartLoc = descriptor.template.loc.start;
253
+ const absoluteLine = templateStartLoc.line + line - 1;
254
+ const absoluteColumn = line === 1 ? templateStartLoc.column + column - 1 : column;
255
+ const attrValue = formatAttrValue(normalizedPath, absoluteLine, absoluteColumn);
249
256
  const tagNameEnd = loc.start.offset + tagName.length + 1;
250
257
  const absoluteOffset = templateBlockStart + tagNameEnd;
251
258
  ms.appendLeft(absoluteOffset, ` ${attributeName}="${attrValue}"`);
@@ -305,9 +312,11 @@ var import_node_http = __toESM(require("http"), 1);
305
312
  var import_node_fs2 = __toESM(require("fs"), 1);
306
313
  var import_node_path5 = __toESM(require("path"), 1);
307
314
  var import_node_os2 = __toESM(require("os"), 1);
315
+ var import_node_crypto = __toESM(require("crypto"), 1);
308
316
  var import_node_child_process = require("child_process");
309
317
  var import_portfinder = __toESM(require("portfinder"), 1);
310
318
  var import_launch_ide = require("launch-ide");
319
+ var import_types2 = require("@inspecto-dev/types");
311
320
 
312
321
  // src/server/snippet.ts
313
322
  var fs = __toESM(require("fs"), 1);
@@ -423,8 +432,79 @@ var import_node_path4 = __toESM(require("path"), 1);
423
432
  var import_node_os = __toESM(require("os"), 1);
424
433
  var import_defu = require("defu");
425
434
  var import_types = require("@inspecto-dev/types");
435
+
436
+ // src/utils/logger.ts
437
+ var LOG_LEVELS = {
438
+ silent: 0,
439
+ error: 1,
440
+ warn: 2,
441
+ info: 3
442
+ };
443
+ function isDebugEnabled(namespace) {
444
+ if (typeof process === "undefined" || !process.env) return false;
445
+ const debugEnv = process.env.DEBUG;
446
+ if (!debugEnv) return false;
447
+ const namespaces = debugEnv.split(",").map((s) => s.trim());
448
+ for (const ns of namespaces) {
449
+ if (ns === "*") return true;
450
+ if (ns.endsWith("*")) {
451
+ const prefix = ns.slice(0, -1);
452
+ if (namespace.startsWith(prefix)) return true;
453
+ } else if (ns === namespace) {
454
+ return true;
455
+ }
456
+ }
457
+ return false;
458
+ }
459
+ var globalLevel = "warn";
460
+ var registeredLoggers = /* @__PURE__ */ new Set();
461
+ function setLoggerGlobalLevel(level) {
462
+ globalLevel = level;
463
+ for (const logger of registeredLoggers) {
464
+ if (logger.setLevel) {
465
+ logger.setLevel(level);
466
+ }
467
+ }
468
+ }
469
+ function createLogger(namespace, options) {
470
+ let currentLevel = options?.logLevel ?? globalLevel;
471
+ let numericLevel = LOG_LEVELS[currentLevel] ?? 2;
472
+ const debugEnabled = isDebugEnabled(namespace);
473
+ const logger = {
474
+ setLevel(level) {
475
+ currentLevel = level;
476
+ numericLevel = LOG_LEVELS[level] ?? 2;
477
+ },
478
+ info(msg, ...args) {
479
+ if (numericLevel >= LOG_LEVELS.info) {
480
+ console.log(`\x1B[36m[inspecto]\x1B[0m ${msg}`, ...args);
481
+ }
482
+ },
483
+ warn(msg, ...args) {
484
+ if (numericLevel >= LOG_LEVELS.warn) {
485
+ console.warn(`\x1B[33m[inspecto] WARN:\x1B[0m ${msg}`, ...args);
486
+ }
487
+ },
488
+ error(msg, ...args) {
489
+ if (numericLevel >= LOG_LEVELS.error) {
490
+ console.error(`\x1B[31m[inspecto] ERROR:\x1B[0m ${msg}`, ...args);
491
+ }
492
+ },
493
+ debug(msg, ...args) {
494
+ if (debugEnabled) {
495
+ console.log(`\x1B[90m[${namespace}]\x1B[0m ${msg}`, ...args);
496
+ }
497
+ }
498
+ };
499
+ registeredLoggers.add(logger);
500
+ return logger;
501
+ }
502
+
503
+ // src/config.ts
504
+ var configLogger = createLogger("inspecto:config");
426
505
  var loadedConfig = null;
427
506
  var loadedPrompts = null;
507
+ var globalLogLevel = "warn";
428
508
  var isWatching = false;
429
509
  var arrayReplaceMerge = (0, import_defu.createDefu)((obj, key, val) => {
430
510
  if (Array.isArray(val)) {
@@ -432,6 +512,15 @@ var arrayReplaceMerge = (0, import_defu.createDefu)((obj, key, val) => {
432
512
  return true;
433
513
  }
434
514
  });
515
+ function setGlobalLogLevel(level) {
516
+ if (level) {
517
+ globalLogLevel = level;
518
+ setLoggerGlobalLevel(level);
519
+ }
520
+ }
521
+ function getGlobalLogLevel() {
522
+ return globalLogLevel;
523
+ }
435
524
  function resolveConfigRoots(cwd, gitRoot) {
436
525
  const roots = [];
437
526
  let current = cwd;
@@ -461,7 +550,7 @@ function loadUserConfigSync(force = false, cwd = process.cwd(), gitRoot) {
461
550
  layers.push(readJsonSafely(import_node_path4.default.join(root, ".inspecto", "settings.json")));
462
551
  }
463
552
  layers.push(readJsonSafely(import_node_path4.default.join(import_node_os.default.homedir(), ".inspecto", "settings.json")));
464
- layers.push({ providers: {} });
553
+ layers.push({});
465
554
  const validLayers = layers.filter((l) => l !== null);
466
555
  loadedConfig = arrayReplaceMerge(...validLayers);
467
556
  return loadedConfig;
@@ -504,48 +593,145 @@ function readJsonSafely(filePath) {
504
593
  }
505
594
  } catch (e) {
506
595
  if (e instanceof SyntaxError) {
507
- console.warn(`[inspecto] Failed to parse config at ${filePath}: Invalid JSON`);
596
+ configLogger.warn(`Failed to parse config at ${filePath}: Invalid JSON`);
508
597
  } else {
509
- console.warn(`[inspecto] Failed to read config at ${filePath}:`, e);
598
+ configLogger.warn(`Failed to read config at ${filePath}:`, e);
510
599
  }
511
600
  }
512
601
  return null;
513
602
  }
514
- function resolveTargetTool(config) {
515
- if (config.prefer) {
516
- return config.prefer;
603
+ function resolveTargetTool(config, ide = "vscode") {
604
+ const defaultProvider = config["provider.default"];
605
+ if (defaultProvider) {
606
+ const tool = defaultProvider.split(".")[0];
607
+ return tool;
517
608
  }
518
- if (config.providers && Object.keys(config.providers).length > 0) {
519
- return Object.keys(config.providers)[0];
520
- }
521
- return "github-copilot";
609
+ return "copilot";
522
610
  }
523
- function resolveToolMode(tool, ide, config) {
611
+ function resolveProviderMode(tool, ide, config) {
524
612
  let requestedType = void 0;
525
- if (config.providers && config.providers[tool] && config.providers[tool].type) {
526
- const type = config.providers[tool].type;
527
- if (type === "plugin" || type === "cli") {
528
- requestedType = type;
529
- }
613
+ const defaultProvider = config["provider.default"];
614
+ if (defaultProvider && defaultProvider.startsWith(`${tool}.`)) {
615
+ const mode = defaultProvider.split(".")[1];
616
+ if (mode === "extension") requestedType = "extension";
617
+ if (mode === "cli") requestedType = "cli";
530
618
  }
531
- requestedType = requestedType ?? import_types.DEFAULT_TOOL_MODE[tool];
532
- const valid = import_types.VALID_MODES[tool] || [import_types.DEFAULT_TOOL_MODE[tool]];
619
+ requestedType = requestedType ?? import_types.DEFAULT_PROVIDER_MODE[tool];
620
+ const valid = import_types.VALID_MODES[tool] || [import_types.DEFAULT_PROVIDER_MODE[tool]];
533
621
  return requestedType && valid.includes(requestedType) ? requestedType : valid[0];
534
622
  }
535
623
  function extractToolOverrides(ide, config) {
536
624
  const result = {};
537
- if (!config.providers) return result;
538
- for (const [tool, cfg] of Object.entries(config.providers)) {
539
- if (!cfg) continue;
540
- const overrides = {
541
- type: cfg.type || import_types.DEFAULT_TOOL_MODE[tool] || "plugin"
542
- };
543
- if (cfg.bin) overrides.binaryPath = cfg.bin;
544
- if (cfg.args) overrides.args = cfg.args;
545
- if (cfg.cwd) overrides.cwd = cfg.cwd;
546
- if (cfg.autoSend !== void 0) overrides.autoSend = cfg.autoSend;
547
- result[tool] = overrides;
625
+ if (!config) return result;
626
+ for (const [key, value] of Object.entries(config)) {
627
+ if (!key.startsWith("provider.")) continue;
628
+ if (key === "provider.default") continue;
629
+ const toolIndex = 1;
630
+ const modeIndex = 2;
631
+ const propIndex = 3;
632
+ const parts = key.split(".");
633
+ if (parts.length >= propIndex + 1) {
634
+ const tool = parts[toolIndex];
635
+ const mode = parts[modeIndex];
636
+ const prop = parts[propIndex];
637
+ if (!result[tool]) {
638
+ result[tool] = { type: mode };
639
+ }
640
+ const overrides = result[tool];
641
+ if (prop === "bin") overrides.binaryPath = value;
642
+ if (prop === "args") overrides.args = value;
643
+ if (prop === "cwd") overrides.cwd = value;
644
+ if (prop === "coldStartDelay") overrides.coldStartDelay = value;
645
+ }
646
+ }
647
+ return result;
648
+ }
649
+ function resolveIntents(serverPrompts) {
650
+ const baseMap = /* @__PURE__ */ new Map();
651
+ for (const intent of import_types.DEFAULT_INTENTS) {
652
+ if (intent.id) baseMap.set(intent.id, { ...intent });
653
+ }
654
+ const defaults = () => ensureOpenInEditorLast(Array.from(baseMap.values()));
655
+ if (!serverPrompts) return defaults();
656
+ const isReplace = !Array.isArray(serverPrompts) && typeof serverPrompts === "object" && serverPrompts.$replace === true;
657
+ const promptsArray = Array.isArray(serverPrompts) ? serverPrompts : isReplace ? serverPrompts.items : [];
658
+ if (!promptsArray || promptsArray.length === 0) return defaults();
659
+ if (isReplace) {
660
+ const result = [];
661
+ for (const item of promptsArray) {
662
+ if (typeof item === "string") {
663
+ if (baseMap.has(item)) {
664
+ result.push(baseMap.get(item));
665
+ } else {
666
+ configLogger.warn(
667
+ `Unknown built-in intent id: "${item}". Available: ${[...baseMap.keys()].join(", ")}`
668
+ );
669
+ }
670
+ } else if (typeof item === "object") {
671
+ if (!item.id) {
672
+ configLogger.warn('Intent object missing required "id" field, skipping.');
673
+ continue;
674
+ }
675
+ if (item.enabled === false) {
676
+ configLogger.warn(
677
+ `Intent "${item.id}" is listed in $replace but has enabled:false \u2014 it will be excluded.`
678
+ );
679
+ continue;
680
+ }
681
+ if (item.isAction && item.id !== "open-in-editor") {
682
+ configLogger.warn(
683
+ `isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
684
+ );
685
+ continue;
686
+ }
687
+ result.push(baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item);
688
+ }
689
+ }
690
+ return ensureOpenInEditorLast(result);
691
+ }
692
+ const merged = Array.from(baseMap.values());
693
+ for (const item of promptsArray) {
694
+ if (typeof item === "string") {
695
+ if (!baseMap.has(item)) {
696
+ configLogger.warn(
697
+ `Unknown built-in intent id: "${item}". In append mode, strings have no effect on ordering \u2014 use $replace to control order.`
698
+ );
699
+ }
700
+ continue;
701
+ }
702
+ if (typeof item === "object") {
703
+ if (!item.id) {
704
+ configLogger.warn('Intent object missing required "id" field, skipping.');
705
+ continue;
706
+ }
707
+ if (item.isAction && item.id !== "open-in-editor") {
708
+ configLogger.warn(
709
+ `isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
710
+ );
711
+ continue;
712
+ }
713
+ const existingIdx = merged.findIndex((i) => i.id === item.id);
714
+ if (existingIdx !== -1) {
715
+ if (item.enabled === false) {
716
+ merged.splice(existingIdx, 1);
717
+ } else {
718
+ merged[existingIdx] = { ...merged[existingIdx], ...item };
719
+ }
720
+ } else {
721
+ if (item.enabled !== false) {
722
+ merged.push(item);
723
+ }
724
+ }
725
+ }
548
726
  }
727
+ return ensureOpenInEditorLast(merged);
728
+ }
729
+ function ensureOpenInEditorLast(intents) {
730
+ const idx = intents.findIndex((i) => i.id === "open-in-editor");
731
+ if (idx === -1 || idx === intents.length - 1) return intents;
732
+ const result = [...intents];
733
+ const item = result.splice(idx, 1)[0];
734
+ result.push(item);
549
735
  return result;
550
736
  }
551
737
  var watchers = [];
@@ -582,6 +768,19 @@ function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
582
768
  }
583
769
 
584
770
  // src/server/index.ts
771
+ var serverLogger = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
772
+ var payloadTickets = /* @__PURE__ */ new Map();
773
+ function createTicket(payload) {
774
+ const ticketId = import_node_crypto.default.randomUUID();
775
+ payloadTickets.set(ticketId, JSON.stringify(payload));
776
+ setTimeout(
777
+ () => {
778
+ payloadTickets.delete(ticketId);
779
+ },
780
+ 5 * 60 * 1e3
781
+ );
782
+ return ticketId;
783
+ }
585
784
  var serverState = {
586
785
  port: null,
587
786
  running: false,
@@ -593,8 +792,11 @@ var serverInstance = null;
593
792
  function resolveProjectRoot() {
594
793
  let gitRoot;
595
794
  try {
795
+ serverLogger.info("Resolving project root...");
596
796
  gitRoot = (0, import_node_child_process.execSync)("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
597
- } catch {
797
+ serverLogger.info("Resolved project root: " + gitRoot);
798
+ } catch (e) {
799
+ serverLogger.error("Failed to resolve project root:", e);
598
800
  gitRoot = process.cwd();
599
801
  }
600
802
  let current = gitRoot;
@@ -616,7 +818,7 @@ function launchURI(uri) {
616
818
  (0, import_node_child_process.execFileSync)("xdg-open", [uri]);
617
819
  }
618
820
  } catch (e) {
619
- console.error("[inspecto] Failed to launch URI via execFileSync, falling back to launchIDE:", e);
821
+ serverLogger.error("Failed to launch URI via execFileSync, falling back to launchIDE:", e);
620
822
  (0, import_launch_ide.launchIDE)({ file: uri });
621
823
  }
622
824
  }
@@ -631,7 +833,7 @@ async function startServer() {
631
833
  const port = await import_portfinder.default.getPortPromise();
632
834
  watchConfig(
633
835
  () => {
634
- console.log("[inspecto] user config reloaded.");
836
+ serverLogger.info("user config reloaded.");
635
837
  },
636
838
  serverState.cwd,
637
839
  serverState.configRoot
@@ -647,7 +849,7 @@ async function startServer() {
647
849
  }
648
850
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
649
851
  handleRequest(url, req, res).catch((err) => {
650
- console.error("[inspecto] server error:", err);
852
+ serverLogger.error("server error:", err);
651
853
  res.writeHead(500, { "Content-Type": "application/json" });
652
854
  res.end(JSON.stringify({ success: false, error: String(err) }));
653
855
  });
@@ -660,22 +862,41 @@ async function startServer() {
660
862
  serverInstance.once("error", reject);
661
863
  });
662
864
  serverInstance.on("error", (err) => {
663
- console.error("[inspecto] persistent server error:", err);
865
+ serverLogger.error("persistent server error:", err);
664
866
  });
665
867
  serverState.port = port;
666
868
  serverState.running = true;
667
- const portFile = import_node_path5.default.join(import_node_os2.default.tmpdir(), "inspecto.port");
869
+ const portFile = import_node_path5.default.join(import_node_os2.default.tmpdir(), "inspecto.port.json");
668
870
  try {
669
- import_node_fs2.default.writeFileSync(portFile, String(port), "utf-8");
670
- } catch {
871
+ let portData = {};
872
+ if (import_node_fs2.default.existsSync(portFile)) {
873
+ try {
874
+ portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
875
+ } catch (e) {
876
+ }
877
+ }
878
+ const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
879
+ portData[rootHash] = port;
880
+ import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
881
+ } catch (e) {
882
+ serverLogger.warn("Failed to write port file:", e);
671
883
  }
672
884
  process.once("exit", () => {
673
885
  try {
674
- import_node_fs2.default.unlinkSync(portFile);
886
+ if (import_node_fs2.default.existsSync(portFile)) {
887
+ const portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
888
+ const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
889
+ delete portData[rootHash];
890
+ if (Object.keys(portData).length === 0) {
891
+ import_node_fs2.default.unlinkSync(portFile);
892
+ } else {
893
+ import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
894
+ }
895
+ }
675
896
  } catch {
676
897
  }
677
898
  });
678
- console.log(`[inspecto] server running at http://127.0.0.1:${port}`);
899
+ serverLogger.info(`server running at http://127.0.0.1:${port}`);
679
900
  return port;
680
901
  }
681
902
  async function readBody(req) {
@@ -688,66 +909,63 @@ async function readBody(req) {
688
909
  }
689
910
  async function handleRequest(url, req, res) {
690
911
  const pathname = url.pathname;
691
- if (pathname === "/health" && req.method === "GET") {
912
+ if ((pathname === "/health" || pathname === import_types2.INSPECTO_API_PATHS.HEALTH) && req.method === "GET") {
692
913
  res.writeHead(200, { "Content-Type": "application/json" });
693
914
  res.end(JSON.stringify({ ok: true, port: serverState.port }));
694
915
  return;
695
916
  }
696
- if (pathname === "/config" && req.method === "GET") {
917
+ if (pathname === import_types2.INSPECTO_API_PATHS.CLIENT_CONFIG && req.method === "GET") {
697
918
  const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
698
919
  const promptsConfig = await loadPromptsConfig(false, serverState.cwd, serverState.configRoot);
699
920
  const effectiveIde = userConfig.ide ?? "vscode";
700
921
  let info;
701
922
  if (!serverState.ideInfo) {
702
- const fallbackTargets = userConfig.providers ? Object.keys(userConfig.providers) : ["claude-code", "gemini", "coco", "codex"];
703
923
  info = {
704
- ide: effectiveIde,
705
- providers: fallbackTargets.reduce(
706
- (acc, target) => {
707
- acc[target] = {
708
- mode: resolveToolMode(target, effectiveIde, userConfig),
709
- installed: false
710
- };
711
- return acc;
712
- },
713
- {}
714
- )
924
+ ide: effectiveIde
715
925
  };
716
926
  } else {
717
927
  const { scheme: _scheme, ...rest } = serverState.ideInfo;
718
928
  info = rest;
719
929
  }
720
- const resolvedProviders = { ...info.providers };
721
- for (const tool in resolvedProviders) {
722
- resolvedProviders[tool].mode = resolveToolMode(tool, info.ide, userConfig);
723
- }
724
930
  const config = {
725
931
  ...info,
726
- providers: resolvedProviders,
727
- providerOverrides: extractToolOverrides(info.ide, userConfig),
728
- prompts: promptsConfig,
729
- hotKeys: userConfig.hotKeys,
730
- includeSnippet: userConfig.includeSnippet
932
+ prompts: resolveIntents(promptsConfig),
933
+ hotKeys: userConfig["inspector.hotKey"] ?? "alt",
934
+ theme: userConfig["inspector.theme"] ?? "auto",
935
+ includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
936
+ autoSend: userConfig["prompt.autoSend"] ?? false
731
937
  };
938
+ delete config.providers;
732
939
  res.writeHead(200, { "Content-Type": "application/json" });
733
940
  res.end(JSON.stringify(config));
734
941
  return;
735
942
  }
736
- if (pathname === "/config" && req.method === "POST") {
943
+ if (pathname === import_types2.INSPECTO_API_PATHS.IDE_INFO && req.method === "POST") {
737
944
  try {
738
945
  const body = JSON.parse(await readBody(req));
739
- serverState.ideInfo = body;
740
- console.log(`[inspecto] Received IDE info from extension:`, body);
946
+ const ideWorkspace = body.workspaceRoot || "";
947
+ const serverProjectRoot = serverState.projectRoot || "";
948
+ const isSameProject = !ideWorkspace || !serverProjectRoot || ideWorkspace === serverProjectRoot || serverProjectRoot.startsWith(ideWorkspace);
949
+ if (isSameProject) {
950
+ serverState.ideInfo = body;
951
+ serverLogger.debug(
952
+ `Accepted IDE info from matched workspace (ide-${body.ide} / schema-${body.scheme})`
953
+ );
954
+ } else {
955
+ serverLogger.debug(
956
+ `Ignored IDE info from unrelated workspace (IDE Workspace: ${ideWorkspace}, Server: ${serverProjectRoot}, Scheme: ${body.scheme}, IDE: ${body.ide})`
957
+ );
958
+ }
741
959
  res.writeHead(200, { "Content-Type": "application/json" });
742
960
  res.end(JSON.stringify({ success: true }));
743
961
  } catch (e) {
744
- console.error("[inspecto] Error parsing /config POST request:", e);
962
+ serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.IDE_INFO} POST request:`, e);
745
963
  res.writeHead(400, { "Content-Type": "application/json" });
746
964
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
747
965
  }
748
966
  return;
749
967
  }
750
- if (pathname === "/open" && req.method === "POST") {
968
+ if (pathname === import_types2.INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
751
969
  let body;
752
970
  try {
753
971
  body = JSON.parse(await readBody(req));
@@ -756,28 +974,98 @@ async function handleRequest(url, req, res) {
756
974
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
757
975
  return;
758
976
  }
759
- const absolutePath = import_node_path5.default.isAbsolute(body.file) ? body.file : import_node_path5.default.resolve(serverState.cwd, body.file);
977
+ const absolutePath = import_node_path5.default.isAbsolute(body.file) ? import_node_path5.default.resolve(body.file) : import_node_path5.default.resolve(serverState.cwd, body.file);
978
+ const relativeToRoot = import_node_path5.default.relative(serverState.projectRoot, absolutePath);
979
+ if (relativeToRoot.startsWith("..") || import_node_path5.default.isAbsolute(relativeToRoot)) {
980
+ serverLogger.warn(`Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}`);
981
+ res.writeHead(403, { "Content-Type": "application/json" });
982
+ res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
983
+ return;
984
+ }
760
985
  const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
761
- const ide = userConfig.ide ?? "vscode";
762
- const editorHint = "code";
763
- (0, import_launch_ide.launchIDE)({
764
- file: absolutePath,
765
- line: body.line,
766
- column: body.column,
767
- editor: editorHint,
768
- type: process.platform === "darwin" ? "open" : "exec"
769
- });
986
+ const configuredIde = userConfig.ide;
987
+ const activeIde = serverState.ideInfo?.ide;
988
+ const activeIdeScheme = serverState.ideInfo?.scheme;
989
+ const rawEditorHint = configuredIde || activeIde || activeIdeScheme || "code";
990
+ if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
991
+ serverLogger.warn(
992
+ `Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
993
+ );
994
+ }
995
+ let editorHint = rawEditorHint;
996
+ if (rawEditorHint === "vscode") editorHint = "code";
997
+ else if (rawEditorHint === "vscode-insiders") editorHint = "code-insiders";
998
+ else if (rawEditorHint === "vscodium") editorHint = "codium";
999
+ else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
1000
+ serverLogger.debug(
1001
+ `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1002
+ );
1003
+ const VSCODE_FAMILY_SCHEMES = [
1004
+ "vscode",
1005
+ "vscode-insiders",
1006
+ "cursor",
1007
+ "windsurf",
1008
+ "trae",
1009
+ "trae-cn",
1010
+ "vscodium",
1011
+ "codebuddy",
1012
+ "codebuddy-cn",
1013
+ "antigravity"
1014
+ ];
1015
+ if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
1016
+ const uri = `${rawEditorHint}://file${absolutePath}:${body.line}:${body.column}`;
1017
+ serverLogger.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1018
+ try {
1019
+ if (process.platform === "darwin") {
1020
+ (0, import_node_child_process.execFileSync)("open", [uri]);
1021
+ } else if (process.platform === "win32") {
1022
+ (0, import_node_child_process.execFileSync)("cmd", ["/c", "start", '""', uri]);
1023
+ } else {
1024
+ (0, import_node_child_process.execFileSync)("xdg-open", [uri]);
1025
+ }
1026
+ } catch (e) {
1027
+ serverLogger.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1028
+ (0, import_launch_ide.launchIDE)({
1029
+ file: absolutePath,
1030
+ line: body.line,
1031
+ column: body.column,
1032
+ editor: editorHint,
1033
+ type: process.platform === "darwin" ? "open" : "exec"
1034
+ });
1035
+ }
1036
+ } else {
1037
+ (0, import_launch_ide.launchIDE)({
1038
+ file: absolutePath,
1039
+ line: body.line,
1040
+ column: body.column,
1041
+ editor: editorHint,
1042
+ type: process.platform === "darwin" ? "open" : "exec"
1043
+ });
1044
+ }
770
1045
  res.writeHead(200, { "Content-Type": "application/json" });
771
1046
  res.end(JSON.stringify({ success: true }));
772
1047
  return;
773
1048
  }
774
- if (pathname === "/snippet" && req.method === "GET") {
1049
+ if (pathname === import_types2.INSPECTO_API_PATHS.PROJECT_SNIPPET && req.method === "GET") {
775
1050
  const file = url.searchParams.get("file") ?? "";
776
1051
  const line = parseInt(url.searchParams.get("line") ?? "1", 10);
777
1052
  const column = parseInt(url.searchParams.get("column") ?? "1", 10);
778
1053
  const maxLines = parseInt(url.searchParams.get("maxLines") ?? "100", 10);
779
1054
  try {
780
- const absolutePath = import_node_path5.default.isAbsolute(file) ? file : import_node_path5.default.resolve(serverState.cwd, file);
1055
+ const absolutePath = import_node_path5.default.isAbsolute(file) ? import_node_path5.default.resolve(file) : import_node_path5.default.resolve(serverState.cwd, file);
1056
+ const relativeToRoot = import_node_path5.default.relative(serverState.projectRoot, absolutePath);
1057
+ if (relativeToRoot.startsWith("..") || import_node_path5.default.isAbsolute(relativeToRoot)) {
1058
+ serverLogger.warn(`Security: Blocked path traversal attempt in PROJECT_SNIPPET: ${file}`);
1059
+ res.writeHead(403, { "Content-Type": "application/json" });
1060
+ res.end(
1061
+ JSON.stringify({
1062
+ success: false,
1063
+ error: "Access denied: File is outside of project workspace",
1064
+ errorCode: "FORBIDDEN"
1065
+ })
1066
+ );
1067
+ return;
1068
+ }
781
1069
  const result = await extractSnippet({ file: absolutePath, line, column, maxLines });
782
1070
  res.writeHead(200, { "Content-Type": "application/json" });
783
1071
  res.end(JSON.stringify(result));
@@ -789,7 +1077,7 @@ async function handleRequest(url, req, res) {
789
1077
  }
790
1078
  return;
791
1079
  }
792
- if (pathname === "/send-to-ai" && req.method === "POST") {
1080
+ if (pathname === import_types2.INSPECTO_API_PATHS.AI_DISPATCH && req.method === "POST") {
793
1081
  try {
794
1082
  const rawBody = await readBody(req);
795
1083
  const body = JSON.parse(rawBody);
@@ -797,19 +1085,30 @@ async function handleRequest(url, req, res) {
797
1085
  res.writeHead(result.success ? 200 : 500, { "Content-Type": "application/json" });
798
1086
  res.end(JSON.stringify(result));
799
1087
  } catch (e) {
800
- console.error("[inspecto] Error parsing /send-to-ai request:", e);
1088
+ serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.AI_DISPATCH} request:`, e);
801
1089
  res.writeHead(500, { "Content-Type": "application/json" });
802
1090
  res.end(JSON.stringify({ success: false, error: String(e), errorCode: "INTERNAL_ERROR" }));
803
1091
  }
804
1092
  return;
805
1093
  }
1094
+ if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1095
+ const ticketId = pathname.substring(import_types2.INSPECTO_API_PATHS.AI_TICKET.length + 1);
1096
+ const payloadStr = payloadTickets.get(ticketId);
1097
+ if (!payloadStr) {
1098
+ res.writeHead(404, { "Content-Type": "application/json" });
1099
+ res.end(JSON.stringify({ success: false, error: "Ticket not found or expired" }));
1100
+ return;
1101
+ }
1102
+ res.writeHead(200, { "Content-Type": "application/json" });
1103
+ res.end(payloadStr);
1104
+ return;
1105
+ }
806
1106
  res.writeHead(404, { "Content-Type": "application/json" });
807
1107
  res.end(JSON.stringify({ error: "not found" }));
808
1108
  }
809
1109
  async function dispatchToAi(req) {
810
1110
  const { location, snippet, prompt } = req;
811
1111
  const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
812
- const ide = userConfig.ide ?? "vscode";
813
1112
  const resolvedTarget = resolveTargetTool(userConfig);
814
1113
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
815
1114
 
@@ -817,22 +1116,45 @@ async function dispatchToAi(req) {
817
1116
  ${snippet}
818
1117
  \`\`\`
819
1118
  `;
1119
+ const ideReportedMode = serverState.ideInfo?.providers[resolvedTarget]?.mode;
1120
+ const configuredIde = userConfig.ide;
1121
+ const activeIde = serverState.ideInfo?.ide;
1122
+ const activeIdeScheme = serverState.ideInfo?.scheme;
1123
+ const finalIde = configuredIde || activeIdeScheme || activeIde || "vscode";
1124
+ if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
1125
+ serverLogger.warn(
1126
+ `dispatchToAi: Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
1127
+ );
1128
+ }
1129
+ const mode = resolveProviderMode(resolvedTarget, finalIde, userConfig);
1130
+ const overrides = extractToolOverrides(finalIde, userConfig)[resolvedTarget] || {};
1131
+ overrides.type = mode;
1132
+ const fullPayload = {
1133
+ ide: finalIde,
1134
+ target: resolvedTarget,
1135
+ targetType: mode,
1136
+ prompt: formattedPrompt,
1137
+ filePath: location.file,
1138
+ line: location.line,
1139
+ column: location.column,
1140
+ snippet,
1141
+ overrides: Object.keys(overrides).length > 0 ? overrides : void 0,
1142
+ autoSend: userConfig["prompt.autoSend"] !== void 0 ? Boolean(userConfig["prompt.autoSend"]) : void 0
1143
+ };
1144
+ const ticketId = createTicket(fullPayload);
820
1145
  const params = new URLSearchParams();
1146
+ params.set("ticket", ticketId);
821
1147
  params.set("target", resolvedTarget);
822
- const overrides = extractToolOverrides(ide, userConfig)[resolvedTarget];
823
- if (overrides) {
824
- params.set("overrides", JSON.stringify(overrides));
825
- }
826
- params.set("prompt", formattedPrompt);
827
- params.set("file", location.file);
828
- params.set("line", String(location.line));
829
- params.set("col", String(location.column));
830
- params.set("snippet", snippet);
831
- const scheme = serverState.ideInfo?.scheme || "vscode";
832
- const uri = `${scheme}://inspecto.inspecto/send?${params.toString()}`;
833
- console.log(`[inspecto] dispatchToAi: Generated URI: ${uri}`);
1148
+ const uri = `${finalIde}://inspecto.inspecto/send?${params.toString()}`;
1149
+ serverLogger.debug(`dispatchToAi: Generated URI: ${uri}`);
834
1150
  launchURI(uri);
835
- return { success: true };
1151
+ return {
1152
+ success: true,
1153
+ fallbackPayload: {
1154
+ prompt: formattedPrompt,
1155
+ file: location.file
1156
+ }
1157
+ };
836
1158
  }
837
1159
 
838
1160
  // src/injectors/utils.ts
@@ -844,7 +1166,9 @@ var resolveClientModule = () => {
844
1166
  try {
845
1167
  return require.resolve("@inspecto-dev/core");
846
1168
  } catch {
847
- console.warn("[inspecto] Could not resolve @inspecto-dev/core \u2014 falling back to bare specifier");
1169
+ console.warn(
1170
+ "[inspecto] Could not resolve @inspecto-dev/core \u2014 falling back to bare specifier"
1171
+ );
848
1172
  return "@inspecto-dev/core";
849
1173
  }
850
1174
  }
@@ -886,9 +1210,11 @@ if (typeof window !== 'undefined') {
886
1210
  }
887
1211
  function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
888
1212
  const inspectoClientPath = resolveClientModule2();
889
- new compiler.webpack.EntryPlugin(compiler.context, inspectoClientPath, { name: void 0 }).apply(
890
- compiler
891
- );
1213
+ if (compiler.webpack && compiler.webpack.EntryPlugin) {
1214
+ new compiler.webpack.EntryPlugin(compiler.context, inspectoClientPath, {
1215
+ name: void 0
1216
+ }).apply(compiler);
1217
+ }
892
1218
  compiler.hooks.compilation.tap("inspecto-overlay", (compilation) => {
893
1219
  const HtmlWebpackPlugin = compiler.options.plugins.find(
894
1220
  (p) => p && p.constructor && p.constructor.name === "HtmlWebpackPlugin"
@@ -970,7 +1296,8 @@ var DEFAULT_OPTIONS = {
970
1296
  exclude: [],
971
1297
  escapeTags: [],
972
1298
  pathType: "absolute",
973
- attributeName: "data-inspecto"
1299
+ attributeName: "data-inspecto",
1300
+ logLevel: "warn"
974
1301
  };
975
1302
  var DEFAULT_PORT = 5678;
976
1303
  var getCleanId = (id) => id.split("?")[0];
@@ -979,6 +1306,8 @@ var InspectoPlugin = (0, import_unplugin.createUnplugin)((userOptions = {}) => {
979
1306
  ...DEFAULT_OPTIONS,
980
1307
  ...userOptions
981
1308
  };
1309
+ setGlobalLogLevel(options.logLevel);
1310
+ const pluginLogger = createLogger("inspecto:plugin", { logLevel: options.logLevel });
982
1311
  const isProduction = process.env["NODE_ENV"] === "production";
983
1312
  let projectRoot = process.cwd();
984
1313
  let serverPort = null;
@@ -994,7 +1323,7 @@ var InspectoPlugin = (0, import_unplugin.createUnplugin)((userOptions = {}) => {
994
1323
  buildStart() {
995
1324
  if (isProduction) return;
996
1325
  projectRoot = serverState.cwd || process.cwd();
997
- ensureServer().catch(console.error);
1326
+ ensureServer().catch((err) => pluginLogger.error("Failed to start server:", err));
998
1327
  },
999
1328
  buildEnd() {
1000
1329
  },