@cregis-dev/cckit 0.6.6 → 0.6.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.
- package/LICENSE +21 -21
- package/bin/cckit.js +3 -3
- package/package.json +53 -53
- package/registry.json +145 -145
- package/src/cli.js +79 -79
- package/src/commands/init.js +174 -174
- package/src/commands/status.js +125 -125
- package/src/commands/update.js +192 -192
- package/src/core/config.js +82 -75
- package/src/core/orchestrator.js +79 -79
- package/src/core/registry.js +60 -60
- package/src/steps/add-plugin.js +148 -116
- package/src/steps/configure-user.js +181 -181
- package/src/steps/enable-plugins.js +97 -97
- package/src/steps/install-bmad.js +85 -85
- package/src/steps/install-mcp.js +70 -70
- package/src/steps/install-rules.js +69 -69
- package/src/steps/install-skills.js +56 -56
- package/src/utils/compare-versions.js +106 -106
- package/src/utils/fs.js +33 -33
- package/src/utils/logger.js +16 -16
- package/src/utils/manifest.js +101 -101
- package/src/utils/prompt.js +41 -41
- package/templates/mcp/claude-code/.mcp.json +40 -40
- package/templates/rules/README.md +103 -103
- package/templates/rules/common/agents.md +49 -49
- package/templates/rules/common/coding-style.md +48 -48
- package/templates/rules/common/development-workflow.md +37 -37
- package/templates/rules/common/git-workflow.md +24 -24
- package/templates/rules/common/hooks.md +30 -30
- package/templates/rules/common/patterns.md +31 -31
- package/templates/rules/common/performance.md +55 -55
- package/templates/rules/common/security.md +29 -29
- package/templates/rules/common/testing.md +29 -29
- package/templates/rules/golang/coding-style.md +32 -32
- package/templates/rules/golang/hooks.md +17 -17
- package/templates/rules/golang/patterns.md +45 -45
- package/templates/rules/golang/security.md +34 -34
- package/templates/rules/golang/testing.md +31 -31
- package/templates/rules/python/coding-style.md +42 -42
- package/templates/rules/python/hooks.md +19 -19
- package/templates/rules/python/patterns.md +39 -39
- package/templates/rules/python/security.md +30 -30
- package/templates/rules/python/testing.md +38 -38
- package/templates/rules/swift/coding-style.md +47 -47
- package/templates/rules/swift/hooks.md +20 -20
- package/templates/rules/swift/patterns.md +66 -66
- package/templates/rules/swift/security.md +33 -33
- package/templates/rules/swift/testing.md +45 -45
- package/templates/rules/typescript/coding-style.md +65 -65
- package/templates/rules/typescript/hooks.md +22 -22
- package/templates/rules/typescript/patterns.md +52 -52
- package/templates/rules/typescript/security.md +28 -28
- package/templates/rules/typescript/testing.md +18 -18
package/src/core/orchestrator.js
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Orchestrator — executes installation steps in sequence
|
|
3
|
-
* and collects results into a unified RunReport.
|
|
4
|
-
*
|
|
5
|
-
* Each step is independently retriable; the orchestrator
|
|
6
|
-
* never throws — it captures failures in step results.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {object} StepResult
|
|
11
|
-
* @property {string} stepId - Stable identifier
|
|
12
|
-
* @property {string} name - Human-readable label
|
|
13
|
-
* @property {boolean} success
|
|
14
|
-
* @property {boolean} skipped
|
|
15
|
-
* @property {object} details - Step-specific metadata
|
|
16
|
-
* @property {string} [error] - Error message if !success
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {object} StepSpec
|
|
21
|
-
* @property {string} id
|
|
22
|
-
* @property {Function} fn - async (opts, logger, _deps?) => StepResult
|
|
23
|
-
* @property {object} opts - Step-specific options
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {object} RunReport
|
|
28
|
-
* @property {StepResult[]} steps
|
|
29
|
-
* @property {boolean} success - True if all non-skipped steps succeeded
|
|
30
|
-
* @property {object} fileRegistry - Hash registry (currently MCP only)
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Execute steps sequentially, collecting results.
|
|
35
|
-
*
|
|
36
|
-
* @param {StepSpec[]} steps - Ordered step specifications
|
|
37
|
-
* @param {object} logger - Logger instance
|
|
38
|
-
* @returns {Promise<RunReport>}
|
|
39
|
-
*/
|
|
40
|
-
export async function orchestrate(steps, logger) {
|
|
41
|
-
const results = []
|
|
42
|
-
const fileRegistry = {}
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < steps.length; i++) {
|
|
45
|
-
const { id, fn, opts } = steps[i]
|
|
46
|
-
logger.step(i + 1, steps.length, id)
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const result = await fn(opts, logger)
|
|
50
|
-
results.push(result)
|
|
51
|
-
|
|
52
|
-
// Collect file hashes from steps that produce them
|
|
53
|
-
if (result.details?.hash && result.details?.target) {
|
|
54
|
-
fileRegistry[result.details.target] = result.details.hash
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (result.skipped) {
|
|
58
|
-
logger.debug(` Skipped: ${id}`)
|
|
59
|
-
} else if (result.success) {
|
|
60
|
-
logger.success(result.name || id)
|
|
61
|
-
} else {
|
|
62
|
-
logger.warn(`${result.name || id}: ${result.error || 'completed with errors'}`)
|
|
63
|
-
}
|
|
64
|
-
} catch (err) {
|
|
65
|
-
logger.error(`${id} threw unexpectedly: ${err.message}`)
|
|
66
|
-
results.push({
|
|
67
|
-
stepId: id,
|
|
68
|
-
name: id,
|
|
69
|
-
success: false,
|
|
70
|
-
skipped: false,
|
|
71
|
-
details: {},
|
|
72
|
-
error: err.message,
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const success = results.every(r => r.skipped || r.success)
|
|
78
|
-
return { steps: results, success, fileRegistry }
|
|
79
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator — executes installation steps in sequence
|
|
3
|
+
* and collects results into a unified RunReport.
|
|
4
|
+
*
|
|
5
|
+
* Each step is independently retriable; the orchestrator
|
|
6
|
+
* never throws — it captures failures in step results.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} StepResult
|
|
11
|
+
* @property {string} stepId - Stable identifier
|
|
12
|
+
* @property {string} name - Human-readable label
|
|
13
|
+
* @property {boolean} success
|
|
14
|
+
* @property {boolean} skipped
|
|
15
|
+
* @property {object} details - Step-specific metadata
|
|
16
|
+
* @property {string} [error] - Error message if !success
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} StepSpec
|
|
21
|
+
* @property {string} id
|
|
22
|
+
* @property {Function} fn - async (opts, logger, _deps?) => StepResult
|
|
23
|
+
* @property {object} opts - Step-specific options
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} RunReport
|
|
28
|
+
* @property {StepResult[]} steps
|
|
29
|
+
* @property {boolean} success - True if all non-skipped steps succeeded
|
|
30
|
+
* @property {object} fileRegistry - Hash registry (currently MCP only)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Execute steps sequentially, collecting results.
|
|
35
|
+
*
|
|
36
|
+
* @param {StepSpec[]} steps - Ordered step specifications
|
|
37
|
+
* @param {object} logger - Logger instance
|
|
38
|
+
* @returns {Promise<RunReport>}
|
|
39
|
+
*/
|
|
40
|
+
export async function orchestrate(steps, logger) {
|
|
41
|
+
const results = []
|
|
42
|
+
const fileRegistry = {}
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < steps.length; i++) {
|
|
45
|
+
const { id, fn, opts } = steps[i]
|
|
46
|
+
logger.step(i + 1, steps.length, id)
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = await fn(opts, logger)
|
|
50
|
+
results.push(result)
|
|
51
|
+
|
|
52
|
+
// Collect file hashes from steps that produce them
|
|
53
|
+
if (result.details?.hash && result.details?.target) {
|
|
54
|
+
fileRegistry[result.details.target] = result.details.hash
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (result.skipped) {
|
|
58
|
+
logger.debug(` Skipped: ${id}`)
|
|
59
|
+
} else if (result.success) {
|
|
60
|
+
logger.success(result.name || id)
|
|
61
|
+
} else {
|
|
62
|
+
logger.warn(`${result.name || id}: ${result.error || 'completed with errors'}`)
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error(`${id} threw unexpectedly: ${err.message}`)
|
|
66
|
+
results.push({
|
|
67
|
+
stepId: id,
|
|
68
|
+
name: id,
|
|
69
|
+
success: false,
|
|
70
|
+
skipped: false,
|
|
71
|
+
details: {},
|
|
72
|
+
error: err.message,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const success = results.every(r => r.skipped || r.success)
|
|
78
|
+
return { steps: results, success, fileRegistry }
|
|
79
|
+
}
|
package/src/core/registry.js
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Load and parse registry.json from disk.
|
|
9
|
-
*
|
|
10
|
-
* @param {string} [customPath] - Override path to registry.json
|
|
11
|
-
* @returns {Promise<object>} Parsed registry object
|
|
12
|
-
*/
|
|
13
|
-
export async function loadRegistry(customPath) {
|
|
14
|
-
const registryPath = customPath || path.resolve(__dirname, '../../registry.json')
|
|
15
|
-
const content = await fse.readFile(registryPath, 'utf8')
|
|
16
|
-
return JSON.parse(content)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get IDs of all plugins marked as default.
|
|
21
|
-
*
|
|
22
|
-
* @param {object} registry - Loaded registry object
|
|
23
|
-
* @returns {string[]} Array of plugin IDs
|
|
24
|
-
*/
|
|
25
|
-
export function getDefaultPlugins(registry) {
|
|
26
|
-
return Object.entries(registry.plugins || {})
|
|
27
|
-
.filter(([, def]) => def.default)
|
|
28
|
-
.map(([id]) => id)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve requested plugin IDs to plugin definitions.
|
|
33
|
-
*
|
|
34
|
-
* @param {object} registry - Registry object
|
|
35
|
-
* @param {object} config - Merged config (uses config.plugins)
|
|
36
|
-
* @returns {Array<{id: string, def: object}>} Resolved plugin entries
|
|
37
|
-
*/
|
|
38
|
-
export function resolvePlugins(registry, config) {
|
|
39
|
-
const pluginIds = config.plugins === 'default'
|
|
40
|
-
? getDefaultPlugins(registry)
|
|
41
|
-
: config.plugins === 'none'
|
|
42
|
-
? []
|
|
43
|
-
: Array.isArray(config.plugins) ? config.plugins : []
|
|
44
|
-
|
|
45
|
-
return pluginIds
|
|
46
|
-
.map(id => ({ id, def: registry.plugins?.[id] }))
|
|
47
|
-
.filter(p => p.def)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Resolve a model alias to a model ID using the registry's model map.
|
|
52
|
-
*
|
|
53
|
-
* @param {object} userSettings - registry.userSettings
|
|
54
|
-
* @param {string} [preferredAlias] - User-specified model alias (e.g. 'kimi')
|
|
55
|
-
* @returns {string} Resolved model ID (e.g. 'Kimi-K2.5')
|
|
56
|
-
*/
|
|
57
|
-
export function resolveModelId(userSettings = {}, preferredAlias) {
|
|
58
|
-
const alias = preferredAlias || userSettings.defaultModel || 'minimax'
|
|
59
|
-
return (userSettings.models || {})[alias] || alias
|
|
60
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Load and parse registry.json from disk.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} [customPath] - Override path to registry.json
|
|
11
|
+
* @returns {Promise<object>} Parsed registry object
|
|
12
|
+
*/
|
|
13
|
+
export async function loadRegistry(customPath) {
|
|
14
|
+
const registryPath = customPath || path.resolve(__dirname, '../../registry.json')
|
|
15
|
+
const content = await fse.readFile(registryPath, 'utf8')
|
|
16
|
+
return JSON.parse(content)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get IDs of all plugins marked as default.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} registry - Loaded registry object
|
|
23
|
+
* @returns {string[]} Array of plugin IDs
|
|
24
|
+
*/
|
|
25
|
+
export function getDefaultPlugins(registry) {
|
|
26
|
+
return Object.entries(registry.plugins || {})
|
|
27
|
+
.filter(([, def]) => def.default)
|
|
28
|
+
.map(([id]) => id)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve requested plugin IDs to plugin definitions.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} registry - Registry object
|
|
35
|
+
* @param {object} config - Merged config (uses config.plugins)
|
|
36
|
+
* @returns {Array<{id: string, def: object}>} Resolved plugin entries
|
|
37
|
+
*/
|
|
38
|
+
export function resolvePlugins(registry, config) {
|
|
39
|
+
const pluginIds = config.plugins === 'default'
|
|
40
|
+
? getDefaultPlugins(registry)
|
|
41
|
+
: config.plugins === 'none'
|
|
42
|
+
? []
|
|
43
|
+
: Array.isArray(config.plugins) ? config.plugins : []
|
|
44
|
+
|
|
45
|
+
return pluginIds
|
|
46
|
+
.map(id => ({ id, def: registry.plugins?.[id] }))
|
|
47
|
+
.filter(p => p.def)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a model alias to a model ID using the registry's model map.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} userSettings - registry.userSettings
|
|
54
|
+
* @param {string} [preferredAlias] - User-specified model alias (e.g. 'kimi')
|
|
55
|
+
* @returns {string} Resolved model ID (e.g. 'Kimi-K2.5')
|
|
56
|
+
*/
|
|
57
|
+
export function resolveModelId(userSettings = {}, preferredAlias) {
|
|
58
|
+
const alias = preferredAlias || userSettings.defaultModel || 'minimax'
|
|
59
|
+
return (userSettings.models || {})[alias] || alias
|
|
60
|
+
}
|
package/src/steps/add-plugin.js
CHANGED
|
@@ -1,116 +1,148 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Step: Install plugins using Claude Code CLI.
|
|
3
|
-
*
|
|
4
|
-
* Uses `claude plugin install <plugin>@<marketplace> --scope <scope>` commands
|
|
5
|
-
* to install plugins at project scope.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @returns {
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Step: Install plugins using Claude Code CLI.
|
|
3
|
+
*
|
|
4
|
+
* Uses `claude plugin install <plugin>@<marketplace> --scope <scope>` commands
|
|
5
|
+
* to install plugins at project scope.
|
|
6
|
+
*
|
|
7
|
+
* Requires `claude` CLI to be installed. If not found, the step is skipped
|
|
8
|
+
* with a warning instead of failing the entire installation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn, execSync } from 'node:child_process'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if the `claude` CLI is available on PATH.
|
|
19
|
+
*
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function isClaudeCliAvailable() {
|
|
23
|
+
try {
|
|
24
|
+
execSync('command -v claude', { stdio: 'pipe' })
|
|
25
|
+
return true
|
|
26
|
+
} catch {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} opts
|
|
33
|
+
* @param {string} opts.targetDir - Project root
|
|
34
|
+
* @param {Array<{id: string, def: object}>} opts.plugins - Resolved plugins with scope
|
|
35
|
+
* @param {boolean} opts.skip - Skip this step
|
|
36
|
+
* @param {object} logger
|
|
37
|
+
* @param {object} [_deps] - Injectable dependencies for testing
|
|
38
|
+
* @returns {Promise<import('../core/orchestrator.js').StepResult>}
|
|
39
|
+
*/
|
|
40
|
+
export async function addPlugin(opts, logger, _deps = {}) {
|
|
41
|
+
if (opts.skip) {
|
|
42
|
+
return { stepId: 'add-plugin', name: 'Add Plugin (CLI)', success: true, skipped: true, details: {} }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if claude CLI is available
|
|
46
|
+
const checkCli = _deps.isClaudeCliAvailable || isClaudeCliAvailable
|
|
47
|
+
if (!checkCli()) {
|
|
48
|
+
logger.warn('Claude Code CLI not found. Skipping CLI plugin installation.')
|
|
49
|
+
logger.warn('Install Claude Code CLI (https://docs.anthropic.com/en/docs/claude-code) to enable this step.')
|
|
50
|
+
return {
|
|
51
|
+
stepId: 'add-plugin',
|
|
52
|
+
name: 'Add Plugin (CLI)',
|
|
53
|
+
success: true,
|
|
54
|
+
skipped: true,
|
|
55
|
+
details: { reason: 'claude CLI not found' },
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const projectPlugins = opts.plugins.filter(p => p.def.scope === 'project' || !p.def.scope)
|
|
60
|
+
|
|
61
|
+
if (projectPlugins.length === 0) {
|
|
62
|
+
logger.debug('No project-scoped plugins to install via CLI')
|
|
63
|
+
return {
|
|
64
|
+
stepId: 'add-plugin',
|
|
65
|
+
name: 'Add Plugin (CLI)',
|
|
66
|
+
success: true,
|
|
67
|
+
skipped: false,
|
|
68
|
+
details: { installed: [], errors: [] },
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const installed = []
|
|
73
|
+
const errors = []
|
|
74
|
+
|
|
75
|
+
for (const { id, def } of projectPlugins) {
|
|
76
|
+
const pluginKey = `${id}@${def.marketplace}`
|
|
77
|
+
const scope = def.scope || 'project'
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
logger.info(` Installing ${pluginKey} (scope: ${scope})...`)
|
|
81
|
+
|
|
82
|
+
await execCommand('claude', [
|
|
83
|
+
'plugin',
|
|
84
|
+
'install',
|
|
85
|
+
pluginKey,
|
|
86
|
+
'--scope',
|
|
87
|
+
scope,
|
|
88
|
+
], logger)
|
|
89
|
+
|
|
90
|
+
installed.push({ id: pluginKey, scope })
|
|
91
|
+
logger.success(` ✓ ${pluginKey} installed`)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
errors.push({ id: pluginKey, error: err.message })
|
|
94
|
+
logger.warn(` ✗ Failed to install ${pluginKey}: ${err.message}`)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
stepId: 'add-plugin',
|
|
100
|
+
name: 'Add Plugin (CLI)',
|
|
101
|
+
success: errors.length === 0,
|
|
102
|
+
skipped: false,
|
|
103
|
+
details: {
|
|
104
|
+
installed,
|
|
105
|
+
errors,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Execute a command and return a promise.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} command - Command to run
|
|
114
|
+
* @param {string[]} args - Command arguments
|
|
115
|
+
* @param {object} logger - Logger instance
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
function execCommand(command, args, logger) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const proc = spawn(command, args, {
|
|
121
|
+
shell: true,
|
|
122
|
+
stdio: 'pipe',
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
let stdout = ''
|
|
126
|
+
let stderr = ''
|
|
127
|
+
|
|
128
|
+
proc.stdout.on('data', (data) => {
|
|
129
|
+
stdout += data.toString()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
proc.stderr.on('data', (data) => {
|
|
133
|
+
stderr += data.toString()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
proc.on('close', (code) => {
|
|
137
|
+
if (code === 0) {
|
|
138
|
+
resolve()
|
|
139
|
+
} else {
|
|
140
|
+
reject(new Error(stderr || stdout || `Exit code: ${code}`))
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
proc.on('error', (err) => {
|
|
145
|
+
reject(err)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
}
|