@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.
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-test.log +5160 -92
- package/CHANGELOG.md +20 -0
- package/README.md +126 -6
- package/dist/bin.js +59 -361
- package/dist/{chunk-PSYZB5GI.js → chunk-LJOKPCPD.js} +1389 -91
- package/dist/index.d.ts +74 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/bin.ts +85 -6
- package/src/commands/integration-automation.ts +485 -0
- package/src/commands/integration-dispatch-mode.ts +92 -0
- package/src/commands/integration-doctor.ts +117 -0
- package/src/commands/integration-host-ide.ts +156 -0
- package/src/commands/integration-install.ts +262 -101
- package/src/commands/onboard.ts +19 -0
- package/src/index.ts +1 -0
- package/src/inject/extension.ts +144 -24
- package/src/integrations/capabilities.ts +131 -0
- package/src/utils/process.ts +3 -0
- package/tests/extension-installer.test.ts +205 -0
- package/tests/install-wrapper.test.ts +45 -4
- package/tests/integration-automation.test.ts +491 -0
- package/tests/integration-dispatch-mode.test.ts +203 -0
- package/tests/integration-doctor.test.ts +165 -0
- package/tests/integration-host-ide.test.ts +172 -0
- package/tests/integration-install.test.ts +282 -20
- package/tests/onboard.test.ts +118 -0
- package/tests/shared-capabilities.test.ts +45 -0
|
@@ -90,7 +90,7 @@ function reportCommandError(error, options = {}) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// src/onboarding/apply.ts
|
|
93
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
883
|
+
const normalizedRepo = path6.resolve(devRepo);
|
|
679
884
|
return {
|
|
680
885
|
installSpec: [
|
|
681
|
-
shellQuote(
|
|
682
|
-
shellQuote(
|
|
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 =
|
|
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 =
|
|
775
|
-
const promptsPath =
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
1013
|
-
const normalized = relative.split(
|
|
1014
|
-
return normalized ||
|
|
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 ?
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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) =>
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
1337
|
-
exists(
|
|
1338
|
-
exists(
|
|
1339
|
-
exists(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
2734
|
+
const pluginPath = path12.join(root, "node_modules", "@inspecto-dev", "plugin");
|
|
2530
2735
|
if (await exists(pluginPath)) {
|
|
2531
|
-
const pkgJson = await readJSON(
|
|
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(
|
|
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 =
|
|
2608
|
-
const settingsLocalPath =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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 =
|
|
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(
|
|
3078
|
-
await removeDir(
|
|
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(
|
|
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
|
};
|