@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/src/index.ts CHANGED
@@ -1,5 +1,20 @@
1
+ export { apply } from './commands/apply.js'
2
+ export { detect } from './commands/detect.js'
1
3
  export { init } from './commands/init.js'
2
- export { doctor } from './commands/doctor.js'
4
+ export { collectDoctorResult, doctor } from './commands/doctor.js'
5
+ export { onboard } from './commands/onboard.js'
6
+ export { plan } from './commands/plan.js'
3
7
  export { teardown } from './commands/teardown.js'
4
- export type { InitOptions, BuildTool, PackageManager, InstallLock } from './types.js'
8
+ export { writeCommandOutput, reportCommandError } from './utils/output.js'
9
+ export type {
10
+ InitOptions,
11
+ BuildTool,
12
+ PackageManager,
13
+ InstallLock,
14
+ DoctorDiagnostic,
15
+ DoctorResult,
16
+ OnboardStatus,
17
+ OnboardCommandResult,
18
+ ResolvedOnboardingSession,
19
+ } from './types.js'
5
20
  export type { Framework } from './detect/framework.js'
@@ -15,7 +15,7 @@
15
15
 
16
16
  import path from 'node:path'
17
17
  import { loadFile, writeFile as writeAstFile } from 'magicast'
18
- import { exists, readFile } from '../utils/fs.js'
18
+ import { readFile } from '../utils/fs.js'
19
19
  import { log } from '../utils/logger.js'
20
20
  import type { BuildToolDetection, Mutation } from '../types.js'
21
21
  import { STRATEGIES } from './strategies/index.js'
@@ -25,7 +25,9 @@ function printManualInstructions(
25
25
  strategy: InjectStrategy | undefined,
26
26
  detection: BuildToolDetection,
27
27
  reason: string,
28
+ quiet = false,
28
29
  ) {
30
+ if (quiet) return
29
31
  log.warn(`Could not automatically configure ${detection.configPath}`)
30
32
  log.hint(`(reason: ${reason})`)
31
33
  log.blank()
@@ -33,7 +35,7 @@ function printManualInstructions(
33
35
 
34
36
  if (strategy) {
35
37
  const instructions = strategy.getManualInstructions(detection, reason)
36
- log.codeBlock(instructions)
38
+ log.copyableCodeBlock(instructions)
37
39
  } else {
38
40
  log.error(`Unsupported build tool: ${detection.tool}`)
39
41
  }
@@ -73,6 +75,7 @@ export async function injectPlugin(
73
75
  root: string,
74
76
  detection: BuildToolDetection,
75
77
  dryRun: boolean,
78
+ quiet = false,
76
79
  ): Promise<InjectionResult> {
77
80
  const configPath = path.join(root, detection.configPath)
78
81
  const mutations: Mutation[] = []
@@ -82,13 +85,15 @@ export async function injectPlugin(
82
85
  // Step 1: Read config file to check existence and idempotency
83
86
  const content = await readFile(configPath)
84
87
  if (!content) {
85
- printManualInstructions(strategy, detection, 'config file not readable')
88
+ printManualInstructions(strategy, detection, 'config file not readable', quiet)
86
89
  return { success: false, mutations, failureReason: 'config file not readable' }
87
90
  }
88
91
 
89
92
  // Step 2: Idempotency check
90
93
  if (isAlreadyInjected(content)) {
91
- log.success(`Plugin already configured in ${detection.configPath} (skipped)`)
94
+ if (!quiet) {
95
+ log.success(`Plugin already configured in ${detection.configPath} (skipped)`)
96
+ }
92
97
 
93
98
  mutations.push({
94
99
  type: 'file_modified',
@@ -104,13 +109,16 @@ export async function injectPlugin(
104
109
  strategy,
105
110
  detection,
106
111
  `No injection strategy found for ${detection.tool}`,
112
+ quiet,
107
113
  )
108
114
  return { success: false, mutations, failureReason: 'No strategy found' }
109
115
  }
110
116
 
111
117
  // Step 3: Automatic configuration
112
118
  if (dryRun) {
113
- log.dryRun(`Would automatically configure plugin in ${detection.configPath}`)
119
+ if (!quiet) {
120
+ log.dryRun(`Would automatically configure plugin in ${detection.configPath}`)
121
+ }
114
122
  return { success: true, mutations: [] }
115
123
  }
116
124
 
@@ -132,7 +140,9 @@ export async function injectPlugin(
132
140
  description: 'Automatically configured inspecto() plugin',
133
141
  })
134
142
 
135
- log.success(`Configured plugin in ${detection.configPath}`)
143
+ if (!quiet) {
144
+ log.success(`Configured plugin in ${detection.configPath}`)
145
+ }
136
146
  return { success: true, mutations }
137
147
  } catch (err) {
138
148
  // Graceback degradation
@@ -140,6 +150,7 @@ export async function injectPlugin(
140
150
  strategy,
141
151
  detection,
142
152
  `Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`,
153
+ quiet,
143
154
  )
144
155
  return {
145
156
  success: false,
@@ -69,9 +69,15 @@ async function tryOpenURI(uri: string): Promise<boolean> {
69
69
  /**
70
70
  * Attempt to install the VS Code extension using waterfall degradation.
71
71
  */
72
- export async function installExtension(dryRun: boolean, ide?: string): Promise<Mutation | null> {
72
+ export async function installExtension(
73
+ dryRun: boolean,
74
+ ide?: string,
75
+ quiet = false,
76
+ ): Promise<Mutation | null> {
73
77
  if (dryRun) {
74
- log.dryRun('Would attempt to install VS Code extension')
78
+ if (!quiet) {
79
+ log.dryRun('Would attempt to install VS Code extension')
80
+ }
75
81
  return null
76
82
  }
77
83
 
@@ -82,7 +88,9 @@ export async function installExtension(dryRun: boolean, ide?: string): Promise<M
82
88
  if (await which('code')) {
83
89
  try {
84
90
  await run('code', ['--install-extension', EXTENSION_ID])
85
- log.success('VS Code extension installed via CLI')
91
+ if (!quiet) {
92
+ log.success('VS Code extension installed via CLI')
93
+ }
86
94
  return { type: 'extension_installed', id: EXTENSION_ID }
87
95
  } catch {
88
96
  // Fall through to next level
@@ -94,10 +102,12 @@ export async function installExtension(dryRun: boolean, ide?: string): Promise<M
94
102
  if (codePath) {
95
103
  try {
96
104
  await run(codePath, ['--install-extension', EXTENSION_ID])
97
- log.success('VS Code extension installed via binary path')
98
- log.info(
99
- 'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future',
100
- )
105
+ if (!quiet) {
106
+ log.success('VS Code extension installed via binary path')
107
+ log.info(
108
+ 'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future',
109
+ )
110
+ }
101
111
  return { type: 'extension_installed', id: EXTENSION_ID }
102
112
  } catch {
103
113
  // Fall through to next level
@@ -107,29 +117,37 @@ export async function installExtension(dryRun: boolean, ide?: string): Promise<M
107
117
  // Level 3: URI scheme
108
118
  const uri = `vscode:extension/${EXTENSION_ID}`
109
119
  if (await tryOpenURI(uri)) {
110
- log.warn('Opened extension page in VS Code')
111
- log.hint('Please click "Install" in the opened VS Code window to complete setup.')
120
+ if (!quiet) {
121
+ log.warn('Opened extension page in VS Code')
122
+ log.hint('Please click "Install" in the opened VS Code window to complete setup.')
123
+ }
112
124
  return { type: 'extension_installed', id: EXTENSION_ID, manual_action_required: true }
113
125
  }
114
126
 
115
127
  // Level 4: Manual fallback
116
- log.warn('Could not auto-install VS Code extension')
117
- log.hint('Please install it manually to enable Inspector features:')
118
- log.hint(' 1. Open VS Code')
119
- log.hint(' 2. Press Ctrl+Shift+X (or Cmd+Shift+X)')
120
- log.hint(' 3. Search for "Inspecto"')
121
- log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`)
128
+ if (!quiet) {
129
+ log.warn('Could not auto-install VS Code extension')
130
+ log.hint('Please install it manually to enable Inspector features:')
131
+ log.hint(' 1. Open VS Code')
132
+ log.hint(' 2. Press Ctrl+Shift+X (or Cmd+Shift+X)')
133
+ log.hint(' 3. Search for "Inspecto"')
134
+ log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`)
135
+ }
122
136
  return null
123
137
  }
124
138
 
125
139
  // Other IDEs: Prompt to install via VSIX
126
- log.warn(`Could not auto-install extension for ${ide}`)
127
- log.hint('Please install it manually to enable Inspector features:')
128
- log.hint(' 1. Download the latest .vsix file from Inspecto releases')
129
- log.hint(` 2. Open ${ide}`)
130
- log.hint(' 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)')
131
- log.hint(' 4. Type and select "Extensions: Install from VSIX..."')
132
- log.hint(' 5. Select the downloaded .vsix file')
140
+ if (!quiet) {
141
+ log.warn(`Could not auto-install extension for ${ide}`)
142
+ log.hint('Please install it manually to enable Inspector features:')
143
+ log.hint(
144
+ ' 1. Download the latest .vsix file (Open VSX: https://open-vsx.org/extension/inspecto/inspecto)',
145
+ )
146
+ log.hint(` 2. Open ${ide}`)
147
+ log.hint(' 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)')
148
+ log.hint(' 4. Type and select "Extensions: Install from VSIX..."')
149
+ log.hint(' 5. Select the downloaded .vsix file')
150
+ }
133
151
  return null
134
152
  }
135
153
 
@@ -21,6 +21,7 @@ export async function updateGitignore(
21
21
  root: string,
22
22
  shared: boolean,
23
23
  dryRun: boolean,
24
+ quiet = false,
24
25
  ): Promise<void> {
25
26
  const gitignorePath = path.join(root, '.gitignore')
26
27
  let content = (await readFile(gitignorePath)) ?? ''
@@ -34,7 +35,9 @@ export async function updateGitignore(
34
35
  if (!dryRun) {
35
36
  await writeFile(gitignorePath, content)
36
37
  }
37
- log.success('Updated .gitignore: .inspecto/ is no longer fully ignored')
38
+ if (!quiet) {
39
+ log.success('Updated .gitignore: .inspecto/ is no longer fully ignored')
40
+ }
38
41
  return
39
42
  }
40
43
 
@@ -49,10 +52,14 @@ export async function updateGitignore(
49
52
  content = content.trimEnd() + '\n' + section
50
53
 
51
54
  if (dryRun) {
52
- log.dryRun(`Would update .gitignore with: ${missingRules.join(', ')}`)
55
+ if (!quiet) {
56
+ log.dryRun(`Would update .gitignore with: ${missingRules.join(', ')}`)
57
+ }
53
58
  } else {
54
59
  await writeFile(gitignorePath, content)
55
- log.success('Updated .gitignore')
60
+ if (!quiet) {
61
+ log.success('Updated .gitignore')
62
+ }
56
63
  }
57
64
  }
58
65
 
@@ -2,54 +2,68 @@ import { log } from './utils/logger.js'
2
2
 
3
3
  export function printNuxtManualInstructions() {
4
4
  log.blank()
5
- log.hint('To enable Inspecto in Nuxt, update your nuxt.config.ts:')
6
- console.log(`\x1b[36m
7
- import { vitePlugin as inspecto } from '@inspecto-dev/plugin'
8
- export default defineNuxtConfig({
9
- vite: {
10
- plugins: [inspecto()]
11
- }
12
- })
13
- \x1b[0m`)
14
- log.hint('And create a Nuxt plugin at plugins/inspecto.client.ts:')
15
- console.log(`\x1b[36m
16
- export default defineNuxtPlugin(() => {
17
- if (import.meta.dev) {
18
- import('@inspecto-dev/core').then(({ mountInspector }) => {
19
- mountInspector()
20
- })
21
- }
22
- })
23
- \x1b[0m`)
5
+ log.hint('Nuxt requires manual setup in the current version.')
6
+ log.hint('1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:')
7
+ log.copyableCodeBlock([
8
+ "import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
9
+ '',
10
+ 'export default defineNuxtConfig({',
11
+ ' vite: {',
12
+ ' plugins: [inspecto()],',
13
+ ' },',
14
+ '})',
15
+ ])
16
+ log.hint('2. Create `plugins/inspecto.client.ts` to mount `@inspecto-dev/core` in development:')
17
+ log.copyableCodeBlock([
18
+ 'export default defineNuxtPlugin(() => {',
19
+ ' if (import.meta.dev) {',
20
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
21
+ ' mountInspector()',
22
+ ' })',
23
+ ' }',
24
+ '})',
25
+ ])
26
+ log.hint('3. Restart your Nuxt dev server after updating the config.')
24
27
  }
25
28
 
26
29
  export function printNextJsManualInstructions() {
27
30
  log.blank()
28
- log.hint('To enable Inspecto in Next.js, update your next.config.mjs:')
29
- console.log(`\x1b[36m
30
- import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'
31
- const nextConfig = {
32
- webpack: (config, { dev, isServer }) => {
33
- if (dev && !isServer) config.plugins.push(inspecto())
34
- return config
35
- }
36
- }
37
- export default nextConfig
38
- \x1b[0m`)
39
- log.hint('And initialize the client dynamically in your app/layout.tsx (or pages/_app.tsx):')
40
- console.log(`\x1b[36m
41
- 'use client'
42
- import { useEffect } from 'react'
43
-
44
- export default function RootLayout({ children }) {
45
- useEffect(() => {
46
- if (process.env.NODE_ENV !== 'production') {
47
- import('@inspecto-dev/core').then(({ mountInspector }) => {
48
- mountInspector({ serverUrl: 'http://127.0.0.1:5678' })
49
- })
50
- }
51
- }, [])
52
- return <html><body>{children}</body></html>
53
- }
54
- \x1b[0m`)
31
+ log.hint('Next.js requires manual setup in the current version.')
32
+ log.hint('1. Update `next.config.mjs` to register the Inspecto webpack plugin:')
33
+ log.copyableCodeBlock([
34
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
35
+ '',
36
+ "/** @type {import('next').NextConfig} */",
37
+ 'const nextConfig = {',
38
+ ' webpack: (config, { dev, isServer }) => {',
39
+ ' if (dev && !isServer) {',
40
+ ' config.plugins.push(inspecto())',
41
+ ' }',
42
+ ' return config',
43
+ ' },',
44
+ '}',
45
+ '',
46
+ 'export default nextConfig',
47
+ ])
48
+ log.hint(
49
+ '2. Initialize `@inspecto-dev/core` from a client component such as `app/layout.tsx` or `pages/_app.tsx`:',
50
+ )
51
+ log.copyableCodeBlock([
52
+ "'use client'",
53
+ '',
54
+ "import { useEffect } from 'react'",
55
+ '',
56
+ 'export default function RootLayout({ children }) {',
57
+ ' useEffect(() => {',
58
+ " if (process.env.NODE_ENV !== 'production') {",
59
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
60
+ " mountInspector({ serverUrl: 'http://127.0.0.1:5678' })",
61
+ ' })',
62
+ ' }',
63
+ ' }, [])',
64
+ '',
65
+ ' return <html><body>{children}</body></html>',
66
+ '}',
67
+ ])
68
+ log.hint('3. Restart your Next.js dev server after updating the config.')
55
69
  }