@inspecto-dev/cli 0.3.1 → 0.3.3

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.
@@ -90,7 +90,7 @@ function reportCommandError(error, options = {}) {
90
90
  }
91
91
 
92
92
  // src/onboarding/apply.ts
93
- import path5 from "path";
93
+ import path6 from "path";
94
94
  import ora from "ora";
95
95
 
96
96
  // src/detect/package-manager.ts
@@ -496,24 +496,119 @@ async function which(bin) {
496
496
  }
497
497
  }
498
498
 
499
+ // src/integrations/capabilities.ts
500
+ import path4 from "path";
501
+ import {
502
+ DUAL_MODE_PROVIDER_CAPABILITIES,
503
+ HOST_IDE_IDS,
504
+ getHostIdeLabel,
505
+ isSupportedHostIde
506
+ } from "@inspecto-dev/types";
507
+ var HOST_IDE_CAPABILITIES = {
508
+ vscode: {
509
+ label: "VS Code",
510
+ artifactDir: ".vscode",
511
+ extensionDir: ".vscode/extensions",
512
+ binaryName: "code",
513
+ binaryPaths: {
514
+ darwin: [
515
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
516
+ "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders",
517
+ `${process.env.HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`
518
+ ],
519
+ linux: [
520
+ "/usr/bin/code",
521
+ "/usr/share/code/bin/code",
522
+ "/snap/bin/code",
523
+ "/usr/bin/code-insiders"
524
+ ],
525
+ win32: [
526
+ `${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code\\bin\\code.cmd`,
527
+ `${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code Insiders\\bin\\code-insiders.cmd`,
528
+ `${process.env.PROGRAMFILES}\\Microsoft VS Code\\bin\\code.cmd`
529
+ ]
530
+ }
531
+ },
532
+ cursor: {
533
+ label: "Cursor",
534
+ artifactDir: ".cursor",
535
+ extensionDir: ".cursor/extensions",
536
+ binaryName: "cursor",
537
+ binaryPaths: {
538
+ darwin: [
539
+ "/Applications/Cursor.app/Contents/Resources/app/bin/cursor",
540
+ `${process.env.HOME}/Applications/Cursor.app/Contents/Resources/app/bin/cursor`
541
+ ],
542
+ linux: ["/usr/bin/cursor", "/opt/Cursor/resources/app/bin/cursor"],
543
+ win32: [
544
+ `${process.env.LOCALAPPDATA}\\Programs\\Cursor\\resources\\app\\bin\\cursor.cmd`,
545
+ `${process.env.PROGRAMFILES}\\Cursor\\resources\\app\\bin\\cursor.cmd`
546
+ ]
547
+ }
548
+ },
549
+ trae: {
550
+ label: "Trae",
551
+ artifactDir: ".trae",
552
+ extensionDir: ".trae/extensions",
553
+ binaryName: "trae",
554
+ binaryPaths: {
555
+ darwin: [
556
+ "/Applications/Trae.app/Contents/Resources/app/bin/trae",
557
+ `${process.env.HOME}/Applications/Trae.app/Contents/Resources/app/bin/trae`
558
+ ],
559
+ linux: ["/usr/bin/trae", "/opt/Trae/resources/app/bin/trae"],
560
+ win32: [
561
+ `${process.env.LOCALAPPDATA}\\Programs\\Trae\\resources\\app\\bin\\trae.cmd`,
562
+ `${process.env.PROGRAMFILES}\\Trae\\resources\\app\\bin\\trae.cmd`
563
+ ]
564
+ }
565
+ },
566
+ "trae-cn": {
567
+ label: "Trae CN",
568
+ artifactDir: ".trae-cn",
569
+ extensionDir: ".trae-cn/extensions",
570
+ binaryName: "trae-cn",
571
+ binaryPaths: {
572
+ darwin: [
573
+ "/Applications/Trae CN.app/Contents/Resources/app/bin/trae-cn",
574
+ `${process.env.HOME}/Applications/Trae CN.app/Contents/Resources/app/bin/trae-cn`
575
+ ],
576
+ linux: ["/usr/bin/trae-cn", "/opt/Trae CN/resources/app/bin/trae-cn"],
577
+ win32: [
578
+ `${process.env.LOCALAPPDATA}\\Programs\\Trae CN\\resources\\app\\bin\\trae-cn.cmd`,
579
+ `${process.env.PROGRAMFILES}\\Trae CN\\resources\\app\\bin\\trae-cn.cmd`
580
+ ]
581
+ }
582
+ }
583
+ };
584
+ function getHostIdeResolutionSourceLabel(source) {
585
+ if (source === "explicit") return "from --host-ide";
586
+ if (source === "config") return "from .inspecto settings";
587
+ if (source === "env") return "from IDE terminal environment";
588
+ if (source === "artifact") return "from project files";
589
+ return source;
590
+ }
591
+ function getHostIdeArtifactPath(ide, cwd) {
592
+ return path4.join(cwd, HOST_IDE_CAPABILITIES[ide].artifactDir);
593
+ }
594
+ function getHostIdeExtensionDir(ide, homeDir) {
595
+ return path4.join(homeDir, HOST_IDE_CAPABILITIES[ide].extensionDir);
596
+ }
597
+ function getHostIdeBinaryName(ide) {
598
+ return HOST_IDE_CAPABILITIES[ide].binaryName ?? null;
599
+ }
600
+ function getHostIdeBinaryCandidates(ide) {
601
+ const platform = process.platform;
602
+ return HOST_IDE_CAPABILITIES[ide].binaryPaths?.[platform] ?? [];
603
+ }
604
+ function getDualModeAssistantCapability(assistant) {
605
+ return DUAL_MODE_PROVIDER_CAPABILITIES[assistant];
606
+ }
607
+
499
608
  // src/inject/extension.ts
500
609
  var EXTENSION_ID = "inspecto.inspecto";
501
- var VSCODE_PATHS = {
502
- darwin: [
503
- "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
504
- "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders",
505
- `${process.env.HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`
506
- ],
507
- linux: ["/usr/bin/code", "/usr/share/code/bin/code", "/snap/bin/code", "/usr/bin/code-insiders"],
508
- win32: [
509
- `${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code\\bin\\code.cmd`,
510
- `${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code Insiders\\bin\\code-insiders.cmd`,
511
- `${process.env.PROGRAMFILES}\\Microsoft VS Code\\bin\\code.cmd`
512
- ]
513
- };
514
610
  async function findVSCodeBinary() {
515
- const platform = process.platform;
516
- const candidates = VSCODE_PATHS[platform] || [];
611
+ const candidates = getHostIdeBinaryCandidates("vscode");
517
612
  for (const candidate of candidates) {
518
613
  if (await exists(candidate)) {
519
614
  return candidate;
@@ -524,7 +619,70 @@ async function findVSCodeBinary() {
524
619
  }
525
620
  return null;
526
621
  }
527
- async function tryOpenURI(uri) {
622
+ async function findIdeBinary(ide) {
623
+ const binaryName = getHostIdeBinaryName(ide);
624
+ if (binaryName && await which(binaryName)) {
625
+ return binaryName;
626
+ }
627
+ if (ide === "vscode") {
628
+ return findVSCodeBinary();
629
+ }
630
+ const candidates = getHostIdeBinaryCandidates(ide);
631
+ for (const candidate of candidates) {
632
+ if (await exists(candidate)) {
633
+ return candidate;
634
+ }
635
+ }
636
+ return null;
637
+ }
638
+ async function installAlternativeIdeExtension(binaryPath, ideLabel, extensionRef, quiet) {
639
+ try {
640
+ const { stdout } = await run(binaryPath, ["--list-extensions"]);
641
+ if (extensionRef === EXTENSION_ID && stdout.toLowerCase().includes(EXTENSION_ID)) {
642
+ if (!quiet) {
643
+ log.success(`${ideLabel} extension already installed`);
644
+ }
645
+ return { type: "extension_installed", id: EXTENSION_ID, description: "already_installed" };
646
+ }
647
+ } catch {
648
+ }
649
+ try {
650
+ await run(binaryPath, ["--install-extension", extensionRef, "--force"]);
651
+ if (!quiet) {
652
+ log.success(`${ideLabel} extension installed via CLI`);
653
+ }
654
+ return { type: "extension_installed", id: extensionRef, description: "installed_via_cli" };
655
+ } catch {
656
+ try {
657
+ const { stdout } = await run(binaryPath, ["--list-extensions"]);
658
+ if (extensionRef === EXTENSION_ID && stdout.toLowerCase().includes(EXTENSION_ID)) {
659
+ if (!quiet) {
660
+ log.success(`${ideLabel} extension already installed`);
661
+ }
662
+ return { type: "extension_installed", id: EXTENSION_ID, description: "already_installed" };
663
+ }
664
+ } catch {
665
+ }
666
+ return null;
667
+ }
668
+ }
669
+ async function resolveHostIdeBinary(ide) {
670
+ if (!isSupportedHostIde(ide)) return null;
671
+ return findIdeBinary(ide);
672
+ }
673
+ async function openIdeWorkspace(ide, cwd) {
674
+ const binaryPath = await resolveHostIdeBinary(ide);
675
+ if (!binaryPath) {
676
+ return false;
677
+ }
678
+ try {
679
+ await run(binaryPath, ["--new-window", cwd]);
680
+ return true;
681
+ } catch {
682
+ return false;
683
+ }
684
+ }
685
+ async function openUri(uri) {
528
686
  try {
529
687
  const platform = process.platform;
530
688
  if (platform === "win32") {
@@ -538,7 +696,7 @@ async function tryOpenURI(uri) {
538
696
  return false;
539
697
  }
540
698
  }
541
- async function installExtension(dryRun, ide, quiet = false) {
699
+ async function installExtension(dryRun, ide, quiet = false, extensionRef = EXTENSION_ID) {
542
700
  if (dryRun) {
543
701
  if (!quiet) {
544
702
  log.dryRun("Would attempt to install VS Code extension");
@@ -553,7 +711,7 @@ async function installExtension(dryRun, ide, quiet = false) {
553
711
  if (!quiet) {
554
712
  log.success("VS Code extension installed via CLI");
555
713
  }
556
- return { type: "extension_installed", id: EXTENSION_ID };
714
+ return { type: "extension_installed", id: EXTENSION_ID, description: "installed_via_cli" };
557
715
  } catch {
558
716
  }
559
717
  }
@@ -567,17 +725,22 @@ async function installExtension(dryRun, ide, quiet = false) {
567
725
  'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future'
568
726
  );
569
727
  }
570
- return { type: "extension_installed", id: EXTENSION_ID };
728
+ return { type: "extension_installed", id: EXTENSION_ID, description: "installed_via_cli" };
571
729
  } catch {
572
730
  }
573
731
  }
574
732
  const uri = `vscode:extension/${EXTENSION_ID}`;
575
- if (await tryOpenURI(uri)) {
733
+ if (await openUri(uri)) {
576
734
  if (!quiet) {
577
735
  log.warn("Opened extension page in VS Code");
578
736
  log.hint('Please click "Install" in the opened VS Code window to complete setup.');
579
737
  }
580
- return { type: "extension_installed", id: EXTENSION_ID, manual_action_required: true };
738
+ return {
739
+ type: "extension_installed",
740
+ id: EXTENSION_ID,
741
+ description: "opened_install_page",
742
+ manual_action_required: true
743
+ };
581
744
  }
582
745
  if (!quiet) {
583
746
  log.warn("Could not auto-install VS Code extension");
@@ -589,6 +752,48 @@ async function installExtension(dryRun, ide, quiet = false) {
589
752
  }
590
753
  return null;
591
754
  }
755
+ if (ide === "cursor" && process.platform === "darwin") {
756
+ const cursorPath = await findIdeBinary("cursor");
757
+ if (cursorPath) {
758
+ const result = await installAlternativeIdeExtension(
759
+ cursorPath,
760
+ getHostIdeLabel("cursor"),
761
+ extensionRef,
762
+ quiet
763
+ );
764
+ if (result) {
765
+ return result;
766
+ }
767
+ }
768
+ }
769
+ if (ide === "trae" && process.platform === "darwin") {
770
+ const traePath = await findIdeBinary("trae");
771
+ if (traePath) {
772
+ const result = await installAlternativeIdeExtension(
773
+ traePath,
774
+ getHostIdeLabel("trae"),
775
+ extensionRef,
776
+ quiet
777
+ );
778
+ if (result) {
779
+ return result;
780
+ }
781
+ }
782
+ }
783
+ if (ide === "trae-cn" && process.platform === "darwin") {
784
+ const traeCnPath = await findIdeBinary("trae-cn");
785
+ if (traeCnPath) {
786
+ const result = await installAlternativeIdeExtension(
787
+ traeCnPath,
788
+ getHostIdeLabel("trae-cn"),
789
+ extensionRef,
790
+ quiet
791
+ );
792
+ if (result) {
793
+ return result;
794
+ }
795
+ }
796
+ }
592
797
  if (!quiet) {
593
798
  log.warn(`Could not auto-install extension for ${ide}`);
594
799
  log.hint("Please install it manually to enable Inspector features:");
@@ -620,11 +825,11 @@ async function isExtensionInstalled() {
620
825
  }
621
826
 
622
827
  // src/inject/gitignore.ts
623
- import path4 from "path";
828
+ import path5 from "path";
624
829
  var DEFAULT_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
625
830
  var SHARED_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
626
831
  async function updateGitignore(root, shared, dryRun, quiet = false) {
627
- const gitignorePath = path4.join(root, ".gitignore");
832
+ const gitignorePath = path5.join(root, ".gitignore");
628
833
  let content = await readFile(gitignorePath) ?? "";
629
834
  const desiredRules = shared ? SHARED_RULES : DEFAULT_RULES;
630
835
  const hasGlobalRule = content.match(/^\.inspecto\/\s*$/m) !== null;
@@ -656,7 +861,7 @@ async function updateGitignore(root, shared, dryRun, quiet = false) {
656
861
  }
657
862
  }
658
863
  async function cleanGitignore(root) {
659
- const gitignorePath = path4.join(root, ".gitignore");
864
+ const gitignorePath = path5.join(root, ".gitignore");
660
865
  const content = await readFile(gitignorePath);
661
866
  if (!content) return;
662
867
  const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
@@ -675,11 +880,11 @@ function resolveRuntimePackages() {
675
880
  installedDependencyNames: ["@inspecto-dev/plugin", "@inspecto-dev/core"]
676
881
  };
677
882
  }
678
- const normalizedRepo = path5.resolve(devRepo);
883
+ const normalizedRepo = path6.resolve(devRepo);
679
884
  return {
680
885
  installSpec: [
681
- shellQuote(path5.join(normalizedRepo, "packages/plugin")),
682
- shellQuote(path5.join(normalizedRepo, "packages/core"))
886
+ shellQuote(path6.join(normalizedRepo, "packages/plugin")),
887
+ shellQuote(path6.join(normalizedRepo, "packages/core"))
683
888
  ].join(" "),
684
889
  installedDependencyNames: ["@inspecto-dev/plugin", "@inspecto-dev/core"]
685
890
  };
@@ -768,11 +973,11 @@ async function applyOnboardingPlanInternal(input) {
768
973
  };
769
974
  }
770
975
  const mutations = [];
771
- const settingsDir = path5.join(input.projectRoot, ".inspecto");
976
+ const settingsDir = path6.join(input.projectRoot, ".inspecto");
772
977
  const settingsFileName = input.options.shared ? "settings.json" : "settings.local.json";
773
978
  const promptsFileName = input.options.shared ? "prompts.json" : "prompts.local.json";
774
- const settingsPath = path5.join(settingsDir, settingsFileName);
775
- const promptsPath = path5.join(settingsDir, promptsFileName);
979
+ const settingsPath = path6.join(settingsDir, settingsFileName);
980
+ const promptsPath = path6.join(settingsDir, promptsFileName);
776
981
  const runtimePackages = resolveRuntimePackages();
777
982
  const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec);
778
983
  const nextSteps = [];
@@ -873,7 +1078,7 @@ async function applyOnboardingPlanInternal(input) {
873
1078
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
874
1079
  mutations
875
1080
  };
876
- await writeJSON(path5.join(settingsDir, "install.lock"), lock);
1081
+ await writeJSON(path6.join(settingsDir, "install.lock"), lock);
877
1082
  }
878
1083
  const shouldInstallExt = !input.options.noExtension && (!input.selectedIDE || input.selectedIDE.supported);
879
1084
  let manualExtensionInstallNeeded = false;
@@ -890,7 +1095,7 @@ async function applyOnboardingPlanInternal(input) {
890
1095
  if (extMutation.manual_action_required) {
891
1096
  manualExtensionInstallNeeded = true;
892
1097
  }
893
- const lockPath = path5.join(settingsDir, "install.lock");
1098
+ const lockPath = path6.join(settingsDir, "install.lock");
894
1099
  const lock = await readJSON(lockPath);
895
1100
  if (lock) {
896
1101
  lock.mutations = mutations;
@@ -935,12 +1140,12 @@ async function applyOnboardingPlanInternal(input) {
935
1140
  }
936
1141
 
937
1142
  // src/detect/build-tool.ts
938
- import path6 from "path";
1143
+ import path7 from "path";
939
1144
  import fs2 from "fs/promises";
940
1145
  import { createRequire } from "module";
941
1146
  function isPackageResolvable(pkgName, root) {
942
1147
  try {
943
- const require2 = createRequire(path6.join(root, "package.json"));
1148
+ const require2 = createRequire(path7.join(root, "package.json"));
944
1149
  try {
945
1150
  require2.resolve(`${pkgName}/package.json`, { paths: [root] });
946
1151
  return true;
@@ -954,7 +1159,7 @@ function isPackageResolvable(pkgName, root) {
954
1159
  }
955
1160
  async function getResolvedPackageVersion(pkgName, root) {
956
1161
  try {
957
- const require2 = createRequire(path6.join(root, "package.json"));
1162
+ const require2 = createRequire(path7.join(root, "package.json"));
958
1163
  const pkgJsonPath = require2.resolve(`${pkgName}/package.json`, { paths: [root] });
959
1164
  const pkg = await readJSON(pkgJsonPath);
960
1165
  return pkg?.version || null;
@@ -1009,9 +1214,9 @@ var UNSUPPORTED_META = [
1009
1214
  { name: "SvelteKit", dep: "@sveltejs/kit", files: ["svelte.config.js", "svelte.config.ts"] }
1010
1215
  ];
1011
1216
  function normalizeRelativePath(root, filePath) {
1012
- const relative = path6.relative(root, filePath);
1013
- const normalized = relative.split(path6.sep).join("/");
1014
- return normalized || path6.basename(filePath);
1217
+ const relative = path7.relative(root, filePath);
1218
+ const normalized = relative.split(path7.sep).join("/");
1219
+ return normalized || path7.basename(filePath);
1015
1220
  }
1016
1221
  function createTargets(root, packagePaths) {
1017
1222
  if (!packagePaths || packagePaths.length === 0) {
@@ -1019,12 +1224,12 @@ function createTargets(root, packagePaths) {
1019
1224
  }
1020
1225
  return packagePaths.map((pkg) => ({
1021
1226
  packagePath: pkg,
1022
- absolutePath: pkg ? path6.join(root, pkg) : root
1227
+ absolutePath: pkg ? path7.join(root, pkg) : root
1023
1228
  }));
1024
1229
  }
1025
1230
  async function getWorkspacePackagePatterns(root) {
1026
1231
  const pkg = await readJSON(
1027
- path6.join(root, "package.json")
1232
+ path7.join(root, "package.json")
1028
1233
  );
1029
1234
  const workspaces = pkg?.workspaces;
1030
1235
  if (Array.isArray(workspaces)) {
@@ -1033,7 +1238,7 @@ async function getWorkspacePackagePatterns(root) {
1033
1238
  if (workspaces && Array.isArray(workspaces.packages)) {
1034
1239
  return workspaces.packages;
1035
1240
  }
1036
- const pnpmWorkspace = await readFile(path6.join(root, "pnpm-workspace.yaml"));
1241
+ const pnpmWorkspace = await readFile(path7.join(root, "pnpm-workspace.yaml"));
1037
1242
  if (!pnpmWorkspace) {
1038
1243
  return [];
1039
1244
  }
@@ -1052,7 +1257,7 @@ async function expandWorkspacePattern(root, pattern) {
1052
1257
  return [];
1053
1258
  }
1054
1259
  if (!normalized.includes("*")) {
1055
- return await exists(path6.join(root, normalized)) ? [normalized] : [];
1260
+ return await exists(path7.join(root, normalized)) ? [normalized] : [];
1056
1261
  }
1057
1262
  const starIndex = normalized.indexOf("*");
1058
1263
  const baseDir = normalized.slice(0, starIndex).replace(/\/$/, "");
@@ -1060,13 +1265,13 @@ async function expandWorkspacePattern(root, pattern) {
1060
1265
  if (suffix && suffix !== "") {
1061
1266
  return [];
1062
1267
  }
1063
- const absoluteBaseDir = path6.join(root, baseDir);
1268
+ const absoluteBaseDir = path7.join(root, baseDir);
1064
1269
  if (!await exists(absoluteBaseDir)) {
1065
1270
  return [];
1066
1271
  }
1067
1272
  try {
1068
1273
  const entries = await fs2.readdir(absoluteBaseDir, { withFileTypes: true });
1069
- return entries.filter((entry) => entry.isDirectory()).map((entry) => path6.posix.join(baseDir, entry.name));
1274
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => path7.posix.join(baseDir, entry.name));
1070
1275
  } catch {
1071
1276
  return [];
1072
1277
  }
@@ -1085,7 +1290,7 @@ async function detectWorkspaceTargets(root) {
1085
1290
  }
1086
1291
  return Array.from(packagePaths).map((packagePath) => ({
1087
1292
  packagePath,
1088
- absolutePath: path6.join(root, packagePath)
1293
+ absolutePath: path7.join(root, packagePath)
1089
1294
  }));
1090
1295
  }
1091
1296
  async function detectBuildTools(root, packagePaths) {
@@ -1095,7 +1300,7 @@ async function detectBuildTools(root, packagePaths) {
1095
1300
  const workspaceTargets = !packagePaths || packagePaths.length === 0 ? await detectWorkspaceTargets(root) : [];
1096
1301
  const targets = workspaceTargets.length > 0 ? workspaceTargets : explicitTargets;
1097
1302
  for (const target of targets) {
1098
- const pkg = await readJSON(path6.join(target.absolutePath, "package.json"));
1303
+ const pkg = await readJSON(path7.join(target.absolutePath, "package.json"));
1099
1304
  const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
1100
1305
  const scripts = pkg?.scripts || {};
1101
1306
  const supportedChecks = SUPPORTED_PATTERNS.map(
@@ -1117,7 +1322,7 @@ async function detectBuildTools(root, packagePaths) {
1117
1322
  const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
1118
1323
  if (!(meta.dep in allDeps)) return null;
1119
1324
  for (const file of meta.files) {
1120
- if (await exists(path6.join(target.absolutePath, file))) {
1325
+ if (await exists(path7.join(target.absolutePath, file))) {
1121
1326
  return meta.name;
1122
1327
  }
1123
1328
  }
@@ -1165,7 +1370,7 @@ async function detectPattern({
1165
1370
  return null;
1166
1371
  }
1167
1372
  for (const file of pattern.files) {
1168
- if (await exists(path6.join(targetRoot, file))) {
1373
+ if (await exists(path7.join(targetRoot, file))) {
1169
1374
  detectedFile = file;
1170
1375
  break;
1171
1376
  }
@@ -1175,7 +1380,7 @@ async function detectPattern({
1175
1380
  if (cmd.includes("node ")) {
1176
1381
  const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
1177
1382
  if (match && match[1]) {
1178
- if (await exists(path6.join(targetRoot, match[1]))) {
1383
+ if (await exists(path7.join(targetRoot, match[1]))) {
1179
1384
  if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
1180
1385
  detectedFile = match[1];
1181
1386
  break;
@@ -1186,7 +1391,7 @@ async function detectPattern({
1186
1391
  if (pattern.tool === "webpack" || pattern.tool === "rspack") {
1187
1392
  const configMatch = cmd.match(/--config\s+([^\s]+)/);
1188
1393
  if (configMatch && configMatch[1]) {
1189
- if (await exists(path6.join(targetRoot, configMatch[1]))) {
1394
+ if (await exists(path7.join(targetRoot, configMatch[1]))) {
1190
1395
  detectedFile = configMatch[1];
1191
1396
  break;
1192
1397
  }
@@ -1224,7 +1429,7 @@ async function detectPattern({
1224
1429
  isLegacyWebpack = true;
1225
1430
  }
1226
1431
  }
1227
- const absoluteConfig = path6.join(targetRoot, detectedFile);
1432
+ const absoluteConfig = path7.join(targetRoot, detectedFile);
1228
1433
  const relativeConfig = normalizeRelativePath(workspaceRoot, absoluteConfig);
1229
1434
  return {
1230
1435
  tool: pattern.tool,
@@ -1242,7 +1447,7 @@ function resolveInjectionTarget(detections) {
1242
1447
  }
1243
1448
 
1244
1449
  // src/detect/framework.ts
1245
- import path7 from "path";
1450
+ import path8 from "path";
1246
1451
  import { createRequire as createRequire2 } from "module";
1247
1452
  var META_FRAMEWORK_MAP = {
1248
1453
  next: "react",
@@ -1268,7 +1473,7 @@ var UNSUPPORTED_FRAMEWORKS = [
1268
1473
  ];
1269
1474
  function isPackageResolvable2(pkgName, root) {
1270
1475
  try {
1271
- const require2 = createRequire2(path7.join(root, "package.json"));
1476
+ const require2 = createRequire2(path8.join(root, "package.json"));
1272
1477
  try {
1273
1478
  require2.resolve(`${pkgName}/package.json`, { paths: [root] });
1274
1479
  return true;
@@ -1281,7 +1486,7 @@ function isPackageResolvable2(pkgName, root) {
1281
1486
  }
1282
1487
  }
1283
1488
  async function detectFrameworks(root) {
1284
- const pkg = await readJSON(path7.join(root, "package.json"));
1489
+ const pkg = await readJSON(path8.join(root, "package.json"));
1285
1490
  const allDeps = {
1286
1491
  ...pkg?.dependencies || {},
1287
1492
  ...pkg?.devDependencies || {},
@@ -1316,7 +1521,7 @@ async function detectFrameworks(root) {
1316
1521
  }
1317
1522
 
1318
1523
  // src/detect/ide.ts
1319
- import path8 from "path";
1524
+ import path9 from "path";
1320
1525
  var SUPPORTED_IDE = "vscode";
1321
1526
  async function detectIDE(root) {
1322
1527
  const detected = /* @__PURE__ */ new Map();
@@ -1333,10 +1538,10 @@ async function detectIDE(root) {
1333
1538
  detected.set("Windsurf", { ide: "Windsurf", supported: false });
1334
1539
  }
1335
1540
  const [hasTrae, hasCursor, hasVscode, hasIdea] = await Promise.all([
1336
- exists(path8.join(root, ".trae")),
1337
- exists(path8.join(root, ".cursor")),
1338
- exists(path8.join(root, ".vscode")),
1339
- exists(path8.join(root, ".idea"))
1541
+ exists(path9.join(root, ".trae")),
1542
+ exists(path9.join(root, ".cursor")),
1543
+ exists(path9.join(root, ".vscode")),
1544
+ exists(path9.join(root, ".idea"))
1340
1545
  ]);
1341
1546
  if (hasTrae && !detected.has("Trae")) {
1342
1547
  detected.set("Trae", { ide: "trae", supported: true });
@@ -1359,7 +1564,7 @@ async function detectIDE(root) {
1359
1564
  }
1360
1565
 
1361
1566
  // src/detect/provider.ts
1362
- import path9 from "path";
1567
+ import path10 from "path";
1363
1568
  var KNOWN_CLI_TOOLS = [
1364
1569
  { id: "claude-code", bin: "claude", label: "Claude Code", supported: true },
1365
1570
  { id: "coco", bin: "coco", label: "Trae CLI (Coco)", supported: true },
@@ -1386,7 +1591,7 @@ async function detectProviders(root) {
1386
1591
  }
1387
1592
  });
1388
1593
  await Promise.all(cliChecks);
1389
- const extensionsJsonPath = path9.join(root, ".vscode", "extensions.json");
1594
+ const extensionsJsonPath = path10.join(root, ".vscode", "extensions.json");
1390
1595
  let recommendedExts = [];
1391
1596
  if (await exists(extensionsJsonPath)) {
1392
1597
  try {
@@ -1398,14 +1603,14 @@ async function detectProviders(root) {
1398
1603
  }
1399
1604
  }
1400
1605
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
1401
- const globalExtDir = path9.join(homeDir, ".vscode", "extensions");
1606
+ const globalExtDir = path10.join(homeDir, ".vscode", "extensions");
1402
1607
  const globalExtExists = await exists(globalExtDir);
1403
1608
  let installedExtensionFolders = [];
1404
1609
  if (globalExtExists) {
1405
1610
  try {
1406
1611
  const { readdir } = await import("fs/promises");
1407
1612
  installedExtensionFolders = await readdir(globalExtDir);
1408
- const obsoletePath = path9.join(globalExtDir, ".obsolete");
1613
+ const obsoletePath = path10.join(globalExtDir, ".obsolete");
1409
1614
  if (await exists(obsoletePath)) {
1410
1615
  try {
1411
1616
  const obsoleteData = await readJSON(obsoletePath);
@@ -1832,7 +2037,7 @@ async function detect(json = false) {
1832
2037
  }
1833
2038
 
1834
2039
  // src/commands/init.ts
1835
- import path10 from "path";
2040
+ import path11 from "path";
1836
2041
 
1837
2042
  // src/onboarding/target-resolution.ts
1838
2043
  function normalizePackagePath(packagePath) {
@@ -2108,7 +2313,7 @@ async function init(options) {
2108
2313
  verifiedPackages.push(pkg);
2109
2314
  continue;
2110
2315
  }
2111
- const absolutePath = path10.join(repoRoot, pkg);
2316
+ const absolutePath = path11.join(repoRoot, pkg);
2112
2317
  if (await exists(absolutePath)) {
2113
2318
  verifiedPackages.push(pkg);
2114
2319
  } else {
@@ -2121,7 +2326,7 @@ async function init(options) {
2121
2326
  return;
2122
2327
  }
2123
2328
  log.header("Inspecto Setup");
2124
- if (!await exists(path10.join(repoRoot, "package.json"))) {
2329
+ if (!await exists(path11.join(repoRoot, "package.json"))) {
2125
2330
  log.error("No package.json found in current directory");
2126
2331
  log.hint("Run this command from your project root");
2127
2332
  return;
@@ -2156,12 +2361,12 @@ async function init(options) {
2156
2361
  log.hint("Run `inspecto init` inside the target app, or pass --packages <app-path>.");
2157
2362
  return;
2158
2363
  }
2159
- projectRoot = path10.join(repoRoot, selectedPackage);
2364
+ projectRoot = path11.join(repoRoot, selectedPackage);
2160
2365
  buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
2161
2366
  log.info(`Continuing initialization in ${selectedPackage}`);
2162
2367
  } else if (targetResolution.selected?.packagePath) {
2163
2368
  const selectedPackage = targetResolution.selected.packagePath;
2164
- projectRoot = path10.join(repoRoot, selectedPackage);
2369
+ projectRoot = path11.join(repoRoot, selectedPackage);
2165
2370
  buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
2166
2371
  log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
2167
2372
  log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
@@ -2380,7 +2585,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
2380
2585
  const supportByPackage = {};
2381
2586
  await Promise.all(
2382
2587
  packagePaths.map(async (packagePath) => {
2383
- const result = await detectFrameworks(path10.join(repoRoot, packagePath));
2588
+ const result = await detectFrameworks(path11.join(repoRoot, packagePath));
2384
2589
  supportByPackage[packagePath] = result.supported;
2385
2590
  })
2386
2591
  );
@@ -2388,7 +2593,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
2388
2593
  }
2389
2594
 
2390
2595
  // src/commands/doctor.ts
2391
- import path11 from "path";
2596
+ import path12 from "path";
2392
2597
  function createDiagnostic(code, status, message2, hints = [], details) {
2393
2598
  return {
2394
2599
  code,
@@ -2432,7 +2637,7 @@ function printDoctorResult(result) {
2432
2637
  }
2433
2638
  async function collectDoctorResult(root = process.cwd()) {
2434
2639
  const checks = [];
2435
- if (!await exists(path11.join(root, "package.json"))) {
2640
+ if (!await exists(path12.join(root, "package.json"))) {
2436
2641
  const diagnostic = createDiagnostic("missing-package-json", "error", "No package.json found", [
2437
2642
  "Run this command from your project root"
2438
2643
  ]);
@@ -2526,9 +2731,9 @@ async function collectDoctorResult(root = process.cwd()) {
2526
2731
  }).join(", ");
2527
2732
  checks.push(createDiagnostic("provider-detected", "ok", `Provider: ${aiNames}`));
2528
2733
  }
2529
- const pluginPath = path11.join(root, "node_modules", "@inspecto-dev", "plugin");
2734
+ const pluginPath = path12.join(root, "node_modules", "@inspecto-dev", "plugin");
2530
2735
  if (await exists(pluginPath)) {
2531
- const pkgJson = await readJSON(path11.join(pluginPath, "package.json"));
2736
+ const pkgJson = await readJSON(path12.join(pluginPath, "package.json"));
2532
2737
  const version = pkgJson?.version ?? "unknown";
2533
2738
  checks.push(
2534
2739
  createDiagnostic("plugin-installed", "ok", `@inspecto-dev/plugin@${version} installed`, [], {
@@ -2545,7 +2750,7 @@ async function collectDoctorResult(root = process.cwd()) {
2545
2750
  if (buildResult.supported.length > 0) {
2546
2751
  let injected = false;
2547
2752
  for (const bt of buildResult.supported) {
2548
- const content = await readFile(path11.join(root, bt.configPath));
2753
+ const content = await readFile(path12.join(root, bt.configPath));
2549
2754
  if (content && content.includes("@inspecto-dev/plugin")) {
2550
2755
  checks.push(
2551
2756
  createDiagnostic("plugin-configured", "ok", `Plugin configured in ${bt.configPath}`, [], {
@@ -2604,8 +2809,8 @@ async function collectDoctorResult(root = process.cwd()) {
2604
2809
  );
2605
2810
  }
2606
2811
  }
2607
- const settingsJsonPath = path11.join(root, ".inspecto", "settings.json");
2608
- const settingsLocalPath = path11.join(root, ".inspecto", "settings.local.json");
2812
+ const settingsJsonPath = path12.join(root, ".inspecto", "settings.json");
2813
+ const settingsLocalPath = path12.join(root, ".inspecto", "settings.local.json");
2609
2814
  const hasSettingsJson = await exists(settingsJsonPath);
2610
2815
  const hasSettingsLocal = await exists(settingsLocalPath);
2611
2816
  if (hasSettingsJson || hasSettingsLocal) {
@@ -2639,7 +2844,7 @@ async function collectDoctorResult(root = process.cwd()) {
2639
2844
  )
2640
2845
  );
2641
2846
  }
2642
- const gitignoreContent = await readFile(path11.join(root, ".gitignore"));
2847
+ const gitignoreContent = await readFile(path12.join(root, ".gitignore"));
2643
2848
  if (gitignoreContent) {
2644
2849
  const hasLockIgnore = gitignoreContent.includes(".inspecto/install.lock") || gitignoreContent.includes(".inspecto/");
2645
2850
  if (!hasLockIgnore) {
@@ -2677,7 +2882,7 @@ async function doctor(options = {}) {
2677
2882
  }
2678
2883
 
2679
2884
  // src/onboarding/session.ts
2680
- import path12 from "path";
2885
+ import path13 from "path";
2681
2886
  function normalizePackagePath2(packagePath) {
2682
2887
  if (!packagePath || packagePath === ".") return "";
2683
2888
  return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
@@ -2702,7 +2907,7 @@ function getVerificationCommand(packageManager) {
2702
2907
  }
2703
2908
  async function buildVerification(projectRoot, packageManager) {
2704
2909
  const packageJson = await readJSON(
2705
- path12.join(projectRoot, "package.json")
2910
+ path13.join(projectRoot, "package.json")
2706
2911
  );
2707
2912
  if (packageJson?.scripts?.dev) {
2708
2913
  const devCommand = getVerificationCommand(packageManager);
@@ -2742,7 +2947,7 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
2742
2947
  await Promise.all(
2743
2948
  Array.from(packagePaths).map(async (packagePath) => {
2744
2949
  const frameworkResult = await detectFrameworks(
2745
- packagePath ? path12.join(repoRoot, packagePath) : repoRoot
2950
+ packagePath ? path13.join(repoRoot, packagePath) : repoRoot
2746
2951
  );
2747
2952
  supportByPackage[packagePath] = frameworkResult.supported;
2748
2953
  })
@@ -2750,7 +2955,7 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
2750
2955
  return supportByPackage;
2751
2956
  }
2752
2957
  async function buildTargetedContext(rootContext, packagePath) {
2753
- const projectRoot = packagePath ? path12.join(rootContext.root, packagePath) : rootContext.root;
2958
+ const projectRoot = packagePath ? path13.join(rootContext.root, packagePath) : rootContext.root;
2754
2959
  const [frameworks, ides, providers] = await Promise.all([
2755
2960
  detectFrameworks(projectRoot),
2756
2961
  detectIDE(projectRoot),
@@ -2991,6 +3196,21 @@ function buildDeferredOnboardResult(session) {
2991
3196
  }
2992
3197
 
2993
3198
  // src/commands/onboard.ts
3199
+ function printManualExtensionGuidance(result) {
3200
+ if (!result.ideExtension?.required || !result.ideExtension.manualRequired) {
3201
+ return;
3202
+ }
3203
+ log.warn("Complete the IDE extension install before verification.");
3204
+ if (result.ideExtension.installCommand) {
3205
+ log.hint(result.ideExtension.installCommand);
3206
+ }
3207
+ if (result.ideExtension.marketplaceUrl) {
3208
+ log.hint(result.ideExtension.marketplaceUrl);
3209
+ }
3210
+ if (result.ideExtension.openVsxUrl) {
3211
+ log.hint(result.ideExtension.openVsxUrl);
3212
+ }
3213
+ }
2994
3214
  function printOnboardResult(result) {
2995
3215
  log.header("Inspecto Onboard");
2996
3216
  log.info(`Status: ${result.status}`);
@@ -3004,6 +3224,7 @@ function printOnboardResult(result) {
3004
3224
  if (result.confirmation.required && result.confirmation.question) {
3005
3225
  log.warn(result.confirmation.question);
3006
3226
  }
3227
+ printManualExtensionGuidance(result);
3007
3228
  const extensionReady = !result.ideExtension?.required || result.ideExtension.installed && !result.ideExtension.manualRequired;
3008
3229
  if (extensionReady && (result.status === "success" || result.status === "partial_success") && result.verification?.message) {
3009
3230
  log.info(result.verification.message);
@@ -3057,11 +3278,11 @@ async function plan(json = false) {
3057
3278
  }
3058
3279
 
3059
3280
  // src/commands/teardown.ts
3060
- import path13 from "path";
3281
+ import path14 from "path";
3061
3282
  async function teardown() {
3062
3283
  const root = process.cwd();
3063
3284
  log.header("Inspecto Teardown");
3064
- const lockPath = path13.join(root, ".inspecto", "install.lock");
3285
+ const lockPath = path14.join(root, ".inspecto", "install.lock");
3065
3286
  const lock = await readJSON(lockPath);
3066
3287
  if (!lock) {
3067
3288
  log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
@@ -3074,8 +3295,8 @@ async function teardown() {
3074
3295
  } catch {
3075
3296
  log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
3076
3297
  }
3077
- if (await exists(path13.join(root, ".inspecto"))) {
3078
- await removeDir(path13.join(root, ".inspecto"));
3298
+ if (await exists(path14.join(root, ".inspecto"))) {
3299
+ await removeDir(path14.join(root, ".inspecto"));
3079
3300
  log.success("Deleted .inspecto/ directory");
3080
3301
  }
3081
3302
  await cleanGitignore(root);
@@ -3124,7 +3345,7 @@ async function teardown() {
3124
3345
  }
3125
3346
  }
3126
3347
  }
3127
- await removeDir(path13.join(root, ".inspecto"));
3348
+ await removeDir(path14.join(root, ".inspecto"));
3128
3349
  log.success("Deleted .inspecto/ directory");
3129
3350
  await cleanGitignore(root);
3130
3351
  log.blank();
@@ -3132,10 +3353,1083 @@ async function teardown() {
3132
3353
  log.blank();
3133
3354
  }
3134
3355
 
3356
+ // src/commands/integration-install.ts
3357
+ import fs3 from "fs/promises";
3358
+ import { homedir as homedir2 } from "os";
3359
+ import path17 from "path";
3360
+ import { fileURLToPath } from "url";
3361
+
3362
+ // src/commands/integration-host-ide.ts
3363
+ import path15 from "path";
3364
+ async function resolveIntegrationHostIde(options = {}) {
3365
+ if (isSupportedHostIde(options.explicitIde)) {
3366
+ return {
3367
+ ide: options.explicitIde,
3368
+ confidence: "high",
3369
+ source: "explicit",
3370
+ candidates: [options.explicitIde]
3371
+ };
3372
+ }
3373
+ const cwd = options.cwd ?? process.cwd();
3374
+ const configuredIde = await resolveConfiguredIde(cwd);
3375
+ if (configuredIde) {
3376
+ return {
3377
+ ide: configuredIde,
3378
+ confidence: "high",
3379
+ source: "config",
3380
+ candidates: [configuredIde]
3381
+ };
3382
+ }
3383
+ const envCandidates = detectEnvHostIdes();
3384
+ if (envCandidates.length === 1) {
3385
+ return {
3386
+ ide: envCandidates[0],
3387
+ confidence: "high",
3388
+ source: "env",
3389
+ candidates: envCandidates
3390
+ };
3391
+ }
3392
+ if (envCandidates.length > 1) {
3393
+ return {
3394
+ ide: null,
3395
+ confidence: "low",
3396
+ source: "ambiguous",
3397
+ candidates: envCandidates
3398
+ };
3399
+ }
3400
+ const artifactCandidates = await detectArtifactHostIdes(cwd);
3401
+ if (artifactCandidates.length === 1) {
3402
+ return {
3403
+ ide: artifactCandidates[0],
3404
+ confidence: "medium",
3405
+ source: "artifact",
3406
+ candidates: artifactCandidates
3407
+ };
3408
+ }
3409
+ if (artifactCandidates.length > 1) {
3410
+ return {
3411
+ ide: null,
3412
+ confidence: "low",
3413
+ source: "ambiguous",
3414
+ candidates: artifactCandidates
3415
+ };
3416
+ }
3417
+ return {
3418
+ ide: null,
3419
+ confidence: "low",
3420
+ source: "none",
3421
+ candidates: []
3422
+ };
3423
+ }
3424
+ async function resolveConfiguredIde(cwd) {
3425
+ const settingsPaths = [
3426
+ path15.join(cwd, ".inspecto", "settings.local.json"),
3427
+ path15.join(cwd, ".inspecto", "settings.json")
3428
+ ];
3429
+ for (const settingsPath of settingsPaths) {
3430
+ const settings = await readJSON(settingsPath);
3431
+ if (settings && isSupportedHostIde(settings.ide)) {
3432
+ return settings.ide;
3433
+ }
3434
+ }
3435
+ return null;
3436
+ }
3437
+ function detectEnvHostIdes() {
3438
+ const detected = /* @__PURE__ */ new Set();
3439
+ if (process.env.CURSOR_TRACE_DIR || process.env.CURSOR_CHANNEL) {
3440
+ detected.add("cursor");
3441
+ }
3442
+ if (process.env.TRAE_APP_DIR || process.env.__CFBundleIdentifier === "com.byteocean.trae" || process.env.COCO_IDE_PLUGIN_TYPE === "Trae" || process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes("trae")) {
3443
+ detected.add("trae");
3444
+ }
3445
+ if (process.env.__CFBundleIdentifier === "com.byteocean.trae.cn" || process.env.COCO_IDE_PLUGIN_TYPE === "TraeCN" || process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes("trae-cn")) {
3446
+ detected.add("trae-cn");
3447
+ }
3448
+ if (detected.size === 0 && process.env.TERM_PROGRAM === "vscode") {
3449
+ detected.add("vscode");
3450
+ }
3451
+ return Array.from(detected);
3452
+ }
3453
+ async function detectArtifactHostIdes(cwd) {
3454
+ const artifactOrder = ["cursor", "trae", "trae-cn", "vscode"];
3455
+ const candidates = artifactOrder.map((ide) => ({
3456
+ ide,
3457
+ target: getHostIdeArtifactPath(ide, cwd)
3458
+ }));
3459
+ const resolved = await Promise.all(
3460
+ candidates.map(async (candidate) => await exists(candidate.target) ? candidate.ide : null)
3461
+ );
3462
+ return resolved.filter((value) => value !== null);
3463
+ }
3464
+
3465
+ // src/commands/integration-dispatch-mode.ts
3466
+ import { homedir } from "os";
3467
+ import path16 from "path";
3468
+ async function resolveIntegrationDispatchMode(options) {
3469
+ const assistantRule = getDualModeAssistantCapability(options.assistant);
3470
+ const home = options.homeDir ?? homedir();
3471
+ const extensionDir = getHostIdeExtensionDir(options.hostIde, home);
3472
+ if (assistantRule && extensionDir) {
3473
+ if (await isIdeExtensionInstalled(assistantRule.extensionId, extensionDir)) {
3474
+ return {
3475
+ mode: "extension",
3476
+ ready: true,
3477
+ reason: `${options.hostIde}_${options.assistant}_extension`
3478
+ };
3479
+ }
3480
+ if (await which(assistantRule.cliBin)) {
3481
+ return {
3482
+ mode: "cli",
3483
+ ready: true,
3484
+ reason: `${options.assistant}_cli`
3485
+ };
3486
+ }
3487
+ return {
3488
+ mode: null,
3489
+ ready: false,
3490
+ reason: `missing_${options.assistant}_runtime`
3491
+ };
3492
+ }
3493
+ return {
3494
+ mode: null,
3495
+ ready: true,
3496
+ reason: "default"
3497
+ };
3498
+ }
3499
+ async function isIdeExtensionInstalled(extensionId, extensionsDir) {
3500
+ if (!await exists(extensionsDir)) {
3501
+ return false;
3502
+ }
3503
+ let extensionFolders;
3504
+ try {
3505
+ const { readdir } = await import("fs/promises");
3506
+ extensionFolders = await readdir(extensionsDir);
3507
+ } catch {
3508
+ return false;
3509
+ }
3510
+ const obsoletePath = path16.join(extensionsDir, ".obsolete");
3511
+ let obsoleteFolders = /* @__PURE__ */ new Set();
3512
+ if (await exists(obsoletePath)) {
3513
+ const obsolete = await readJSON(obsoletePath);
3514
+ if (obsolete) {
3515
+ obsoleteFolders = new Set(Object.keys(obsolete));
3516
+ }
3517
+ }
3518
+ return extensionFolders.some((folder) => {
3519
+ if (obsoleteFolders.has(folder)) return false;
3520
+ const lower = folder.toLowerCase();
3521
+ const normalized = extensionId.toLowerCase();
3522
+ return lower === normalized || lower.startsWith(`${normalized}-`);
3523
+ });
3524
+ }
3525
+
3526
+ // src/commands/integration-automation.ts
3527
+ var ONBOARDING_PROMPT = "Set up Inspecto in this project";
3528
+ var TOTAL_STEPS = 6;
3529
+ var EXTENSION_ID2 = "inspecto.inspecto";
3530
+ function getPreviewReadyMessage() {
3531
+ return "Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.";
3532
+ }
3533
+ function getPreviewBlockedMessage() {
3534
+ return "Preview blocked. Inspecto did not write files or open IDE windows because setup cannot continue until the blocking issue below is resolved.";
3535
+ }
3536
+ function getHostIdeBlockedMessage() {
3537
+ return "Automatic setup stopped: Inspecto could not determine which IDE should receive onboarding.";
3538
+ }
3539
+ function getRuntimeBlockedMessage(assistant, ide) {
3540
+ return `Automatic setup stopped: Inspecto could not find a runnable ${getAssistantLabel(assistant)} target in ${getHostIdeLabel(ide)}.`;
3541
+ }
3542
+ function getLaunchBlockedMessage(ide) {
3543
+ return `Automatic setup stopped: Inspecto could not open onboarding in ${getHostIdeLabel(ide)}.`;
3544
+ }
3545
+ async function runIntegrationAutomation(assistant, options = {}, cwd) {
3546
+ const silent = options.silent ?? false;
3547
+ const resolvedHostIde = await resolveIntegrationHostIde({
3548
+ ...options.ide ? { explicitIde: options.ide } : {},
3549
+ ...cwd ? { cwd } : {}
3550
+ });
3551
+ const details = {
3552
+ hostIde: {
3553
+ id: resolvedHostIde.ide,
3554
+ confidence: resolvedHostIde.confidence,
3555
+ candidates: resolvedHostIde.candidates,
3556
+ ...resolvedHostIde.ide ? {
3557
+ label: getHostIdeLabel(resolvedHostIde.ide),
3558
+ source: getHostIdeResolutionSourceLabel(resolvedHostIde.source)
3559
+ } : {}
3560
+ }
3561
+ };
3562
+ if (!resolvedHostIde.ide || resolvedHostIde.confidence === "low") {
3563
+ if (!silent) {
3564
+ log.warn(formatIntegrationStep(2, "Could not confidently resolve the host IDE"));
3565
+ }
3566
+ if (resolvedHostIde.candidates.length > 0) {
3567
+ if (!silent) {
3568
+ log.hint(`Candidates: ${resolvedHostIde.candidates.join(", ")}`);
3569
+ }
3570
+ }
3571
+ if (!silent) {
3572
+ log.hint(
3573
+ "Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup."
3574
+ );
3575
+ }
3576
+ return {
3577
+ status: "blocked",
3578
+ message: getHostIdeBlockedMessage(),
3579
+ nextStep: "Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup.",
3580
+ details
3581
+ };
3582
+ }
3583
+ const previewParams = new URLSearchParams();
3584
+ previewParams.set("target", assistant);
3585
+ previewParams.set("prompt", ONBOARDING_PROMPT);
3586
+ previewParams.set("autoSend", String(shouldAutoSend(assistant, resolvedHostIde.ide)));
3587
+ if (cwd) {
3588
+ previewParams.set("workspace", cwd);
3589
+ }
3590
+ const dispatchMode = await resolveIntegrationDispatchMode({
3591
+ assistant,
3592
+ hostIde: resolvedHostIde.ide
3593
+ });
3594
+ if (dispatchMode.mode) {
3595
+ previewParams.set("overrides", JSON.stringify({ type: dispatchMode.mode }));
3596
+ }
3597
+ const launchUri = `${resolvedHostIde.ide}://inspecto.inspecto/send?${previewParams.toString()}`;
3598
+ details.inspectoExtension = {
3599
+ source: options.inspectoVsix ? "local_vsix" : "marketplace",
3600
+ reference: options.inspectoVsix ?? EXTENSION_ID2
3601
+ };
3602
+ details.runtime = {
3603
+ assistant: getAssistantLabel(assistant),
3604
+ ready: dispatchMode.ready,
3605
+ mode: dispatchMode.mode
3606
+ };
3607
+ details.workspace = {
3608
+ ...cwd ? { path: cwd } : {},
3609
+ attempted: Boolean(cwd)
3610
+ };
3611
+ details.onboarding = {
3612
+ uri: launchUri,
3613
+ autoSend: shouldAutoSend(assistant, resolvedHostIde.ide)
3614
+ };
3615
+ if (options.preview) {
3616
+ if (!silent) {
3617
+ log.info(formatIntegrationStep(2, "Previewed host IDE resolution"));
3618
+ log.hint(
3619
+ `${getHostIdeLabel(resolvedHostIde.ide)} (${getHostIdeResolutionSourceLabel(resolvedHostIde.source)})`
3620
+ );
3621
+ }
3622
+ const hostIdeBinary = await resolveHostIdeBinary(resolvedHostIde.ide);
3623
+ details.inspectoExtension.binaryAvailable = Boolean(hostIdeBinary);
3624
+ details.inspectoExtension.binaryPath = hostIdeBinary;
3625
+ if (!hostIdeBinary) {
3626
+ const nextStep = `Install the ${getHostIdeLabel(resolvedHostIde.ide)} CLI binary or rerun the command from a shell where it is available, then rerun the command.`;
3627
+ details.inspectoExtension.status = "missing_host_ide_binary";
3628
+ if (!silent) {
3629
+ log.warn(
3630
+ formatIntegrationStep(
3631
+ 3,
3632
+ `Could not verify Inspecto extension installation in ${getHostIdeLabel(resolvedHostIde.ide)}`
3633
+ )
3634
+ );
3635
+ log.hint(
3636
+ `No ${getHostIdeLabel(resolvedHostIde.ide)} CLI binary was found. Automatic extension install and workspace opening may not work.`
3637
+ );
3638
+ }
3639
+ return {
3640
+ status: "preview_blocked",
3641
+ message: getPreviewBlockedMessage(),
3642
+ nextStep,
3643
+ details
3644
+ };
3645
+ }
3646
+ if (options.inspectoVsix && !await exists(options.inspectoVsix)) {
3647
+ const nextStep = `Provide a valid local VSIX path for ${getHostIdeLabel(resolvedHostIde.ide)} or remove --inspecto-vsix, then rerun the command.`;
3648
+ details.inspectoExtension.status = "missing_local_vsix";
3649
+ if (!silent) {
3650
+ log.warn(
3651
+ formatIntegrationStep(
3652
+ 3,
3653
+ `Could not verify the local Inspecto VSIX for ${getHostIdeLabel(resolvedHostIde.ide)}`
3654
+ )
3655
+ );
3656
+ log.hint(`The local VSIX path does not exist: ${options.inspectoVsix}`);
3657
+ }
3658
+ return {
3659
+ status: "preview_blocked",
3660
+ message: getPreviewBlockedMessage(),
3661
+ nextStep,
3662
+ details
3663
+ };
3664
+ }
3665
+ details.inspectoExtension.status = "preview_ready";
3666
+ if (!silent) {
3667
+ log.info(formatIntegrationStep(3, "Previewed Inspecto extension installation"));
3668
+ log.hint(
3669
+ options.inspectoVsix ? `Local VSIX (${options.inspectoVsix})` : `Marketplace install in ${getHostIdeLabel(resolvedHostIde.ide)}`
3670
+ );
3671
+ }
3672
+ if (!dispatchMode.ready) {
3673
+ const nextStep = `Install the ${getAssistantLabel(assistant)} plugin in ${getHostIdeLabel(resolvedHostIde.ide)} or install the \`${getAssistantCliName(assistant)}\` CLI, then rerun the command.`;
3674
+ if (!silent) {
3675
+ log.warn(
3676
+ formatIntegrationStep(
3677
+ 4,
3678
+ `Could not resolve a runnable ${getAssistantLabel(assistant)} runtime`
3679
+ )
3680
+ );
3681
+ log.hint(nextStep);
3682
+ }
3683
+ return {
3684
+ status: "preview_blocked",
3685
+ message: getPreviewBlockedMessage(),
3686
+ nextStep,
3687
+ details
3688
+ };
3689
+ }
3690
+ if (!silent) {
3691
+ log.info(formatIntegrationStep(4, `Previewed ${getAssistantLabel(assistant)} runtime`));
3692
+ log.hint(getDispatchModeLabel(assistant, resolvedHostIde.ide, dispatchMode.mode));
3693
+ }
3694
+ if (cwd) {
3695
+ if (!silent) {
3696
+ log.info(
3697
+ formatIntegrationStep(
3698
+ 5,
3699
+ `Previewed workspace routing in ${getHostIdeLabel(resolvedHostIde.ide)}`
3700
+ )
3701
+ );
3702
+ log.hint(cwd);
3703
+ }
3704
+ }
3705
+ if (!silent) {
3706
+ log.info(formatIntegrationStep(6, "Previewed onboarding launch"));
3707
+ log.hint(launchUri);
3708
+ }
3709
+ return {
3710
+ status: "preview",
3711
+ message: getPreviewReadyMessage(),
3712
+ nextStep: "Run the same command again without --preview to apply the integration and launch onboarding.",
3713
+ details
3714
+ };
3715
+ }
3716
+ if (!silent) {
3717
+ log.success(formatIntegrationStep(2, "Resolved host IDE"));
3718
+ log.hint(
3719
+ `${getHostIdeLabel(resolvedHostIde.ide)} (${getHostIdeResolutionSourceLabel(resolvedHostIde.source)})`
3720
+ );
3721
+ }
3722
+ const installResult = await installExtension(
3723
+ false,
3724
+ resolvedHostIde.ide,
3725
+ true,
3726
+ options.inspectoVsix
3727
+ );
3728
+ details.inspectoExtension.status = installResult?.description ?? "not_prepared";
3729
+ if (!silent) {
3730
+ logInstallStep(resolvedHostIde.ide, installResult);
3731
+ }
3732
+ if (installResult?.type === "extension_installed") {
3733
+ if (!silent) {
3734
+ if (installResult.manual_action_required) {
3735
+ log.hint("Complete the IDE install prompt before retrying if onboarding does not appear.");
3736
+ } else {
3737
+ log.hint("Waiting briefly for the IDE extension to finish activating...");
3738
+ }
3739
+ }
3740
+ await new Promise((resolve) => setTimeout(resolve, 1500));
3741
+ } else {
3742
+ if (!silent) {
3743
+ log.warn(
3744
+ formatIntegrationStep(
3745
+ 3,
3746
+ `Could not prepare the Inspecto extension for ${getHostIdeLabel(resolvedHostIde.ide)}`
3747
+ )
3748
+ );
3749
+ log.hint("Automatic onboarding may fail until the Inspecto extension is installed.");
3750
+ }
3751
+ }
3752
+ if (cwd) {
3753
+ const openedWorkspace = await openIdeWorkspace(resolvedHostIde.ide, cwd);
3754
+ details.workspace.opened = openedWorkspace;
3755
+ if (openedWorkspace) {
3756
+ if (!silent) {
3757
+ log.success(
3758
+ formatIntegrationStep(5, `Opened workspace in ${getHostIdeLabel(resolvedHostIde.ide)}`)
3759
+ );
3760
+ log.hint(cwd);
3761
+ }
3762
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3763
+ } else {
3764
+ if (!silent) {
3765
+ log.warn(
3766
+ formatIntegrationStep(
3767
+ 5,
3768
+ `Could not open the workspace in ${getHostIdeLabel(resolvedHostIde.ide)}`
3769
+ )
3770
+ );
3771
+ log.hint(cwd);
3772
+ }
3773
+ }
3774
+ }
3775
+ if (!dispatchMode.ready) {
3776
+ const nextStep = `Install the ${getAssistantLabel(assistant)} plugin in ${getHostIdeLabel(resolvedHostIde.ide)} or install the \`${getAssistantCliName(assistant)}\` CLI, then rerun the command.`;
3777
+ if (!silent) {
3778
+ log.warn(
3779
+ formatIntegrationStep(
3780
+ 4,
3781
+ `Could not resolve a runnable ${getAssistantLabel(assistant)} runtime`
3782
+ )
3783
+ );
3784
+ log.hint(nextStep);
3785
+ }
3786
+ return {
3787
+ status: "blocked",
3788
+ message: getRuntimeBlockedMessage(assistant, resolvedHostIde.ide),
3789
+ nextStep,
3790
+ details
3791
+ };
3792
+ }
3793
+ if (!silent) {
3794
+ log.success(formatIntegrationStep(4, `Resolved ${getAssistantLabel(assistant)} runtime`));
3795
+ log.hint(getDispatchModeLabel(assistant, resolvedHostIde.ide, dispatchMode.mode));
3796
+ }
3797
+ const workspaceOpenFailed = Boolean(cwd) && details.workspace?.opened === false;
3798
+ const launched = await openUri(launchUri);
3799
+ if (launched) {
3800
+ if (!silent) {
3801
+ log.success(
3802
+ formatIntegrationStep(6, `Launched onboarding in ${getHostIdeLabel(resolvedHostIde.ide)}`)
3803
+ );
3804
+ log.hint(`${getAssistantLabel(assistant)} via ${dispatchMode.mode ?? "default"} mode`);
3805
+ }
3806
+ if (workspaceOpenFailed) {
3807
+ const nextStep = `If the wrong IDE window received onboarding, open ${cwd} in ${getHostIdeLabel(resolvedHostIde.ide)} and rerun the command from that project.`;
3808
+ return {
3809
+ status: "partial",
3810
+ message: `Onboarding opened in ${getHostIdeLabel(resolvedHostIde.ide)} for ${getAssistantLabel(assistant)}, but Inspecto could not open the target workspace first.`,
3811
+ nextStep,
3812
+ details
3813
+ };
3814
+ }
3815
+ return {
3816
+ status: installResult ? "launched" : "partial",
3817
+ message: installResult ? `Onboarding opened in ${getHostIdeLabel(resolvedHostIde.ide)} for ${getAssistantLabel(assistant)}.` : `Onboarding opened in ${getHostIdeLabel(resolvedHostIde.ide)} for ${getAssistantLabel(assistant)}, but the Inspecto extension may still need manual setup.`,
3818
+ ...installResult ? {} : {
3819
+ nextStep: `Install the Inspecto extension in ${getHostIdeLabel(resolvedHostIde.ide)} if IDE-side features are missing.`
3820
+ },
3821
+ details
3822
+ };
3823
+ } else {
3824
+ if (!silent) {
3825
+ log.warn(
3826
+ formatIntegrationStep(
3827
+ 6,
3828
+ `Could not launch onboarding in ${getHostIdeLabel(resolvedHostIde.ide)}`
3829
+ )
3830
+ );
3831
+ log.hint(launchUri);
3832
+ }
3833
+ return {
3834
+ status: "blocked",
3835
+ message: getLaunchBlockedMessage(resolvedHostIde.ide),
3836
+ nextStep: launchUri,
3837
+ details
3838
+ };
3839
+ }
3840
+ }
3841
+ function shouldAutoSend(assistant, ide) {
3842
+ if (assistant === "copilot") return true;
3843
+ if (assistant === "codex") return true;
3844
+ return false;
3845
+ }
3846
+ function getAssistantLabel(assistant) {
3847
+ return getDualModeAssistantCapability(assistant)?.label ?? assistant;
3848
+ }
3849
+ function getAssistantCliName(assistant) {
3850
+ return getDualModeAssistantCapability(assistant)?.cliBin ?? assistant;
3851
+ }
3852
+ function formatIntegrationStep(step, text) {
3853
+ return `Step ${step}/${TOTAL_STEPS}: ${text}`;
3854
+ }
3855
+ function logInstallStep(ide, installResult) {
3856
+ if (!installResult) {
3857
+ return;
3858
+ }
3859
+ if (installResult.description === "already_installed") {
3860
+ log.success(
3861
+ formatIntegrationStep(3, `Inspecto extension already installed in ${getHostIdeLabel(ide)}`)
3862
+ );
3863
+ log.hint(installResult.id ?? "inspecto.inspecto");
3864
+ return;
3865
+ }
3866
+ if (installResult.description === "opened_install_page") {
3867
+ log.warn(
3868
+ formatIntegrationStep(
3869
+ 3,
3870
+ `Opened the Inspecto extension install page in ${getHostIdeLabel(ide)}`
3871
+ )
3872
+ );
3873
+ log.hint("Finish the extension install in the IDE window, then rerun the command if needed.");
3874
+ return;
3875
+ }
3876
+ log.success(
3877
+ formatIntegrationStep(3, `Installed the Inspecto extension in ${getHostIdeLabel(ide)}`)
3878
+ );
3879
+ log.hint(installResult.id ?? "inspecto.inspecto");
3880
+ }
3881
+ function getDispatchModeLabel(assistant, ide, mode) {
3882
+ if (mode === "cli") {
3883
+ return `CLI fallback (\`${getAssistantCliName(assistant)}\`)`;
3884
+ }
3885
+ if (mode === "extension") {
3886
+ return `${getAssistantLabel(assistant)} plugin in ${getHostIdeLabel(ide)}`;
3887
+ }
3888
+ return "Default runtime";
3889
+ }
3890
+
3891
+ // src/commands/integration-install.ts
3892
+ var REPO_RAW_BASE = "https://raw.githubusercontent.com/inspecto-dev/inspecto/main";
3893
+ var TOTAL_STEPS2 = 6;
3894
+ var INTEGRATION_MANIFESTS = [
3895
+ {
3896
+ assistant: "codex",
3897
+ type: "native-skill",
3898
+ installTarget: ".agents/skills/",
3899
+ preferredInstall: "npx @inspecto-dev/cli integrations install codex --host-ide <vscode|cursor|trae|trae-cn>",
3900
+ cliSupported: true
3901
+ },
3902
+ {
3903
+ assistant: "claude-code",
3904
+ type: "native-skill",
3905
+ installTarget: ".claude/skills/ or ~/.claude/skills/",
3906
+ preferredInstall: "npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn>",
3907
+ cliSupported: true
3908
+ },
3909
+ {
3910
+ assistant: "copilot",
3911
+ type: "native-skill",
3912
+ installTarget: ".github/skills/inspecto-onboarding/",
3913
+ preferredInstall: "npx @inspecto-dev/cli integrations install copilot --host-ide <vscode|cursor|trae|trae-cn>",
3914
+ cliSupported: true
3915
+ },
3916
+ {
3917
+ assistant: "cursor",
3918
+ type: "native-skill",
3919
+ installTarget: ".cursor/skills/inspecto-onboarding/",
3920
+ preferredInstall: "npx @inspecto-dev/cli integrations install cursor --host-ide <vscode|cursor|trae|trae-cn>",
3921
+ cliSupported: true
3922
+ },
3923
+ {
3924
+ assistant: "gemini",
3925
+ type: "native-skill",
3926
+ installTarget: ".gemini/skills/inspecto-onboarding/",
3927
+ preferredInstall: "npx @inspecto-dev/cli integrations install gemini --host-ide <vscode|cursor|trae|trae-cn>",
3928
+ cliSupported: true
3929
+ },
3930
+ {
3931
+ assistant: "trae",
3932
+ type: "native-skill",
3933
+ installTarget: ".trae/skills/inspecto-onboarding/",
3934
+ preferredInstall: "npx @inspecto-dev/cli integrations install trae --host-ide <vscode|cursor|trae|trae-cn>",
3935
+ cliSupported: true
3936
+ },
3937
+ {
3938
+ assistant: "coco",
3939
+ type: "native-skill",
3940
+ installTarget: ".traecli/skills/inspecto-onboarding/",
3941
+ preferredInstall: "npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>",
3942
+ cliSupported: true
3943
+ }
3944
+ ];
3945
+ async function installIntegration(assistant, options = {}) {
3946
+ const plan2 = resolveInstallPlan(assistant, options);
3947
+ const manifest = getIntegrationManifest(assistant);
3948
+ const silent = options.json ?? false;
3949
+ if (!silent) {
3950
+ log.header("Inspecto Integration Install");
3951
+ }
3952
+ if (!options.preview) {
3953
+ const existingFiles = /* @__PURE__ */ new Map();
3954
+ for (const asset of plan2.assets) {
3955
+ if (await exists(asset.target)) {
3956
+ if (options.force) {
3957
+ } else if (manifest.type === "context-template") {
3958
+ const originalContent = await fs3.readFile(asset.target, "utf-8");
3959
+ existingFiles.set(asset.target, originalContent);
3960
+ if (!silent) {
3961
+ log.info(`File ${asset.target} already exists. Content will be appended safely.`);
3962
+ }
3963
+ } else {
3964
+ throw new Error(
3965
+ `Refusing to overwrite existing file: ${asset.target}. Re-run with --force if you want to replace it.`
3966
+ );
3967
+ }
3968
+ }
3969
+ }
3970
+ const downloadedAssets = [];
3971
+ for (const asset of plan2.assets) {
3972
+ let content = await loadAsset(asset);
3973
+ if (existingFiles.has(asset.target)) {
3974
+ const existingContent = existingFiles.get(asset.target);
3975
+ if (!existingContent.includes("Inspecto Onboarding") && !existingContent.includes("inspecto-onboarding")) {
3976
+ content = `${existingContent}
3977
+
3978
+ ---
3979
+
3980
+ ${content}`;
3981
+ } else {
3982
+ if (!silent) {
3983
+ log.info(
3984
+ `Skipping ${asset.target} as it seems to already contain Inspecto rules. Use --force to overwrite.`
3985
+ );
3986
+ }
3987
+ continue;
3988
+ }
3989
+ }
3990
+ downloadedAssets.push({ asset, content });
3991
+ }
3992
+ for (const { asset, content } of downloadedAssets) {
3993
+ await writeFile(asset.target, content);
3994
+ if (asset.executable) {
3995
+ await fs3.chmod(asset.target, 493);
3996
+ }
3997
+ }
3998
+ }
3999
+ const stepOneMessage = options.preview ? formatIntegrationStep2(1, `Previewing ${getAssistantLabel2(assistant)} integration assets`) : formatIntegrationStep2(1, `Installed ${getAssistantLabel2(assistant)} integration assets`);
4000
+ if (!silent) {
4001
+ if (options.preview) {
4002
+ log.info(stepOneMessage);
4003
+ } else {
4004
+ log.success(stepOneMessage);
4005
+ }
4006
+ for (const asset of plan2.assets) {
4007
+ log.hint(asset.target);
4008
+ }
4009
+ if (!options.preview) {
4010
+ log.hint(plan2.nextStep);
4011
+ }
4012
+ }
4013
+ if (shouldSkipAutomationForInstall(options)) {
4014
+ const message2 = `Installed ${getAssistantLabel2(assistant)} integration assets. User-level installs only write integration assets and do not launch onboarding automatically.`;
4015
+ const nextStep = options.ide ? `Run the install command again from your target project root with --host-ide ${options.ide} when you want to launch onboarding automatically.` : "Run the install command again from your target project root with --host-ide <vscode|cursor|trae|trae-cn> when you want to launch onboarding automatically.";
4016
+ const result2 = {
4017
+ status: "partial",
4018
+ assistant,
4019
+ preview: options.preview ?? false,
4020
+ assets: plan2.assets.map((asset) => asset.target),
4021
+ message: message2,
4022
+ nextStep,
4023
+ automation: {
4024
+ status: "blocked",
4025
+ message: message2,
4026
+ nextStep
4027
+ }
4028
+ };
4029
+ if (options.json) {
4030
+ return writeCommandOutput(result2, true, () => {
4031
+ });
4032
+ }
4033
+ log.ready(message2);
4034
+ if (options.ide) {
4035
+ log.hint(
4036
+ `The provided --host-ide value is saved only as a rerun hint for later; this command does not open ${formatHostIdeLabel(options.ide)} for user-level installs.`
4037
+ );
4038
+ }
4039
+ log.hint(nextStep);
4040
+ return result2;
4041
+ }
4042
+ const automationResult = await runIntegrationAutomation(
4043
+ assistant,
4044
+ { ...options, silent },
4045
+ process.cwd()
4046
+ );
4047
+ const result = {
4048
+ status: automationResult.status,
4049
+ assistant,
4050
+ preview: options.preview ?? false,
4051
+ assets: plan2.assets.map((asset) => asset.target),
4052
+ message: automationResult.message,
4053
+ ...automationResult.nextStep ? { nextStep: automationResult.nextStep } : {},
4054
+ automation: automationResult
4055
+ };
4056
+ if (options.json) {
4057
+ return writeCommandOutput(result, true, () => {
4058
+ });
4059
+ }
4060
+ if (automationResult.status === "launched") {
4061
+ log.ready(automationResult.message);
4062
+ return result;
4063
+ }
4064
+ if (automationResult.status === "preview") {
4065
+ log.ready(automationResult.message);
4066
+ if (automationResult.nextStep) {
4067
+ log.hint(automationResult.nextStep);
4068
+ }
4069
+ return result;
4070
+ }
4071
+ if (automationResult.status === "partial" || automationResult.status === "preview_blocked") {
4072
+ log.warn(automationResult.message);
4073
+ if (automationResult.nextStep) {
4074
+ log.hint(automationResult.nextStep);
4075
+ }
4076
+ return result;
4077
+ }
4078
+ log.warn(automationResult.message);
4079
+ if (automationResult.nextStep) {
4080
+ log.hint(automationResult.nextStep);
4081
+ }
4082
+ return result;
4083
+ }
4084
+ function shouldSkipAutomationForInstall(options) {
4085
+ return options.scope === "user" && !options.preview;
4086
+ }
4087
+ function describeIntegration(assistant, options = {}) {
4088
+ const manifest = getIntegrationManifest(assistant);
4089
+ const targets = manifest.cliSupported ? resolveInstallPlan(assistant, options).assets.map((asset) => asset.target) : [manifest.installTarget];
4090
+ return {
4091
+ assistant: manifest.assistant,
4092
+ type: manifest.type,
4093
+ targets,
4094
+ preferredInstall: manifest.preferredInstall,
4095
+ cliSupported: manifest.cliSupported
4096
+ };
4097
+ }
4098
+ function printIntegrationList() {
4099
+ log.header("Inspecto Integrations");
4100
+ for (const manifest of INTEGRATION_MANIFESTS) {
4101
+ const support = manifest.cliSupported ? "CLI" : "native installer";
4102
+ log.info(`${manifest.assistant} \u2014 ${manifest.type} \u2014 ${manifest.installTarget} \u2014 ${support}`);
4103
+ }
4104
+ }
4105
+ function printIntegrationPath(assistant, options = {}) {
4106
+ const description = describeIntegration(assistant, options);
4107
+ log.header(`Inspecto Integration Paths: ${description.assistant}`);
4108
+ for (const target of description.targets) {
4109
+ log.info(target);
4110
+ }
4111
+ if (description.cliSupported) {
4112
+ log.hint(`Preferred install: ${description.preferredInstall}`);
4113
+ } else {
4114
+ log.hint(`Native install required: ${description.preferredInstall}`);
4115
+ }
4116
+ }
4117
+ function resolveInstallPlan(assistant, options) {
4118
+ switch (assistant) {
4119
+ case "codex":
4120
+ return resolveCodexPlan(options);
4121
+ case "claude-code":
4122
+ return resolveClaudeCodePlan(options);
4123
+ case "copilot":
4124
+ return resolveCopilotPlan(options);
4125
+ case "cursor":
4126
+ return resolveCursorPlan(options);
4127
+ case "gemini":
4128
+ return {
4129
+ assets: [
4130
+ {
4131
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-gemini/SKILL.md`,
4132
+ target: ".gemini/skills/inspecto-onboarding/SKILL.md",
4133
+ localSource: "skills/inspecto-onboarding-gemini/SKILL.md"
4134
+ }
4135
+ ],
4136
+ successMessage: "Installed Gemini skill to .gemini/skills/inspecto-onboarding/SKILL.md",
4137
+ nextStep: "Start a new Gemini CLI session and use /skills list to verify."
4138
+ };
4139
+ case "trae":
4140
+ return {
4141
+ assets: [
4142
+ {
4143
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
4144
+ target: ".trae/skills/inspecto-onboarding/SKILL.md",
4145
+ localSource: "skills/inspecto-onboarding-trae/SKILL.md"
4146
+ }
4147
+ ],
4148
+ successMessage: "Installed Trae skill to .trae/skills/inspecto-onboarding/SKILL.md",
4149
+ nextStep: "Open a new Trae chat and verify the inspecto-onboarding skill is available."
4150
+ };
4151
+ case "coco":
4152
+ return {
4153
+ assets: [
4154
+ {
4155
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
4156
+ target: ".traecli/skills/inspecto-onboarding/SKILL.md",
4157
+ localSource: "skills/inspecto-onboarding-trae/SKILL.md"
4158
+ }
4159
+ ],
4160
+ successMessage: "Installed Coco skill to .traecli/skills/inspecto-onboarding/SKILL.md",
4161
+ nextStep: "Start a new Coco session."
4162
+ };
4163
+ default:
4164
+ throw new Error(`Unknown assistant: ${assistant}`);
4165
+ }
4166
+ }
4167
+ function getIntegrationManifest(assistant) {
4168
+ const manifest = INTEGRATION_MANIFESTS.find((item) => item.assistant === assistant);
4169
+ if (!manifest) {
4170
+ throw new Error(
4171
+ `Unknown assistant: ${assistant}. Run 'inspecto integrations list' to see available targets.`
4172
+ );
4173
+ }
4174
+ return manifest;
4175
+ }
4176
+ function formatIntegrationStep2(step, text) {
4177
+ return `Step ${step}/${TOTAL_STEPS2}: ${text}`;
4178
+ }
4179
+ function getAssistantLabel2(assistant) {
4180
+ if (assistant === "claude-code") return "Claude Code";
4181
+ if (assistant === "codex") return "Codex";
4182
+ if (assistant === "copilot") return "GitHub Copilot";
4183
+ if (assistant === "cursor") return "Cursor";
4184
+ if (assistant === "gemini") return "Gemini";
4185
+ if (assistant === "trae") return "Trae";
4186
+ if (assistant === "coco") return "Coco";
4187
+ return assistant;
4188
+ }
4189
+ function formatHostIdeLabel(ide) {
4190
+ if (ide === "vscode") return "VS Code";
4191
+ if (ide === "cursor") return "Cursor";
4192
+ if (ide === "trae") return "Trae";
4193
+ if (ide === "trae-cn") return "Trae CN";
4194
+ return ide;
4195
+ }
4196
+ function resolveCodexPlan(options) {
4197
+ const scope = options.scope ?? "project";
4198
+ if (options.mode !== void 0) {
4199
+ throw new Error("`--mode` is not supported for codex.");
4200
+ }
4201
+ const baseDir = scope === "user" ? path17.join(homedir2(), ".agents/skills/inspecto-onboarding-codex") : ".agents/skills/inspecto-onboarding-codex";
4202
+ return {
4203
+ assets: [
4204
+ {
4205
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/SKILL.md`,
4206
+ target: path17.join(baseDir, "SKILL.md"),
4207
+ localSource: "skills/inspecto-onboarding-codex/SKILL.md"
4208
+ },
4209
+ {
4210
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/agents/openai.yaml`,
4211
+ target: path17.join(baseDir, "agents/openai.yaml"),
4212
+ localSource: "skills/inspecto-onboarding-codex/agents/openai.yaml"
4213
+ },
4214
+ {
4215
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh`,
4216
+ target: path17.join(baseDir, "scripts/run-inspecto.sh"),
4217
+ executable: true,
4218
+ localSource: "skills/inspecto-onboarding-codex/scripts/run-inspecto.sh"
4219
+ }
4220
+ ],
4221
+ successMessage: `Installed Codex skill to ${baseDir}`,
4222
+ nextStep: "Restart Codex or start a new Codex session to load the skill."
4223
+ };
4224
+ }
4225
+ function resolveClaudeCodePlan(options) {
4226
+ const scope = options.scope ?? "project";
4227
+ const unsupportedMode = options.mode !== void 0;
4228
+ if (unsupportedMode) {
4229
+ throw new Error(
4230
+ "`--mode` is not supported for claude-code. Use `--scope project|user` instead."
4231
+ );
4232
+ }
4233
+ if (scope !== "project" && scope !== "user") {
4234
+ throw new Error(`Unknown Claude Code scope: ${scope}`);
4235
+ }
4236
+ const baseDir = scope === "user" ? path17.join(homedir2(), ".claude/skills/inspecto-onboarding-claude-code") : ".claude/skills/inspecto-onboarding-claude-code";
4237
+ return {
4238
+ assets: [
4239
+ {
4240
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/SKILL.md`,
4241
+ target: path17.join(baseDir, "SKILL.md"),
4242
+ localSource: "skills/inspecto-onboarding-claude-code/SKILL.md"
4243
+ },
4244
+ {
4245
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/agents/openai.yaml`,
4246
+ target: path17.join(baseDir, "agents/openai.yaml"),
4247
+ localSource: "skills/inspecto-onboarding-claude-code/agents/openai.yaml"
4248
+ },
4249
+ {
4250
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh`,
4251
+ target: path17.join(baseDir, "scripts/run-inspecto.sh"),
4252
+ executable: true,
4253
+ localSource: "skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh"
4254
+ }
4255
+ ],
4256
+ successMessage: `Installed Claude Code skill to ${baseDir}`,
4257
+ nextStep: "Restart Claude Code to load the new skill."
4258
+ };
4259
+ }
4260
+ function resolveCopilotPlan(options) {
4261
+ const mode = options.mode ?? "skills";
4262
+ if (options.scope !== void 0) {
4263
+ throw new Error(
4264
+ "`--scope` is not supported for copilot. Use `--mode skills|instructions|agents` instead."
4265
+ );
4266
+ }
4267
+ switch (mode) {
4268
+ case "skills":
4269
+ case "instructions":
4270
+ // Legacy fallback gracefully acts as skills mode now
4271
+ case "agents":
4272
+ return {
4273
+ assets: [
4274
+ {
4275
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-copilot/SKILL.md`,
4276
+ target: ".github/skills/inspecto-onboarding/SKILL.md",
4277
+ localSource: "skills/inspecto-onboarding-copilot/SKILL.md"
4278
+ }
4279
+ ],
4280
+ successMessage: "Installed Copilot skill to .github/skills/inspecto-onboarding/SKILL.md",
4281
+ nextStep: "Open a new Copilot chat or agent session."
4282
+ };
4283
+ default:
4284
+ throw new Error(`Unknown Copilot mode: ${mode}`);
4285
+ }
4286
+ }
4287
+ function resolveCursorPlan(options) {
4288
+ const mode = options.mode ?? "skills";
4289
+ if (options.scope !== void 0) {
4290
+ throw new Error("`--scope` is not supported for cursor. Use `--mode skills|agents` instead.");
4291
+ }
4292
+ switch (mode) {
4293
+ case "skills":
4294
+ case "rules":
4295
+ // Legacy fallback gracefully acts as skills mode now
4296
+ case "agents":
4297
+ return {
4298
+ assets: [
4299
+ {
4300
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-cursor/SKILL.md`,
4301
+ target: ".cursor/skills/inspecto-onboarding/SKILL.md",
4302
+ localSource: "skills/inspecto-onboarding-cursor/SKILL.md"
4303
+ }
4304
+ ],
4305
+ successMessage: "Installed Cursor skill to .cursor/skills/inspecto-onboarding/SKILL.md",
4306
+ nextStep: "Open a new Cursor chat."
4307
+ };
4308
+ default:
4309
+ throw new Error(`Unknown Cursor mode: ${mode}`);
4310
+ }
4311
+ }
4312
+ async function loadAsset(asset) {
4313
+ if (asset.localSource) {
4314
+ const localPath = await resolveBundledAssetPath(asset.localSource);
4315
+ if (localPath) {
4316
+ return await fs3.readFile(localPath, "utf-8");
4317
+ }
4318
+ }
4319
+ return await downloadAsset(asset.source);
4320
+ }
4321
+ async function resolveBundledAssetPath(relativePath) {
4322
+ const startDir = path17.dirname(fileURLToPath(import.meta.url));
4323
+ let currentDir = startDir;
4324
+ for (let depth = 0; depth < 8; depth += 1) {
4325
+ const candidate = path17.join(currentDir, relativePath);
4326
+ if (await exists(candidate)) {
4327
+ return candidate;
4328
+ }
4329
+ const parent = path17.dirname(currentDir);
4330
+ if (parent === currentDir) break;
4331
+ currentDir = parent;
4332
+ }
4333
+ return null;
4334
+ }
4335
+ async function downloadAsset(source) {
4336
+ let response;
4337
+ try {
4338
+ response = await fetch(source);
4339
+ } catch (error) {
4340
+ const reason = error instanceof Error ? error.message : String(error);
4341
+ const wrappedError = new Error(`Failed to download ${source}: ${reason}`);
4342
+ wrappedError.cause = error;
4343
+ throw wrappedError;
4344
+ }
4345
+ if (!response.ok) {
4346
+ throw new Error(`Failed to download ${source}: ${response.status} ${response.statusText}`);
4347
+ }
4348
+ return await response.text();
4349
+ }
4350
+
4351
+ // src/utils/process.ts
4352
+ function exitProcess(code) {
4353
+ process.exit(code);
4354
+ }
4355
+
4356
+ // src/commands/integration-doctor.ts
4357
+ var INTEGRATION_DOCTOR_SCHEMA_VERSION = "1";
4358
+ function printIntegrationDoctorResult(result, options = {}) {
4359
+ log.header("Inspecto Integration Doctor");
4360
+ log.info(`Assistant: ${result.assistant}`);
4361
+ const compact = options.compact ?? false;
4362
+ if (!compact && result.assets.length > 0) {
4363
+ log.info("Asset targets:");
4364
+ for (const asset of result.assets) {
4365
+ log.hint(asset);
4366
+ }
4367
+ }
4368
+ const details = result.automation.details;
4369
+ if (details?.hostIde?.id && details.hostIde.label) {
4370
+ const hostIdeDetail = details.hostIde.source ? `${details.hostIde.label} (${details.hostIde.source})` : details.hostIde.label;
4371
+ log.info(`Host IDE: ${hostIdeDetail}`);
4372
+ }
4373
+ if (!compact && details?.inspectoExtension) {
4374
+ log.info(
4375
+ `Inspecto extension: ${details.inspectoExtension.source} (${details.inspectoExtension.reference})`
4376
+ );
4377
+ }
4378
+ if (details?.runtime) {
4379
+ const mode = details.runtime.mode ?? "default";
4380
+ const readiness = details.runtime.ready ? ` via ${mode}` : " unavailable";
4381
+ log.info(`Runtime: ${details.runtime.assistant}${readiness}`);
4382
+ }
4383
+ if (details?.workspace?.path) {
4384
+ log.info(`Workspace: ${details.workspace.path}`);
4385
+ }
4386
+ if (!compact && details?.onboarding?.uri) {
4387
+ log.info(`Onboarding URI: ${details.onboarding.uri}`);
4388
+ }
4389
+ if (result.status === "ok") {
4390
+ log.ready(result.message);
4391
+ } else {
4392
+ log.warn(result.message);
4393
+ if (result.nextStep) {
4394
+ log.hint(result.nextStep);
4395
+ }
4396
+ }
4397
+ }
4398
+ async function integrationDoctor(assistant, options = {}) {
4399
+ const description = describeIntegration(assistant, options);
4400
+ const automation = await runIntegrationAutomation(
4401
+ assistant,
4402
+ {
4403
+ ...options.ide ? { ide: options.ide } : {},
4404
+ ...options.inspectoVsix ? { inspectoVsix: options.inspectoVsix } : {},
4405
+ preview: true,
4406
+ silent: true
4407
+ },
4408
+ process.cwd()
4409
+ );
4410
+ const result = {
4411
+ schemaVersion: INTEGRATION_DOCTOR_SCHEMA_VERSION,
4412
+ status: automation.status === "preview" ? "ok" : "blocked",
4413
+ assistant,
4414
+ assets: description.targets,
4415
+ message: automation.message,
4416
+ ...automation.nextStep ? { nextStep: automation.nextStep } : {},
4417
+ automation
4418
+ };
4419
+ const written = writeCommandOutput(
4420
+ result,
4421
+ options.json ?? false,
4422
+ (value) => printIntegrationDoctorResult(value, {
4423
+ ...options.compact !== void 0 ? { compact: options.compact } : {}
4424
+ })
4425
+ );
4426
+ if (result.status === "blocked" && options.failOnBlocked) {
4427
+ exitProcess(1);
4428
+ }
4429
+ return written;
4430
+ }
4431
+
3135
4432
  export {
3136
- exists,
3137
- writeFile,
3138
- log,
3139
4433
  writeCommandOutput,
3140
4434
  reportCommandError,
3141
4435
  apply,
@@ -3145,5 +4439,9 @@ export {
3145
4439
  doctor,
3146
4440
  onboard,
3147
4441
  plan,
3148
- teardown
4442
+ teardown,
4443
+ installIntegration,
4444
+ printIntegrationList,
4445
+ printIntegrationPath,
4446
+ integrationDoctor
3149
4447
  };