@inspecto-dev/cli 0.2.0-alpha.5 → 0.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +19 -20
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +93 -11
  4. package/bin/inspecto.js +5 -1
  5. package/dist/bin.d.ts +5 -1
  6. package/dist/bin.js +530 -49
  7. package/dist/chunk-FZS2TLXQ.js +3140 -0
  8. package/dist/index.d.ts +233 -2
  9. package/dist/index.js +17 -3
  10. package/package.json +3 -2
  11. package/src/bin.ts +286 -66
  12. package/src/commands/apply.ts +118 -0
  13. package/src/commands/detect.ts +59 -0
  14. package/src/commands/doctor.ts +225 -72
  15. package/src/commands/init.ts +143 -183
  16. package/src/commands/integration-install.ts +452 -0
  17. package/src/commands/onboard.ts +50 -0
  18. package/src/commands/plan.ts +41 -0
  19. package/src/detect/build-tool.ts +107 -3
  20. package/src/index.ts +17 -2
  21. package/src/inject/ast-injector.ts +17 -6
  22. package/src/inject/extension.ts +40 -22
  23. package/src/inject/gitignore.ts +10 -3
  24. package/src/instructions.ts +60 -46
  25. package/src/onboarding/apply.ts +364 -0
  26. package/src/onboarding/context.ts +36 -0
  27. package/src/onboarding/planner.ts +284 -0
  28. package/src/onboarding/session.ts +434 -0
  29. package/src/onboarding/target-resolution.ts +116 -0
  30. package/src/prompts.ts +54 -11
  31. package/src/types.ts +184 -0
  32. package/src/utils/fs.ts +2 -1
  33. package/src/utils/logger.ts +9 -0
  34. package/src/utils/output.ts +40 -0
  35. package/tests/apply.test.ts +583 -0
  36. package/tests/ast-injector.test.ts +50 -0
  37. package/tests/build-tool.test.ts +3 -5
  38. package/tests/detect.test.ts +94 -0
  39. package/tests/doctor.test.ts +224 -0
  40. package/tests/init.test.ts +364 -0
  41. package/tests/install-wrapper.test.ts +76 -0
  42. package/tests/instructions.test.ts +61 -0
  43. package/tests/integration-install.test.ts +294 -0
  44. package/tests/logger.test.ts +100 -0
  45. package/tests/onboard.test.ts +258 -0
  46. package/tests/plan.test.ts +713 -0
  47. package/tests/workspace-build-tool.test.ts +75 -0
  48. package/.turbo/turbo-test.log +0 -16
  49. package/dist/chunk-MIHQGC3L.js +0 -1720
package/dist/index.d.ts CHANGED
@@ -2,6 +2,188 @@
2
2
  type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm';
3
3
  /** Supported build tools (v1) */
4
4
  type BuildTool = 'vite' | 'webpack' | 'rspack' | 'rsbuild' | 'esbuild' | 'rollup';
5
+ /** Machine-readable status for onboarding commands */
6
+ type CommandStatus = 'ok' | 'warning' | 'blocked' | 'error';
7
+ /** Assistant-facing status for single-entry onboarding */
8
+ type OnboardStatus = 'success' | 'partial_success' | 'needs_target_selection' | 'needs_confirmation' | 'error';
9
+ /** Structured message emitted by onboarding commands */
10
+ interface CommandMessage {
11
+ code: string;
12
+ message: string;
13
+ }
14
+ interface OnboardingProvider {
15
+ id: string;
16
+ label: string;
17
+ supported: boolean;
18
+ preferredMode: 'cli' | 'extension';
19
+ }
20
+ /** Detected build tool with its config path */
21
+ interface BuildToolDetection {
22
+ tool: BuildTool;
23
+ configPath: string;
24
+ /** Human-readable label like "Vite (vite.config.ts)" */
25
+ label: string;
26
+ /** Whether this is a legacy rspack version (< 0.4.0) */
27
+ isLegacyRspack?: boolean;
28
+ /** Whether this is Webpack 4.x */
29
+ isLegacyWebpack?: boolean;
30
+ /** Relative package path when using --packages */
31
+ packagePath?: string;
32
+ }
33
+ /** Normalized onboarding context shared by detection/planning */
34
+ interface OnboardingContext {
35
+ root: string;
36
+ packageManager: PackageManager;
37
+ buildTools: {
38
+ supported: BuildToolDetection[];
39
+ unsupported: string[];
40
+ };
41
+ frameworks: {
42
+ supported: string[];
43
+ unsupported: string[];
44
+ };
45
+ ides: Array<{
46
+ ide: string;
47
+ supported: boolean;
48
+ }>;
49
+ providers: OnboardingProvider[];
50
+ }
51
+ interface OnboardingTargetCandidate {
52
+ packagePath: string;
53
+ configPath: string;
54
+ buildTool: BuildTool;
55
+ frameworks: string[];
56
+ automaticInjection: boolean;
57
+ }
58
+ interface OnboardingTargetResolution {
59
+ status: 'resolved' | 'needs_selection';
60
+ selected?: OnboardingTargetCandidate;
61
+ candidates: OnboardingTargetCandidate[];
62
+ reason: string;
63
+ }
64
+ interface OnboardingSummary {
65
+ headline: string;
66
+ changes: string[];
67
+ risks: string[];
68
+ manualFollowUp: string[];
69
+ }
70
+ interface OnboardingConfirmation {
71
+ required: boolean;
72
+ reason?: string;
73
+ question?: string;
74
+ }
75
+ interface OnboardingExecutionResult {
76
+ changedFiles: string[];
77
+ installedDependencies: string[];
78
+ selectedProviderDefault?: string;
79
+ selectedIDE?: string;
80
+ mutations: Mutation[];
81
+ }
82
+ interface OnboardingDiagnostics {
83
+ warnings: string[];
84
+ errors: string[];
85
+ nextSteps: string[];
86
+ }
87
+ interface OnboardingIdeExtensionStatus {
88
+ required: boolean;
89
+ installed: boolean;
90
+ manualRequired: boolean;
91
+ installCommand?: string;
92
+ marketplaceUrl?: string;
93
+ openVsxUrl?: string;
94
+ }
95
+ interface OnboardingVerification {
96
+ available: boolean;
97
+ devCommand?: string;
98
+ message: string;
99
+ }
100
+ interface ResolvedOnboardingSession {
101
+ status: OnboardStatus;
102
+ target: OnboardingTargetResolution;
103
+ summary: OnboardingSummary;
104
+ confirmation: OnboardingConfirmation;
105
+ verification: OnboardingVerification;
106
+ context: OnboardingContext;
107
+ plan: PlanResult;
108
+ projectRoot: string;
109
+ selectedIDE?: {
110
+ ide: string;
111
+ supported: boolean;
112
+ } | null;
113
+ providerDefault?: string;
114
+ }
115
+ interface OnboardCommandResult {
116
+ status: OnboardStatus;
117
+ target: OnboardingTargetResolution;
118
+ summary: OnboardingSummary;
119
+ confirmation: OnboardingConfirmation;
120
+ ideExtension?: OnboardingIdeExtensionStatus;
121
+ verification?: OnboardingVerification;
122
+ result?: OnboardingExecutionResult;
123
+ diagnostics?: OnboardingDiagnostics;
124
+ }
125
+ /** Machine-readable detection output for skill-first onboarding */
126
+ interface DetectionResult {
127
+ status: CommandStatus;
128
+ warnings: CommandMessage[];
129
+ blockers: CommandMessage[];
130
+ project: {
131
+ root: string;
132
+ packageManager: PackageManager;
133
+ };
134
+ environment: {
135
+ frameworks: string[];
136
+ unsupportedFrameworks: string[];
137
+ buildTools: BuildToolDetection[];
138
+ unsupportedBuildTools: string[];
139
+ ides: Array<{
140
+ ide: string;
141
+ supported: boolean;
142
+ }>;
143
+ providers: OnboardingProvider[];
144
+ };
145
+ }
146
+ /** Machine-readable onboarding plan output */
147
+ interface PlanResult {
148
+ status: CommandStatus;
149
+ warnings: CommandMessage[];
150
+ blockers: CommandMessage[];
151
+ strategy: 'supported' | 'manual' | 'unsupported';
152
+ actions: Array<{
153
+ type: 'install_dependency' | 'modify_file' | 'install_extension' | 'manual_step';
154
+ target: string;
155
+ description: string;
156
+ }>;
157
+ defaults: {
158
+ provider?: string;
159
+ ide?: string;
160
+ shared: boolean;
161
+ extension: boolean;
162
+ };
163
+ }
164
+ /** A single doctor diagnostic check/result */
165
+ interface DoctorDiagnostic {
166
+ code: string;
167
+ status: 'ok' | 'warning' | 'error';
168
+ message: string;
169
+ hints: string[];
170
+ details?: Record<string, unknown>;
171
+ }
172
+ /** Machine-readable diagnostics output for `inspecto doctor` */
173
+ interface DoctorResult {
174
+ status: CommandStatus;
175
+ summary: {
176
+ errors: number;
177
+ warnings: number;
178
+ };
179
+ project: {
180
+ root: string;
181
+ packageManager?: PackageManager;
182
+ };
183
+ errors: DoctorDiagnostic[];
184
+ warnings: DoctorDiagnostic[];
185
+ checks: DoctorDiagnostic[];
186
+ }
5
187
  /** Options passed to `inspecto init` */
6
188
  interface InitOptions {
7
189
  shared: boolean;
@@ -30,12 +212,61 @@ interface InstallLock {
30
212
  mutations: Mutation[];
31
213
  }
32
214
 
215
+ interface ApplyOnboardingResult {
216
+ status: CommandStatus;
217
+ mutations: Mutation[];
218
+ postInstall: {
219
+ installFailed: boolean;
220
+ injectionFailed: boolean;
221
+ manualExtensionInstallNeeded: boolean;
222
+ nextSteps: string[];
223
+ };
224
+ }
225
+
226
+ interface ApplyCommandOptions {
227
+ json?: boolean;
228
+ shared?: boolean;
229
+ skipInstall?: boolean;
230
+ dryRun?: boolean;
231
+ noExtension?: boolean;
232
+ }
233
+ interface ApplyCommandResult extends ApplyOnboardingResult {
234
+ plan: PlanResult;
235
+ }
236
+ declare function apply(options?: ApplyCommandOptions): Promise<ApplyCommandResult>;
237
+
238
+ declare function detect(json?: boolean): Promise<DetectionResult>;
239
+
33
240
  declare function init(options: InitOptions): Promise<void>;
34
241
 
35
- declare function doctor(): Promise<void>;
242
+ interface DoctorCommandOptions {
243
+ json?: boolean;
244
+ }
245
+ declare function collectDoctorResult(root?: string): Promise<DoctorResult>;
246
+ declare function doctor(options?: DoctorCommandOptions | boolean): Promise<DoctorResult>;
247
+
248
+ interface OnboardCommandOptions {
249
+ json?: boolean;
250
+ target?: string;
251
+ yes?: boolean;
252
+ shared?: boolean;
253
+ skipInstall?: boolean;
254
+ dryRun?: boolean;
255
+ noExtension?: boolean;
256
+ }
257
+ declare function onboard(options?: OnboardCommandOptions): Promise<OnboardCommandResult>;
258
+
259
+ declare function plan(json?: boolean): Promise<PlanResult>;
36
260
 
37
261
  declare function teardown(): Promise<void>;
38
262
 
263
+ interface ReportCommandErrorOptions {
264
+ debug?: boolean;
265
+ json?: boolean;
266
+ }
267
+ declare function writeCommandOutput<T>(result: T, json: boolean, renderText: (value: T) => void): T;
268
+ declare function reportCommandError(error: unknown, options?: ReportCommandErrorOptions): void;
269
+
39
270
  type Framework = 'react' | 'vue';
40
271
 
41
- export { type BuildTool, type Framework, type InitOptions, type InstallLock, type PackageManager, doctor, init, teardown };
272
+ export { type BuildTool, type DoctorDiagnostic, type DoctorResult, type Framework, type InitOptions, type InstallLock, type OnboardCommandResult, type OnboardStatus, type PackageManager, type ResolvedOnboardingSession, apply, collectDoctorResult, detect, doctor, init, onboard, plan, reportCommandError, teardown, writeCommandOutput };
package/dist/index.js CHANGED
@@ -1,10 +1,24 @@
1
1
  import {
2
+ apply,
3
+ collectDoctorResult,
4
+ detect,
2
5
  doctor,
3
6
  init,
4
- teardown
5
- } from "./chunk-MIHQGC3L.js";
7
+ onboard,
8
+ plan,
9
+ reportCommandError,
10
+ teardown,
11
+ writeCommandOutput
12
+ } from "./chunk-FZS2TLXQ.js";
6
13
  export {
14
+ apply,
15
+ collectDoctorResult,
16
+ detect,
7
17
  doctor,
8
18
  init,
9
- teardown
19
+ onboard,
20
+ plan,
21
+ reportCommandError,
22
+ teardown,
23
+ writeCommandOutput
10
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspecto-dev/cli",
3
- "version": "0.2.0-alpha.5",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "CLI tools for Inspecto onboarding and lifecycle management",
5
5
  "keywords": [
6
6
  "inspecto",
@@ -17,9 +17,10 @@
17
17
  "dependencies": {
18
18
  "cac": "^6.7.14",
19
19
  "magicast": "^0.5.2",
20
+ "ora": "^9.3.0",
20
21
  "picocolors": "^1.0.0",
21
22
  "prompts": "^2.4.2",
22
- "@inspecto-dev/types": "0.2.0-alpha.4"
23
+ "@inspecto-dev/types": "0.3.0-alpha.1"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@types/node": "^20.0.0",
package/src/bin.ts CHANGED
@@ -4,22 +4,34 @@
4
4
  // v1 scope: VS Code | React + Vue | Vite + Webpack + Rspack + esbuild + Rollup
5
5
  // ============================================================
6
6
  import { cac, type CAC } from 'cac'
7
+ import { fileURLToPath } from 'node:url'
7
8
  import { createRequire } from 'node:module'
9
+ import { apply } from './commands/apply.js'
10
+ import { detect } from './commands/detect.js'
8
11
  import { init } from './commands/init.js'
9
12
  import { doctor } from './commands/doctor.js'
13
+ import { onboard } from './commands/onboard.js'
14
+ import { plan } from './commands/plan.js'
10
15
  import { teardown } from './commands/teardown.js'
11
- import { log } from './utils/logger.js'
16
+ import {
17
+ type InstallIntegrationOptions,
18
+ installIntegration,
19
+ printIntegrationList,
20
+ printIntegrationPath,
21
+ } from './commands/integration-install.js'
22
+ import { reportCommandError } from './utils/output.js'
12
23
 
13
24
  const require = createRequire(import.meta.url)
14
25
  const { version } = require('../package.json')
15
26
 
16
- // cac object
17
- const cli: CAC = cac('inspecto')
18
-
19
27
  interface GlobalOptions {
20
28
  debug?: boolean
21
29
  }
22
30
 
31
+ interface JsonCommandOptions extends GlobalOptions {
32
+ json?: boolean
33
+ }
34
+
23
35
  interface InitCommandOptions extends GlobalOptions {
24
36
  shared?: boolean
25
37
  skipInstall?: boolean
@@ -30,74 +42,282 @@ interface InitCommandOptions extends GlobalOptions {
30
42
  force?: boolean
31
43
  }
32
44
 
33
- cli
34
- .command('init', 'Set up Inspecto in your project')
35
- .option('--shared', 'Share .inspecto/settings.json with your team via Git', { default: false })
36
- .option('--skip-install', 'Skip npm dependency installation', { default: false })
37
- .option('--dry-run', 'Preview changes without modifying files', { default: false })
38
- .option('--provider <provider>', 'Set default provider (e.g. copilot, claude-code)')
39
- .option('--no-extension', 'Skip VS Code extension installation', { default: false })
40
- .option('--packages <names>', '(Monorepo) Comma-separated list of packages to inject')
41
- .option('--force', 'Force initialization even if environment is unsupported', { default: false })
42
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
43
- .action(async (options: InitCommandOptions) => {
44
- try {
45
- await init({
46
- shared: options.shared ?? false,
47
- skipInstall: options.skipInstall ?? false,
48
- dryRun: options.dryRun ?? false,
49
- ...(options.provider && { provider: options.provider }), // Changed from prefer to provider
50
- noExtension: options.noExtension ?? false,
51
- ...(options.packages && {
52
- packages: options.packages.split(',').map((s: string) => s.trim()),
53
- }),
54
- force: options.force ?? false,
55
- })
56
- } catch (err) {
57
- log.error(err instanceof Error ? err.message : String(err))
58
- if (options.debug && err instanceof Error && err.stack) {
59
- console.error(err.stack)
60
- }
61
- process.exit(1)
62
- }
45
+ interface ApplyCommandOptions extends JsonCommandOptions {
46
+ shared?: boolean
47
+ skipInstall?: boolean
48
+ dryRun?: boolean
49
+ extension?: boolean
50
+ }
51
+
52
+ interface OnboardCliOptions extends JsonCommandOptions {
53
+ target?: string
54
+ yes?: boolean
55
+ shared?: boolean
56
+ skipInstall?: boolean
57
+ dryRun?: boolean
58
+ extension?: boolean
59
+ }
60
+
61
+ interface IntegrationCommandOptions extends GlobalOptions {
62
+ scope?: string
63
+ mode?: string
64
+ force?: boolean
65
+ }
66
+
67
+ const integrationScopes = ['project', 'user'] as const
68
+ const integrationModes = ['instructions', 'agents', 'rules'] as const
69
+
70
+ function exitWithError(error: unknown, options: JsonCommandOptions = {}): never {
71
+ reportCommandError(error, {
72
+ debug: options.debug ?? false,
73
+ json: options.json ?? false,
63
74
  })
75
+ process.exit(1)
76
+ }
77
+
78
+ export function createCli(_argv: readonly string[] = process.argv): CAC {
79
+ const cli: CAC = cac('inspecto')
64
80
 
65
- cli
66
- .command('doctor', 'Diagnose your Inspecto installation')
67
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
68
- .action(async (options: GlobalOptions) => {
69
- try {
70
- await doctor()
71
- } catch (err) {
72
- log.error(err instanceof Error ? err.message : String(err))
73
- if (options.debug && err instanceof Error && err.stack) {
74
- console.error(err.stack)
81
+ cli
82
+ .command('init', 'Set up Inspecto in your project')
83
+ .option('--shared', 'Share .inspecto/settings.json with your team via Git', { default: false })
84
+ .option('--skip-install', 'Skip npm dependency installation', { default: false })
85
+ .option('--dry-run', 'Preview changes without modifying files', { default: false })
86
+ .option('--provider <provider>', 'Set default provider (e.g. copilot, claude-code)')
87
+ .option('--no-extension', 'Skip VS Code extension installation', { default: false })
88
+ .option('--packages <names>', '(Monorepo) Comma-separated list of packages to inject')
89
+ .option('--force', 'Force initialization even if environment is unsupported', {
90
+ default: false,
91
+ })
92
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
93
+ .action(async (options: InitCommandOptions) => {
94
+ try {
95
+ await init({
96
+ shared: options.shared ?? false,
97
+ skipInstall: options.skipInstall ?? false,
98
+ dryRun: options.dryRun ?? false,
99
+ ...(options.provider && { provider: options.provider }),
100
+ noExtension: options.noExtension ?? false,
101
+ ...(options.packages && {
102
+ packages: options.packages.split(',').map((s: string) => s.trim()),
103
+ }),
104
+ force: options.force ?? false,
105
+ })
106
+ } catch (error) {
107
+ exitWithError(error, options)
75
108
  }
76
- process.exit(1)
77
- }
78
- })
109
+ })
79
110
 
80
- cli
81
- .command('teardown', 'Remove Inspecto from your project')
82
- .option('--debug', 'Enable debug mode to show full error traces', { default: false })
83
- .action(async (options: GlobalOptions) => {
84
- try {
85
- await teardown()
86
- } catch (err) {
87
- log.error(err instanceof Error ? err.message : String(err))
88
- if (options.debug && err instanceof Error && err.stack) {
89
- console.error(err.stack)
111
+ cli
112
+ .command('doctor', 'Diagnose your Inspecto installation')
113
+ .option('--json', 'Print machine-readable JSON output', { default: false })
114
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
115
+ .action(async (options: JsonCommandOptions) => {
116
+ try {
117
+ await doctor({ json: options.json ?? false })
118
+ } catch (error) {
119
+ exitWithError(error, options)
90
120
  }
91
- process.exit(1)
121
+ })
122
+
123
+ cli
124
+ .command('onboard', 'Run assistant-oriented Inspecto onboarding in one structured flow')
125
+ .option('--json', 'Print machine-readable JSON output', { default: false })
126
+ .option('--target <packagePath>', 'Select a monorepo target package explicitly')
127
+ .option('--yes', 'Accept a lightweight confirmation gate automatically', { default: false })
128
+ .option('--shared', 'Write shared Inspecto settings instead of local-only settings')
129
+ .option('--skip-install', 'Skip npm dependency installation')
130
+ .option('--dry-run', 'Preview changes without modifying files')
131
+ .option('--no-extension', 'Skip IDE extension installation')
132
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
133
+ .action(async (options: OnboardCliOptions) => {
134
+ try {
135
+ await onboard({
136
+ json: options.json ?? false,
137
+ ...(options.target && { target: options.target }),
138
+ yes: options.yes ?? false,
139
+ ...(options.shared !== undefined && { shared: options.shared }),
140
+ ...(options.skipInstall !== undefined && { skipInstall: options.skipInstall }),
141
+ ...(options.dryRun !== undefined && { dryRun: options.dryRun }),
142
+ ...(options.extension === false && { noExtension: true }),
143
+ })
144
+ } catch (error) {
145
+ exitWithError(error, options)
146
+ }
147
+ })
148
+
149
+ cli
150
+ .command('detect', 'Detect whether the current project can be onboarded automatically')
151
+ .option('--json', 'Print machine-readable JSON output', { default: false })
152
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
153
+ .action(async (options: JsonCommandOptions) => {
154
+ try {
155
+ await detect(options.json ?? false)
156
+ } catch (error) {
157
+ exitWithError(error, options)
158
+ }
159
+ })
160
+
161
+ cli
162
+ .command('plan', 'Preview the onboarding plan for the current project')
163
+ .option('--json', 'Print machine-readable JSON output', { default: false })
164
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
165
+ .action(async (options: JsonCommandOptions) => {
166
+ try {
167
+ await plan(options.json ?? false)
168
+ } catch (error) {
169
+ exitWithError(error, options)
170
+ }
171
+ })
172
+
173
+ cli
174
+ .command('apply', 'Apply the onboarding plan to the current project')
175
+ .option('--json', 'Print machine-readable JSON output', { default: false })
176
+ .option('--shared', 'Write shared Inspecto settings instead of local-only settings')
177
+ .option('--skip-install', 'Skip npm dependency installation')
178
+ .option('--dry-run', 'Preview changes without modifying files')
179
+ .option('--no-extension', 'Skip IDE extension installation')
180
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
181
+ .action(async (options: ApplyCommandOptions) => {
182
+ try {
183
+ await apply({
184
+ json: options.json ?? false,
185
+ ...(options.shared !== undefined && { shared: options.shared }),
186
+ ...(options.skipInstall !== undefined && { skipInstall: options.skipInstall }),
187
+ ...(options.dryRun !== undefined && { dryRun: options.dryRun }),
188
+ ...(options.extension === false && { noExtension: true }),
189
+ })
190
+ } catch (error) {
191
+ exitWithError(error, options)
192
+ }
193
+ })
194
+
195
+ cli
196
+ .command('teardown', 'Remove Inspecto from your project')
197
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
198
+ .action(async (options: GlobalOptions) => {
199
+ try {
200
+ await teardown()
201
+ } catch (error) {
202
+ exitWithError(error, options)
203
+ }
204
+ })
205
+
206
+ cli
207
+ .command('integrations [...args]', 'Manage assistant integration assets')
208
+ .option(
209
+ '--scope <scope>',
210
+ 'Set install scope for supported assistants (e.g. claude-code: project|user)',
211
+ )
212
+ .option(
213
+ '--mode <mode>',
214
+ 'Set install mode for supported assistants (e.g. copilot: instructions|agents)',
215
+ )
216
+ .option('--force', 'Overwrite existing integration files', { default: false })
217
+ .option('--debug', 'Enable debug mode to show full error traces', { default: false })
218
+ .action(async (args: string[], options: IntegrationCommandOptions) => {
219
+ try {
220
+ const [subcommand, assistant, ...rest] = args
221
+ const integrationOptions = buildIntegrationOptions(options)
222
+
223
+ if (subcommand === 'list') {
224
+ if (assistant || rest.length > 0 || options.scope || options.mode || options.force) {
225
+ throw new Error(
226
+ 'The `list` subcommand does not accept assistant names, --scope, --mode, or --force.',
227
+ )
228
+ }
229
+
230
+ printIntegrationList()
231
+ return
232
+ }
233
+
234
+ if (subcommand === 'path' && assistant) {
235
+ if (rest.length > 0) {
236
+ throw new Error('The `path` subcommand accepts exactly one assistant argument.')
237
+ }
238
+
239
+ if (options.force) {
240
+ throw new Error('The `path` subcommand does not support `--force`.')
241
+ }
242
+
243
+ printIntegrationPath(assistant, integrationOptions)
244
+ return
245
+ }
246
+
247
+ if (subcommand !== 'install' || !assistant) {
248
+ throw new Error(
249
+ [
250
+ 'Usage:',
251
+ ' inspecto integrations list',
252
+ ' inspecto integrations path <assistant> [--scope <scope>] [--mode <mode>]',
253
+ ' inspecto integrations install <assistant> [--scope <scope>] [--mode <mode>] [--force]',
254
+ ].join('\n'),
255
+ )
256
+ }
257
+
258
+ if (rest.length > 0) {
259
+ throw new Error('The `install` subcommand accepts exactly one assistant argument.')
260
+ }
261
+
262
+ await installIntegration(assistant, {
263
+ ...integrationOptions,
264
+ force: options.force ?? false,
265
+ })
266
+ } catch (error) {
267
+ exitWithError(error, options)
268
+ }
269
+ })
270
+
271
+ cli.help()
272
+ cli.version(version)
273
+
274
+ return cli
275
+ }
276
+
277
+ function buildIntegrationOptions(options: IntegrationCommandOptions): InstallIntegrationOptions {
278
+ const resolved: InstallIntegrationOptions = {}
279
+
280
+ if (options.scope) {
281
+ if (isIntegrationScope(options.scope)) {
282
+ resolved.scope = options.scope
283
+ } else {
284
+ throw new Error(`Unknown integration scope: ${options.scope}`)
92
285
  }
93
- })
286
+ }
94
287
 
95
- cli.help()
96
- cli.version(version)
288
+ if (options.mode) {
289
+ if (isIntegrationMode(options.mode)) {
290
+ resolved.mode = options.mode
291
+ } else {
292
+ throw new Error(`Unknown integration mode: ${options.mode}`)
293
+ }
294
+ }
97
295
 
98
- try {
99
- cli.parse()
100
- } catch (err) {
101
- log.error(err instanceof Error ? err.message : String(err))
102
- process.exit(1)
296
+ return resolved
297
+ }
298
+
299
+ function isIntegrationScope(
300
+ value: string,
301
+ ): value is NonNullable<InstallIntegrationOptions['scope']> {
302
+ return (integrationScopes as readonly string[]).includes(value)
303
+ }
304
+
305
+ function isIntegrationMode(value: string): value is NonNullable<InstallIntegrationOptions['mode']> {
306
+ return (integrationModes as readonly string[]).includes(value)
307
+ }
308
+
309
+ export async function runCli(argv: readonly string[] = process.argv): Promise<void> {
310
+ const cli = createCli(argv)
311
+ const parsedArgv = [...argv]
312
+
313
+ try {
314
+ await cli.parse(parsedArgv)
315
+ } catch (error) {
316
+ exitWithError(error, { json: argv.includes('--json') })
317
+ }
318
+ }
319
+
320
+ const entryPath = process.argv[1]
321
+ if (entryPath && fileURLToPath(import.meta.url) === entryPath) {
322
+ void runCli()
103
323
  }