@inspecto-dev/cli 0.3.6 → 0.3.8

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.
@@ -19,7 +19,15 @@ import {
19
19
  const REPO_RAW_BASE = 'https://raw.githubusercontent.com/inspecto-dev/inspecto/main'
20
20
  const TOTAL_STEPS = 6
21
21
 
22
- type AssistantId = 'codex' | 'claude-code' | 'copilot' | 'cursor' | 'gemini' | 'trae' | 'coco'
22
+ type AssistantId =
23
+ | 'codex'
24
+ | 'claude-code'
25
+ | 'copilot'
26
+ | 'cursor'
27
+ | 'gemini'
28
+ | 'trae'
29
+ | 'coco'
30
+ | 'codebuddy'
23
31
  type ClaudeScope = 'project' | 'user'
24
32
  type CopilotMode = 'skills' | 'instructions' | 'agents'
25
33
  type CursorMode = 'skills' | 'rules' | 'agents'
@@ -86,7 +94,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
86
94
  type: 'native-skill',
87
95
  installTarget: '.agents/skills/',
88
96
  preferredInstall:
89
- 'npx @inspecto-dev/cli integrations install codex --host-ide <vscode|cursor|trae|trae-cn>',
97
+ 'npx @inspecto-dev/cli integrations install codex --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
90
98
  cliSupported: true,
91
99
  },
92
100
  {
@@ -94,7 +102,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
94
102
  type: 'native-skill',
95
103
  installTarget: '.claude/skills/ or ~/.claude/skills/',
96
104
  preferredInstall:
97
- 'npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn>',
105
+ 'npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
98
106
  cliSupported: true,
99
107
  },
100
108
  {
@@ -102,7 +110,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
102
110
  type: 'native-skill',
103
111
  installTarget: '.github/skills/inspecto-onboarding/',
104
112
  preferredInstall:
105
- 'npx @inspecto-dev/cli integrations install copilot --host-ide <vscode|cursor|trae|trae-cn>',
113
+ 'npx @inspecto-dev/cli integrations install copilot --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
106
114
  cliSupported: true,
107
115
  },
108
116
  {
@@ -110,7 +118,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
110
118
  type: 'native-skill',
111
119
  installTarget: '.cursor/skills/inspecto-onboarding/',
112
120
  preferredInstall:
113
- 'npx @inspecto-dev/cli integrations install cursor --host-ide <vscode|cursor|trae|trae-cn>',
121
+ 'npx @inspecto-dev/cli integrations install cursor --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
114
122
  cliSupported: true,
115
123
  },
116
124
  {
@@ -118,7 +126,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
118
126
  type: 'native-skill',
119
127
  installTarget: '.gemini/skills/inspecto-onboarding/',
120
128
  preferredInstall:
121
- 'npx @inspecto-dev/cli integrations install gemini --host-ide <vscode|cursor|trae|trae-cn>',
129
+ 'npx @inspecto-dev/cli integrations install gemini --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
122
130
  cliSupported: true,
123
131
  },
124
132
  {
@@ -126,7 +134,7 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
126
134
  type: 'native-skill',
127
135
  installTarget: '.trae/skills/inspecto-onboarding/',
128
136
  preferredInstall:
129
- 'npx @inspecto-dev/cli integrations install trae --host-ide <vscode|cursor|trae|trae-cn>',
137
+ 'npx @inspecto-dev/cli integrations install trae --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
130
138
  cliSupported: true,
131
139
  },
132
140
  {
@@ -134,7 +142,15 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
134
142
  type: 'native-skill',
135
143
  installTarget: '.trae/skills/inspecto-onboarding/',
136
144
  preferredInstall:
137
- 'npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>',
145
+ 'npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
146
+ cliSupported: true,
147
+ },
148
+ {
149
+ assistant: 'codebuddy',
150
+ type: 'native-skill',
151
+ installTarget: '.codebuddy/skills/inspecto-onboarding/',
152
+ preferredInstall:
153
+ 'npx @inspecto-dev/cli integrations install codebuddy --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn>',
138
154
  cliSupported: true,
139
155
  },
140
156
  ]
@@ -235,7 +251,7 @@ export async function installIntegration(
235
251
  const message = `Installed ${getAssistantLabel(assistant)} integration assets. User-level installs only write integration assets and do not launch onboarding automatically.`
236
252
  const nextStep = options.ide
237
253
  ? `Run the install command again from your target project root with --host-ide ${options.ide} when you want to launch onboarding automatically.`
238
- : '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.'
254
+ : 'Run the install command again from your target project root with --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn> when you want to launch onboarding automatically.'
239
255
  const result: IntegrationInstallResult = {
240
256
  status: 'partial',
241
257
  assistant,
@@ -486,6 +502,26 @@ function resolveInstallPlan(assistant: string, options: InstallIntegrationOption
486
502
  successMessage: 'Installed Coco skill to .trae/skills/inspecto-onboarding/SKILL.md',
487
503
  nextStep: 'Start a new Coco session.',
488
504
  }
505
+ case 'codebuddy':
506
+ return {
507
+ assets: [
508
+ {
509
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codebuddy/SKILL.md`,
510
+ target: '.codebuddy/skills/inspecto-onboarding/SKILL.md',
511
+ localSource: 'skills/inspecto-onboarding-codebuddy/SKILL.md',
512
+ },
513
+ {
514
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codebuddy/scripts/run-inspecto.sh`,
515
+ target: '.codebuddy/skills/inspecto-onboarding/scripts/run-inspecto.sh',
516
+ localSource: 'skills/inspecto-onboarding-codebuddy/scripts/run-inspecto.sh',
517
+ executable: true,
518
+ },
519
+ ],
520
+ successMessage:
521
+ 'Installed CodeBuddy skill to .codebuddy/skills/inspecto-onboarding/SKILL.md',
522
+ nextStep:
523
+ 'Open a new CodeBuddy chat and verify the inspecto-onboarding skill is available.',
524
+ }
489
525
  default:
490
526
  throw new Error(`Unknown assistant: ${assistant}`)
491
527
  }
@@ -515,6 +551,7 @@ function getAssistantLabel(assistant: string): string {
515
551
  if (assistant === 'gemini') return 'Gemini'
516
552
  if (assistant === 'trae') return 'Trae'
517
553
  if (assistant === 'coco') return 'Coco'
554
+ if (assistant === 'codebuddy') return 'CodeBuddy'
518
555
  return assistant
519
556
  }
520
557
 
@@ -523,6 +560,8 @@ function formatHostIdeLabel(ide: string): string {
523
560
  if (ide === 'cursor') return 'Cursor'
524
561
  if (ide === 'trae') return 'Trae'
525
562
  if (ide === 'trae-cn') return 'Trae CN'
563
+ if (ide === 'codebuddy') return 'CodeBuddy'
564
+ if (ide === 'codebuddy-cn') return 'CodeBuddy CN'
526
565
  return ide
527
566
  }
528
567
 
@@ -1,14 +1,14 @@
1
1
  // ============================================================
2
2
  // src/detect/framework.ts — Frontend framework detection
3
3
  //
4
- // v1 supported: React / Vue
5
- // Recognized but unsupported: Solid, Svelte, Angular, Preact, Lit
4
+ // v1 supported: React / Vue / Svelte / Solid / Astro
5
+ // Recognized but unsupported: Angular, Preact, Lit
6
6
  // ============================================================
7
7
  import path from 'node:path'
8
8
  import { createRequire } from 'node:module'
9
9
  import { readJSON } from '../utils/fs.js'
10
10
 
11
- export type Framework = 'react' | 'vue'
11
+ export type Framework = 'react' | 'vue' | 'svelte' | 'solid' | 'astro'
12
12
 
13
13
  export interface FrameworkDetection {
14
14
  supported: Framework[]
@@ -25,11 +25,14 @@ interface PackageJSON {
25
25
  const META_FRAMEWORK_MAP: Record<string, Framework> = {
26
26
  next: 'react',
27
27
  nuxt: 'vue',
28
+ '@sveltejs/kit': 'svelte',
28
29
  '@remix-run/react': 'react',
29
30
  '@remix-run/dev': 'react',
30
31
  '@vue/nuxt': 'vue',
31
32
  'vite-plugin-vue': 'vue',
32
33
  '@vitejs/plugin-vue': 'vue',
34
+ '@sveltejs/vite-plugin-svelte': 'svelte',
35
+ 'vite-plugin-solid': 'solid',
33
36
  '@vitejs/plugin-react': 'react',
34
37
  '@vitejs/plugin-react-swc': 'react',
35
38
  }
@@ -38,13 +41,13 @@ const META_FRAMEWORK_MAP: Record<string, Framework> = {
38
41
  const SUPPORTED_FRAMEWORKS: { framework: Framework; deps: string[] }[] = [
39
42
  { framework: 'react', deps: ['react', 'react-dom'] },
40
43
  { framework: 'vue', deps: ['vue'] },
44
+ { framework: 'svelte', deps: ['svelte'] },
45
+ { framework: 'solid', deps: ['solid-js'] },
46
+ { framework: 'astro', deps: ['astro'] },
41
47
  ]
42
48
 
43
49
  /** Recognized but not supported in v1 — detect and warn */
44
50
  const UNSUPPORTED_FRAMEWORKS: { name: string; dep: string }[] = [
45
- { name: 'Solid', dep: 'solid-js' },
46
- { name: 'Svelte', dep: 'svelte' },
47
- { name: 'SvelteKit', dep: '@sveltejs/kit' },
48
51
  { name: 'Angular', dep: '@angular/core' },
49
52
  { name: 'Preact', dep: 'preact' },
50
53
  { name: 'Lit', dep: 'lit' },
package/src/detect/ide.ts CHANGED
@@ -31,8 +31,14 @@ export async function detectIDE(root: string): Promise<IDEProbeResult> {
31
31
  detected.set('Cursor', { ide: 'cursor', supported: true })
32
32
  }
33
33
 
34
- // Trae
34
+ // Trae CN
35
35
  if (
36
+ process.env.__CFBundleIdentifier === 'com.byteocean.trae.cn' ||
37
+ process.env.COCO_IDE_PLUGIN_TYPE === 'TraeCN' ||
38
+ (process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes('trae-cn'))
39
+ ) {
40
+ detected.set('Trae CN', { ide: 'trae-cn', supported: true })
41
+ } else if (
36
42
  process.env.TRAE_APP_DIR ||
37
43
  process.env.__CFBundleIdentifier === 'com.byteocean.trae' ||
38
44
  process.env.COCO_IDE_PLUGIN_TYPE === 'Trae' ||
@@ -41,6 +47,22 @@ export async function detectIDE(root: string): Promise<IDEProbeResult> {
41
47
  detected.set('Trae', { ide: 'trae', supported: true })
42
48
  }
43
49
 
50
+ // CodeBuddy CN
51
+ if (
52
+ process.env.__CFBundleIdentifier === 'ai.codebuddy.mac.cn' ||
53
+ process.env.COCO_IDE_PLUGIN_TYPE === 'CodeBuddyCN' ||
54
+ (process.env.npm_config_user_agent &&
55
+ process.env.npm_config_user_agent.includes('codebuddy-cn'))
56
+ ) {
57
+ detected.set('CodeBuddy CN', { ide: 'codebuddy-cn', supported: true })
58
+ } else if (
59
+ process.env.__CFBundleIdentifier === 'ai.codebuddy.mac' ||
60
+ process.env.COCO_IDE_PLUGIN_TYPE === 'CodeBuddy' ||
61
+ (process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes('codebuddy'))
62
+ ) {
63
+ detected.set('CodeBuddy', { ide: 'codebuddy', supported: true })
64
+ }
65
+
44
66
  // Zed
45
67
  if (process.env.ZED_TERM) {
46
68
  detected.set('Zed', { ide: 'Zed', supported: false })
@@ -62,18 +84,31 @@ export async function detectIDE(root: string): Promise<IDEProbeResult> {
62
84
  // if (process.env.TERM_PROGRAM === 'vscode') { ... }
63
85
 
64
86
  // 2. Check Directory Artifacts (Indicates project has been opened in these IDEs)
65
- const [hasTrae, hasCursor, hasVscode, hasIdea] = await Promise.all([
66
- exists(path.join(root, '.trae')),
67
- exists(path.join(root, '.cursor')),
68
- exists(path.join(root, '.vscode')),
69
- exists(path.join(root, '.idea')),
70
- ])
87
+ const [hasTrae, hasTraeCn, hasCursor, hasVscode, hasIdea, hasCodeBuddy, hasCodeBuddyCn] =
88
+ await Promise.all([
89
+ exists(path.join(root, '.trae')),
90
+ exists(path.join(root, '.trae-cn')),
91
+ exists(path.join(root, '.cursor')),
92
+ exists(path.join(root, '.vscode')),
93
+ exists(path.join(root, '.idea')),
94
+ exists(path.join(root, '.codebuddy')),
95
+ exists(path.join(root, '.codebuddy-cn')),
96
+ ])
71
97
 
72
98
  // If a directory artifact exists, add it to the detection list.
73
99
  // This allows us to surface multiple options (e.g. if you are in Cursor but also have a .vscode folder).
74
100
  if (hasTrae && !detected.has('Trae')) {
75
101
  detected.set('Trae', { ide: 'trae', supported: true })
76
102
  }
103
+ if (hasTraeCn && !detected.has('Trae CN')) {
104
+ detected.set('Trae CN', { ide: 'trae-cn', supported: true })
105
+ }
106
+ if (hasCodeBuddy && !detected.has('CodeBuddy')) {
107
+ detected.set('CodeBuddy', { ide: 'codebuddy', supported: true })
108
+ }
109
+ if (hasCodeBuddyCn && !detected.has('CodeBuddy CN')) {
110
+ detected.set('CodeBuddy CN', { ide: 'codebuddy-cn', supported: true })
111
+ }
77
112
  if (hasCursor && !detected.has('Cursor')) {
78
113
  detected.set('Cursor', { ide: 'cursor', supported: true })
79
114
  }
@@ -6,7 +6,7 @@
6
6
  import path from 'node:path'
7
7
  import { exists, readJSON } from '../utils/fs.js'
8
8
  import { which } from '../utils/exec.js'
9
- import type { Provider, ProviderMode } from '@inspecto-dev/types'
9
+ import type { Provider } from '@inspecto-dev/types'
10
10
 
11
11
  export interface ProviderDetection {
12
12
  id: Provider
@@ -256,6 +256,36 @@ export async function installExtension(
256
256
  }
257
257
  }
258
258
 
259
+ if (ide === 'codebuddy' && process.platform === 'darwin') {
260
+ const codebuddyPath = await findIdeBinary('codebuddy')
261
+ if (codebuddyPath) {
262
+ const result = await installAlternativeIdeExtension(
263
+ codebuddyPath,
264
+ getHostIdeLabel('codebuddy'),
265
+ extensionRef,
266
+ quiet,
267
+ )
268
+ if (result) {
269
+ return result
270
+ }
271
+ }
272
+ }
273
+
274
+ if (ide === 'codebuddy-cn' && process.platform === 'darwin') {
275
+ const codebuddyCnPath = await findIdeBinary('codebuddy-cn')
276
+ if (codebuddyCnPath) {
277
+ const result = await installAlternativeIdeExtension(
278
+ codebuddyCnPath,
279
+ getHostIdeLabel('codebuddy-cn'),
280
+ extensionRef,
281
+ quiet,
282
+ )
283
+ if (result) {
284
+ return result
285
+ }
286
+ }
287
+ }
288
+
259
289
  // Other IDEs: Prompt to install via VSIX
260
290
  if (!quiet) {
261
291
  log.warn(`Could not auto-install extension for ${ide}`)
@@ -8,11 +8,11 @@ export class EsbuildStrategy implements InjectStrategy {
8
8
  return tool === 'esbuild'
9
9
  }
10
10
 
11
- inject(options: InjectOptions): void {
11
+ inject(_options: InjectOptions): void {
12
12
  throw new Error('Esbuild requires manual plugin configuration')
13
13
  }
14
14
 
15
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
15
+ getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
16
16
  return [
17
17
  `1. Update your esbuild config (${detection.configPath}):`,
18
18
  `import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
@@ -8,11 +8,11 @@ export class RollupStrategy implements InjectStrategy {
8
8
  return tool === 'rollup'
9
9
  }
10
10
 
11
- inject(options: InjectOptions): void {
11
+ inject(_options: InjectOptions): void {
12
12
  throw new Error('Rollup requires manual plugin configuration')
13
13
  }
14
14
 
15
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
15
+ getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
16
16
  return [
17
17
  `1. Update your rollup config (${detection.configPath}):`,
18
18
  `import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
@@ -8,11 +8,11 @@ export class RsbuildStrategy implements InjectStrategy {
8
8
  return tool === 'rsbuild'
9
9
  }
10
10
 
11
- inject(options: InjectOptions): void {
11
+ inject(_options: InjectOptions): void {
12
12
  throw new Error('Rsbuild requires manual plugin configuration due to nested structure')
13
13
  }
14
14
 
15
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
15
+ getManualInstructions(_detection: BuildToolDetection, _reason: string): string[] {
16
16
  return [
17
17
  `import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
18
18
  '',
@@ -8,11 +8,11 @@ export class RspackStrategy implements InjectStrategy {
8
8
  return tool === 'rspack'
9
9
  }
10
10
 
11
- inject(options: InjectOptions): void {
11
+ inject(_options: InjectOptions): void {
12
12
  throw new Error('Rspack requires manual plugin configuration')
13
13
  }
14
14
 
15
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
15
+ getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
16
16
  const importPkg = detection.isLegacyRspack
17
17
  ? '@inspecto-dev/plugin/legacy/rspack'
18
18
  : '@inspecto-dev/plugin'
@@ -9,7 +9,7 @@ export class ViteStrategy implements InjectStrategy {
9
9
  return tool === 'vite'
10
10
  }
11
11
 
12
- inject({ mod, detection }: InjectOptions): void {
12
+ inject({ mod, detection: _detection }: InjectOptions): void {
13
13
  addVitePlugin(mod, {
14
14
  from: '@inspecto-dev/plugin',
15
15
  constructor: 'inspecto',
@@ -17,7 +17,7 @@ export class ViteStrategy implements InjectStrategy {
17
17
  })
18
18
  }
19
19
 
20
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
20
+ getManualInstructions(_detection: BuildToolDetection, _reason: string): string[] {
21
21
  return [
22
22
  `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
23
23
  '',
@@ -8,12 +8,12 @@ export class WebpackStrategy implements InjectStrategy {
8
8
  return tool === 'webpack'
9
9
  }
10
10
 
11
- inject(options: InjectOptions): void {
11
+ inject(_options: InjectOptions): void {
12
12
  // AST manipulation for Webpack configs (often CommonJS or complex objects) is brittle in v1
13
13
  throw new Error('Webpack requires manual plugin configuration')
14
14
  }
15
15
 
16
- getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
16
+ getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
17
17
  const importPkg = detection.isLegacyWebpack
18
18
  ? '@inspecto-dev/plugin/legacy/webpack4'
19
19
  : '@inspecto-dev/plugin'
@@ -94,6 +94,44 @@ export const HOST_IDE_CAPABILITIES: Record<SupportedHostIde, HostIdeCapability>
94
94
  ],
95
95
  },
96
96
  },
97
+ codebuddy: {
98
+ label: 'CodeBuddy',
99
+ artifactDir: '.codebuddy',
100
+ extensionDir: '.codebuddy/extensions',
101
+ binaryName: 'codebuddy',
102
+ binaryPaths: {
103
+ darwin: [
104
+ '/Applications/CodeBuddy.app/Contents/Resources/app/bin/codebuddy',
105
+ '/Applications/CodeBuddy.app/Contents/Resources/app/bin/code',
106
+ `${process.env.HOME}/Applications/CodeBuddy.app/Contents/Resources/app/bin/codebuddy`,
107
+ `${process.env.HOME}/Applications/CodeBuddy.app/Contents/Resources/app/bin/code`,
108
+ ],
109
+ linux: ['/usr/bin/codebuddy', '/opt/CodeBuddy/resources/app/bin/codebuddy'],
110
+ win32: [
111
+ `${process.env.LOCALAPPDATA}\\Programs\\CodeBuddy\\resources\\app\\bin\\codebuddy.cmd`,
112
+ `${process.env.PROGRAMFILES}\\CodeBuddy\\resources\\app\\bin\\codebuddy.cmd`,
113
+ ],
114
+ },
115
+ },
116
+ 'codebuddy-cn': {
117
+ label: 'CodeBuddy CN',
118
+ artifactDir: '.codebuddy-cn',
119
+ extensionDir: '.codebuddy-cn/extensions',
120
+ binaryName: 'codebuddy-cn',
121
+ binaryPaths: {
122
+ darwin: [
123
+ '/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/codebuddy-cn',
124
+ '/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/code',
125
+ `${process.env.HOME}/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/codebuddy-cn`,
126
+ `${process.env.HOME}/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/code`,
127
+ ],
128
+ linux: ['/usr/bin/codebuddy-cn', '/opt/CodeBuddy CN/resources/app/bin/codebuddy-cn'],
129
+ win32: [
130
+ `${process.env.LOCALAPPDATA}\\Programs\\CodeBuddy CN\\resources\\app\\bin\\codebuddy-cn.cmd`,
131
+ `${process.env.PROGRAMFILES}\\CodeBuddy CN\\resources\\app\\bin\\codebuddy-cn.cmd`,
132
+ ],
133
+ },
134
+ },
97
135
  }
98
136
 
99
137
  export { HOST_IDE_IDS, getHostIdeLabel, isSupportedHostIde }
@@ -45,7 +45,7 @@ describe('buildOnboardingContext', () => {
45
45
  })
46
46
  vi.mocked(frameworkDetector.detectFrameworks).mockResolvedValue({
47
47
  supported: ['react'],
48
- unsupported: [{ name: 'Svelte', dep: 'svelte' }],
48
+ unsupported: [{ name: 'Angular', dep: 'angular' }],
49
49
  })
50
50
  vi.mocked(ideDetector.detectIDE).mockResolvedValue({
51
51
  detected: [{ ide: 'vscode', supported: true }],
@@ -79,7 +79,7 @@ describe('buildOnboardingContext', () => {
79
79
  },
80
80
  frameworks: {
81
81
  supported: ['react'],
82
- unsupported: ['Svelte'],
82
+ unsupported: ['Angular'],
83
83
  },
84
84
  ides: [{ ide: 'vscode', supported: true }],
85
85
  providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
@@ -177,6 +177,36 @@ describe('installExtension', () => {
177
177
  expect(logMock.success).toHaveBeenCalledWith('Trae CN extension already installed')
178
178
  expect(logMock.warn).not.toHaveBeenCalledWith('Could not auto-install extension for trae-cn')
179
179
  })
180
+
181
+ it('installs the CodeBuddy CN extension via the app bundle code launcher on macOS when available', async () => {
182
+ vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
183
+ whichMock.mockResolvedValue(false)
184
+ existsMock.mockImplementation(async filePath => {
185
+ return filePath === '/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/code'
186
+ })
187
+ runMock.mockImplementation(async (_command, args: string[]) => {
188
+ if (args[0] === '--list-extensions') {
189
+ return { stdout: '', stderr: '' }
190
+ }
191
+ if (args[0] === '--install-extension') {
192
+ return { stdout: '', stderr: '' }
193
+ }
194
+ throw new Error(`unexpected args: ${args.join(' ')}`)
195
+ })
196
+
197
+ const { installExtension } = await import('../src/inject/extension.js')
198
+
199
+ await expect(installExtension(false, 'codebuddy-cn')).resolves.toMatchObject({
200
+ type: 'extension_installed',
201
+ id: 'inspecto.inspecto',
202
+ })
203
+
204
+ expect(runMock).toHaveBeenCalledWith(
205
+ '/Applications/CodeBuddy CN.app/Contents/Resources/app/bin/code',
206
+ ['--install-extension', 'inspecto.inspecto', '--force'],
207
+ )
208
+ expect(logMock.success).toHaveBeenCalledWith('CodeBuddy CN extension installed via CLI')
209
+ })
180
210
  })
181
211
 
182
212
  describe('openIdeWorkspace', () => {
@@ -202,4 +232,22 @@ describe('openIdeWorkspace', () => {
202
232
  ['--new-window', '/repo/app'],
203
233
  )
204
234
  })
235
+
236
+ it('opens CodeBuddy in a new window via the app bundle code launcher when available', async () => {
237
+ vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
238
+ whichMock.mockResolvedValue(false)
239
+ existsMock.mockImplementation(async filePath => {
240
+ return filePath === '/Applications/CodeBuddy.app/Contents/Resources/app/bin/code'
241
+ })
242
+ runMock.mockResolvedValue({ stdout: '', stderr: '' })
243
+
244
+ const { openIdeWorkspace } = await import('../src/inject/extension.js')
245
+
246
+ await expect(openIdeWorkspace('codebuddy', '/repo/app')).resolves.toBe(true)
247
+
248
+ expect(runMock).toHaveBeenCalledWith(
249
+ '/Applications/CodeBuddy.app/Contents/Resources/app/bin/code',
250
+ ['--new-window', '/repo/app'],
251
+ )
252
+ })
205
253
  })
@@ -31,7 +31,7 @@ describe('detectFrameworks', () => {
31
31
  expect(result.unsupported).toHaveLength(0)
32
32
  })
33
33
 
34
- it('detects Svelte as unsupported framework', async () => {
34
+ it('detects Svelte as supported framework', async () => {
35
35
  vi.mocked(fsUtils.readJSON).mockResolvedValue({
36
36
  devDependencies: {
37
37
  svelte: '^4.0.0',
@@ -39,8 +39,32 @@ describe('detectFrameworks', () => {
39
39
  })
40
40
 
41
41
  const result = await detectFrameworks('/mock/root')
42
- expect(result.supported).toHaveLength(0)
43
- expect(result.unsupported).toContainEqual({ name: 'Svelte', dep: 'svelte' })
42
+ expect(result.supported).toContain('svelte')
43
+ expect(result.unsupported).toHaveLength(0)
44
+ })
45
+
46
+ it('detects Solid as supported framework', async () => {
47
+ vi.mocked(fsUtils.readJSON).mockResolvedValue({
48
+ devDependencies: {
49
+ 'solid-js': '^1.0.0',
50
+ },
51
+ })
52
+
53
+ const result = await detectFrameworks('/mock/root')
54
+ expect(result.supported).toContain('solid')
55
+ expect(result.unsupported).toHaveLength(0)
56
+ })
57
+
58
+ it('detects Astro as supported framework', async () => {
59
+ vi.mocked(fsUtils.readJSON).mockResolvedValue({
60
+ devDependencies: {
61
+ astro: '^4.0.0',
62
+ },
63
+ })
64
+
65
+ const result = await detectFrameworks('/mock/root')
66
+ expect(result.supported).toContain('astro')
67
+ expect(result.unsupported).toHaveLength(0)
44
68
  })
45
69
 
46
70
  it('returns empty if no framework is matched', async () => {
@@ -335,7 +335,7 @@ describe('runIntegrationAutomation', () => {
335
335
  'Step 2/6: Could not confidently resolve the host IDE',
336
336
  )
337
337
  expect(logMock.hint).toHaveBeenCalledWith(
338
- 'Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup.',
338
+ 'Re-run with --host-ide <vscode|cursor|trae|trae-cn|codebuddy|codebuddy-cn> or run the command from the target IDE terminal to continue automatic setup.',
339
339
  )
340
340
  })
341
341
 
@@ -57,6 +57,21 @@ describe('resolveIntegrationHostIde', () => {
57
57
  })
58
58
  })
59
59
 
60
+ it('accepts codebuddy as an explicit ide argument', async () => {
61
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
62
+
63
+ await expect(
64
+ resolveIntegrationHostIde({
65
+ explicitIde: 'codebuddy',
66
+ cwd: '/repo',
67
+ }),
68
+ ).resolves.toMatchObject({
69
+ ide: 'codebuddy',
70
+ confidence: 'high',
71
+ source: 'explicit',
72
+ })
73
+ })
74
+
60
75
  it('uses .inspecto settings ide when present', async () => {
61
76
  vi.mocked(fsUtils.readJSON).mockImplementation(async filePath => {
62
77
  if (filePath === '/repo/.inspecto/settings.local.json') {
@@ -99,6 +114,27 @@ describe('resolveIntegrationHostIde', () => {
99
114
  })
100
115
  })
101
116
 
117
+ it('uses codebuddy-cn from .inspecto settings when present', async () => {
118
+ vi.mocked(fsUtils.readJSON).mockImplementation(async filePath => {
119
+ if (filePath === '/repo/.inspecto/settings.local.json') {
120
+ return { ide: 'codebuddy-cn' }
121
+ }
122
+ return null
123
+ })
124
+
125
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
126
+
127
+ await expect(
128
+ resolveIntegrationHostIde({
129
+ cwd: '/repo',
130
+ }),
131
+ ).resolves.toMatchObject({
132
+ ide: 'codebuddy-cn',
133
+ confidence: 'high',
134
+ source: 'config',
135
+ })
136
+ })
137
+
102
138
  it('treats a single env-detected ide as high confidence', async () => {
103
139
  process.env.CURSOR_CHANNEL = 'stable'
104
140
 
@@ -151,6 +187,24 @@ describe('resolveIntegrationHostIde', () => {
151
187
  })
152
188
  })
153
189
 
190
+ it('treats a .codebuddy-cn project artifact as medium confidence', async () => {
191
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
192
+ return filePath === '/repo/.codebuddy-cn'
193
+ })
194
+
195
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
196
+
197
+ await expect(
198
+ resolveIntegrationHostIde({
199
+ cwd: '/repo',
200
+ }),
201
+ ).resolves.toMatchObject({
202
+ ide: 'codebuddy-cn',
203
+ confidence: 'medium',
204
+ source: 'artifact',
205
+ })
206
+ })
207
+
154
208
  it('refuses to resolve an ide when project artifacts are ambiguous', async () => {
155
209
  vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
156
210
  return filePath === '/repo/.cursor' || filePath === '/repo/.vscode'