cc-skills 0.1.0
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 -0
- package/README.md +165 -0
- package/README_CN.md +165 -0
- package/bin/cc-skills.js +14 -0
- package/package.json +43 -0
- package/src/cli.js +238 -0
- package/src/lib.js +472 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# cc-skills
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/cc-skills)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
A thin wrapper around `npx skills add` for **CC-MIRROR** Claude Code instances.
|
|
8
|
+
|
|
9
|
+
It discovers one or more CC-MIRROR variants, sets `CLAUDE_CONFIG_DIR` to the target instance's `config/` directory, and then delegates the actual install to `npx skills add`.
|
|
10
|
+
|
|
11
|
+
[中文文档](./README_CN.md)
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Multi-instance support** - Install skills to one or more CC-MIRROR instances
|
|
16
|
+
- **Automatic discovery** - Detects CC-MIRROR instances automatically
|
|
17
|
+
- **Zero dependencies** - Uses only Node.js built-in modules
|
|
18
|
+
- **Dry-run mode** - Preview commands before execution
|
|
19
|
+
- **Graceful fallback** - Works even without cc-mirror CLI
|
|
20
|
+
|
|
21
|
+
## Why this exists
|
|
22
|
+
|
|
23
|
+
`skills` already knows how to install into a Claude Code config directory when `CLAUDE_CONFIG_DIR` is set.
|
|
24
|
+
|
|
25
|
+
What this package adds is only the **instance orchestration layer**:
|
|
26
|
+
|
|
27
|
+
- Discover CC-MIRROR instances
|
|
28
|
+
- Target one instance or all instances
|
|
29
|
+
- Loop over targets
|
|
30
|
+
- Set `CLAUDE_CONFIG_DIR` per instance
|
|
31
|
+
- Forward the real install to `npx skills add`
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Run directly with `npx`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx cc-skills instances
|
|
39
|
+
npx cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or install globally:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g cc-skills
|
|
46
|
+
cc-skills instances
|
|
47
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### `instances` - List CC-MIRROR instances
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cc-skills instances
|
|
56
|
+
cc-skills instances --json
|
|
57
|
+
cc-skills instances --cc-root ~/.cc-mirror
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `add` - Install skills to instances
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cc-skills add <source> (--instance <name> | --all-instances) [options]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Examples:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Single instance
|
|
70
|
+
cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
71
|
+
|
|
72
|
+
# Multiple named instances
|
|
73
|
+
cc-skills add better-auth/skills --instance mclaude-auth --instance mclaude-security --copy -y
|
|
74
|
+
|
|
75
|
+
# All instances
|
|
76
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
77
|
+
|
|
78
|
+
# Dry run (preview without executing)
|
|
79
|
+
cc-skills add better-auth/skills --instance mclaude-auth --dry-run --copy -y
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Options
|
|
83
|
+
|
|
84
|
+
### Wrapper options (handled by cc-skills)
|
|
85
|
+
|
|
86
|
+
| Option | Description |
|
|
87
|
+
|--------|-------------|
|
|
88
|
+
| `-i, --instance <name>` | Target a specific CC-MIRROR instance (repeatable) |
|
|
89
|
+
| `--all-instances` | Target all discovered instances |
|
|
90
|
+
| `--cc-root <path>` | Custom CC-MIRROR root directory |
|
|
91
|
+
| `--cc-bin-dir <path>` | Forwarded to `cc-mirror list` |
|
|
92
|
+
| `--dry-run` | Print commands without executing them |
|
|
93
|
+
| `--fail-fast` | Stop after the first failed install |
|
|
94
|
+
| `--verbose` | Print the underlying `npx` commands |
|
|
95
|
+
|
|
96
|
+
### Forwarded options (passed to `npx skills add`)
|
|
97
|
+
|
|
98
|
+
| Option | Description |
|
|
99
|
+
|--------|-------------|
|
|
100
|
+
| `--copy` | Copy files instead of symlinking |
|
|
101
|
+
| `-y` | Skip confirmation prompts |
|
|
102
|
+
| `--skill <name>` | Install specific skill (use `*` for all) |
|
|
103
|
+
| `--list` | List available skills in source |
|
|
104
|
+
|
|
105
|
+
## Important Behavior
|
|
106
|
+
|
|
107
|
+
This wrapper always injects:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
-g -a claude-code
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This means every install is done in the target instance's global Claude Code scope.
|
|
114
|
+
|
|
115
|
+
## Do not use `skills --all`
|
|
116
|
+
|
|
117
|
+
The `skills --all` flag means **all skills to all agents**, which is broader than this wrapper's scope.
|
|
118
|
+
|
|
119
|
+
If you want all skills from a repository, use:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cc-skills add owner/repo --all-instances --skill "*" --copy -y
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## How It Works
|
|
126
|
+
|
|
127
|
+
**Discovery phase:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx -y cc-mirror list --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Installation phase (per instance):**
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
CLAUDE_CONFIG_DIR="~/.cc-mirror/<instance>/config" \
|
|
137
|
+
npx -y skills add <source> -g -a claude-code ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Fallback Behavior
|
|
141
|
+
|
|
142
|
+
If `cc-mirror list --json` is unavailable or fails, the wrapper falls back to scanning the CC-MIRROR root directory and reading `variant.json` / `config/` directly.
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Run tests
|
|
148
|
+
npm test
|
|
149
|
+
|
|
150
|
+
# Run CLI locally
|
|
151
|
+
node bin/cc-skills.js help
|
|
152
|
+
|
|
153
|
+
# Create local tarball
|
|
154
|
+
npm run pack:local
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Publishing
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm publish
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
[MIT](./LICENSE)
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# cc-skills
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/cc-skills)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**CC-MIRROR** Claude Code 实例的 `npx skills add` 轻量封装工具。
|
|
8
|
+
|
|
9
|
+
它可以发现一个或多个 CC-MIRROR 变体,将 `CLAUDE_CONFIG_DIR` 设置为目标实例的 `config/` 目录,然后将实际安装委托给 `npx skills add`。
|
|
10
|
+
|
|
11
|
+
[English](./README.md)
|
|
12
|
+
|
|
13
|
+
## 特性
|
|
14
|
+
|
|
15
|
+
- **多实例支持** - 将技能安装到一个或多个 CC-MIRROR 实例
|
|
16
|
+
- **自动发现** - 自动检测 CC-MIRROR 实例
|
|
17
|
+
- **零依赖** - 仅使用 Node.js 内置模块
|
|
18
|
+
- **试运行模式** - 执行前预览命令
|
|
19
|
+
- **优雅降级** - 即使没有 cc-mirror CLI 也能工作
|
|
20
|
+
|
|
21
|
+
## 为什么需要这个工具
|
|
22
|
+
|
|
23
|
+
`skills` 已经知道如何在设置 `CLAUDE_CONFIG_DIR` 时安装到 Claude Code 配置目录。
|
|
24
|
+
|
|
25
|
+
本工具仅添加了**实例编排层**:
|
|
26
|
+
|
|
27
|
+
- 发现 CC-MIRROR 实例
|
|
28
|
+
- 定位单个或所有实例
|
|
29
|
+
- 遍历目标实例
|
|
30
|
+
- 为每个实例设置 `CLAUDE_CONFIG_DIR`
|
|
31
|
+
- 将实际安装转发给 `npx skills add`
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
直接使用 `npx` 运行:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx cc-skills instances
|
|
39
|
+
npx cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
或全局安装:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g cc-skills
|
|
46
|
+
cc-skills instances
|
|
47
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 使用方法
|
|
51
|
+
|
|
52
|
+
### `instances` - 列出 CC-MIRROR 实例
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cc-skills instances
|
|
56
|
+
cc-skills instances --json
|
|
57
|
+
cc-skills instances --cc-root ~/.cc-mirror
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `add` - 向实例安装技能
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cc-skills add <source> (--instance <name> | --all-instances) [选项]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**示例:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 单个实例
|
|
70
|
+
cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
71
|
+
|
|
72
|
+
# 多个指定实例
|
|
73
|
+
cc-skills add better-auth/skills --instance mclaude-auth --instance mclaude-security --copy -y
|
|
74
|
+
|
|
75
|
+
# 所有实例
|
|
76
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
77
|
+
|
|
78
|
+
# 试运行(预览而不执行)
|
|
79
|
+
cc-skills add better-auth/skills --instance mclaude-auth --dry-run --copy -y
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 选项
|
|
83
|
+
|
|
84
|
+
### 封装工具选项(由 cc-skills 处理)
|
|
85
|
+
|
|
86
|
+
| 选项 | 描述 |
|
|
87
|
+
|------|------|
|
|
88
|
+
| `-i, --instance <name>` | 定位特定的 CC-MIRROR 实例(可重复使用) |
|
|
89
|
+
| `--all-instances` | 定位所有已发现的实例 |
|
|
90
|
+
| `--cc-root <path>` | 自定义 CC-MIRROR 根目录 |
|
|
91
|
+
| `--cc-bin-dir <path>` | 转发给 `cc-mirror list` |
|
|
92
|
+
| `--dry-run` | 仅打印命令,不执行 |
|
|
93
|
+
| `--fail-fast` | 首次安装失败后停止 |
|
|
94
|
+
| `--verbose` | 打印底层的 `npx` 命令 |
|
|
95
|
+
|
|
96
|
+
### 转发选项(传递给 `npx skills add`)
|
|
97
|
+
|
|
98
|
+
| 选项 | 描述 |
|
|
99
|
+
|------|------|
|
|
100
|
+
| `--copy` | 复制文件而非创建符号链接 |
|
|
101
|
+
| `-y` | 跳过确认提示 |
|
|
102
|
+
| `--skill <name>` | 安装特定技能(使用 `*` 表示全部) |
|
|
103
|
+
| `--list` | 列出源中可用的技能 |
|
|
104
|
+
|
|
105
|
+
## 重要行为
|
|
106
|
+
|
|
107
|
+
此封装工具始终会注入:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
-g -a claude-code
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
这意味着每次安装都在目标实例的全局 Claude Code 范围内进行。
|
|
114
|
+
|
|
115
|
+
## 不要使用 `skills --all`
|
|
116
|
+
|
|
117
|
+
`skills --all` 标志意味着**所有技能安装到所有代理**,这超出了本工具的范围。
|
|
118
|
+
|
|
119
|
+
如果你想要仓库中的所有技能,请使用:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cc-skills add owner/repo --all-instances --skill "*" --copy -y
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 工作原理
|
|
126
|
+
|
|
127
|
+
**发现阶段:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx -y cc-mirror list --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**安装阶段(每个实例):**
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
CLAUDE_CONFIG_DIR="~/.cc-mirror/<instance>/config" \
|
|
137
|
+
npx -y skills add <source> -g -a claude-code ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 降级行为
|
|
141
|
+
|
|
142
|
+
如果 `cc-mirror list --json` 不可用或失败,封装工具会降级为扫描 CC-MIRROR 根目录并直接读取 `variant.json` / `config/`。
|
|
143
|
+
|
|
144
|
+
## 开发
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# 运行测试
|
|
148
|
+
npm test
|
|
149
|
+
|
|
150
|
+
# 本地运行 CLI
|
|
151
|
+
node bin/cc-skills.js help
|
|
152
|
+
|
|
153
|
+
# 创建本地打包文件
|
|
154
|
+
npm run pack:local
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 发布
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm publish
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 许可证
|
|
164
|
+
|
|
165
|
+
[MIT](./LICENSE)
|
package/bin/cc-skills.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { main } = require('../src/cli');
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const exitCode = main(process.argv.slice(2));
|
|
7
|
+
if (typeof exitCode === 'number' && exitCode !== 0) {
|
|
8
|
+
process.exitCode = exitCode;
|
|
9
|
+
}
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const message = error && error.message ? error.message : String(error);
|
|
12
|
+
console.error(`Error: ${message}`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Thin wrapper around `npx skills add` that installs skills into one or more CC-MIRROR Claude Code instances by setting CLAUDE_CONFIG_DIR per instance.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Lyon <lyon.liang12@gmail.com>",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"cc-skills": "bin/cc-skills.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"src",
|
|
14
|
+
"README.md",
|
|
15
|
+
"README_CN.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.17"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"cc-mirror",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"skills",
|
|
25
|
+
"agent-skills",
|
|
26
|
+
"wrapper",
|
|
27
|
+
"claude_config_dir",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node --test",
|
|
32
|
+
"start": "node bin/cc-skills.js",
|
|
33
|
+
"pack:local": "npm pack"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/1WorldCapture/cc-skills.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/1WorldCapture/cc-skills#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/1WorldCapture/cc-skills/issues"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
3
|
+
const {
|
|
4
|
+
NPX_BIN,
|
|
5
|
+
buildSkillsAddNpxArgs,
|
|
6
|
+
discoverInstances,
|
|
7
|
+
formatCommand,
|
|
8
|
+
hasListFlag,
|
|
9
|
+
normalizeSkillsAddArgs,
|
|
10
|
+
parseAddArgs,
|
|
11
|
+
parseInstancesArgs,
|
|
12
|
+
runCommand,
|
|
13
|
+
selectInstances,
|
|
14
|
+
summarizeResults,
|
|
15
|
+
} = require('./lib');
|
|
16
|
+
|
|
17
|
+
function printMainHelp() {
|
|
18
|
+
console.log(`
|
|
19
|
+
${pkg.name} v${pkg.version}
|
|
20
|
+
|
|
21
|
+
Thin wrapper around \`npx skills add\` for CC-MIRROR Claude Code instances.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
cc-skills add <source> (--instance <name> | --all-instances) [skills add args...]
|
|
25
|
+
cc-skills instances [--json]
|
|
26
|
+
cc-skills help [command]
|
|
27
|
+
|
|
28
|
+
Commands:
|
|
29
|
+
add Install one skill package into one or more CC-MIRROR instances
|
|
30
|
+
instances List discovered CC-MIRROR instances
|
|
31
|
+
help Show help
|
|
32
|
+
|
|
33
|
+
Global examples:
|
|
34
|
+
cc-skills instances
|
|
35
|
+
cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
36
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- This wrapper always targets Claude Code and forces global install mode.
|
|
40
|
+
- Internally it calls: npx -y cc-mirror list --json
|
|
41
|
+
- Then, per instance, it calls: npx -y skills add <source> -g -a claude-code ...
|
|
42
|
+
- Do not pass \`skills --all\`; use \`--skill "*"\` instead.
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printInstancesHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
Usage:
|
|
49
|
+
cc-skills instances [options]
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
--cc-root <path> Custom CC-MIRROR root (default: ~/.cc-mirror)
|
|
53
|
+
--cc-bin-dir <path> Forward a custom --bin-dir to cc-mirror list
|
|
54
|
+
--json Print raw JSON
|
|
55
|
+
--verbose Print the underlying cc-mirror command
|
|
56
|
+
-h, --help Show this help
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function printAddHelp() {
|
|
61
|
+
console.log(`
|
|
62
|
+
Usage:
|
|
63
|
+
cc-skills add <source> [wrapper options] [skills add args...]
|
|
64
|
+
|
|
65
|
+
Wrapper options:
|
|
66
|
+
-i, --instance <name> Target a specific CC-MIRROR instance (repeatable)
|
|
67
|
+
--all-instances Target every discovered CC-MIRROR instance
|
|
68
|
+
--cc-root <path> Custom CC-MIRROR root (default: ~/.cc-mirror)
|
|
69
|
+
--cc-bin-dir <path> Forward a custom --bin-dir to cc-mirror list
|
|
70
|
+
--dry-run Print commands without executing them
|
|
71
|
+
--fail-fast Stop after the first failed install
|
|
72
|
+
--verbose Print underlying npx commands
|
|
73
|
+
-h, --help Show this help
|
|
74
|
+
|
|
75
|
+
Forwarded to \`skills add\`:
|
|
76
|
+
Any unrecognized args are passed through to \`npx skills add\`.
|
|
77
|
+
Common examples: --copy, -y, --skill <name>, --list
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
cc-skills add better-auth/skills --instance mclaude-auth --copy -y
|
|
81
|
+
cc-skills add better-auth/skills --instance mclaude-auth --skill organization --copy -y
|
|
82
|
+
cc-skills add better-auth/skills --all-instances --skill "*" --copy -y
|
|
83
|
+
cc-skills add better-auth/skills --instance mclaude-auth --dry-run --copy -y
|
|
84
|
+
|
|
85
|
+
Important:
|
|
86
|
+
- The wrapper injects \`-g -a claude-code\` automatically.
|
|
87
|
+
- \`skills --all\` is intentionally blocked because it installs to all agents.
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function printInstances(instances, json) {
|
|
92
|
+
if (json) {
|
|
93
|
+
console.log(JSON.stringify(instances, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (instances.length === 0) {
|
|
98
|
+
console.log('No CC-MIRROR instances found.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const instance of instances) {
|
|
103
|
+
const provider = instance.provider || 'unknown';
|
|
104
|
+
console.log(`${instance.name}\t${provider}\t${instance.configDir}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function handleInstances(argv) {
|
|
109
|
+
const options = parseInstancesArgs(argv);
|
|
110
|
+
if (options.help) {
|
|
111
|
+
printInstancesHelp();
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const instances = discoverInstances(options);
|
|
116
|
+
printInstances(instances, options.json);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function handleAdd(argv) {
|
|
121
|
+
const options = parseAddArgs(argv);
|
|
122
|
+
if (options.help) {
|
|
123
|
+
printAddHelp();
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const discovered = discoverInstances({
|
|
128
|
+
ccRoot: options.ccRoot,
|
|
129
|
+
ccBinDir: options.ccBinDir,
|
|
130
|
+
verbose: options.verbose,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (discovered.length === 0) {
|
|
134
|
+
throw new Error('No CC-MIRROR instances found. Run `cc-skills instances` to verify discovery.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const selected = selectInstances(discovered, options);
|
|
138
|
+
if (selected.length === 0) {
|
|
139
|
+
throw new Error('No target instances selected.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const normalizedSkillsArgs = normalizeSkillsAddArgs(options.skillsArgs);
|
|
143
|
+
const listOnly = hasListFlag(normalizedSkillsArgs);
|
|
144
|
+
const targets = listOnly && selected.length > 1 ? selected.slice(0, 1) : selected;
|
|
145
|
+
const npxArgs = buildSkillsAddNpxArgs(options.source, normalizedSkillsArgs);
|
|
146
|
+
|
|
147
|
+
if (listOnly && selected.length > 1) {
|
|
148
|
+
console.error('`skills add --list` does not depend on the target instance. Running it once for the first selected instance.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const results = [];
|
|
152
|
+
|
|
153
|
+
for (const target of targets) {
|
|
154
|
+
const envPatch = { CLAUDE_CONFIG_DIR: target.configDir };
|
|
155
|
+
const env = { ...process.env, ...envPatch };
|
|
156
|
+
|
|
157
|
+
console.error(`\n==> [${target.name}] ${target.configDir}`);
|
|
158
|
+
|
|
159
|
+
if (options.dryRun) {
|
|
160
|
+
console.log(formatCommand(NPX_BIN, npxArgs, envPatch));
|
|
161
|
+
results.push({
|
|
162
|
+
instance: target.name,
|
|
163
|
+
configDir: target.configDir,
|
|
164
|
+
ok: true,
|
|
165
|
+
dryRun: true,
|
|
166
|
+
exitCode: 0,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const result = runCommand(NPX_BIN, npxArgs, {
|
|
172
|
+
env,
|
|
173
|
+
stdio: 'inherit',
|
|
174
|
+
verbose: options.verbose,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const ok = result.status === 0;
|
|
178
|
+
results.push({
|
|
179
|
+
instance: target.name,
|
|
180
|
+
configDir: target.configDir,
|
|
181
|
+
ok,
|
|
182
|
+
dryRun: false,
|
|
183
|
+
exitCode: result.status ?? 1,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (!ok && options.failFast) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const summary = summarizeResults(results);
|
|
192
|
+
console.error(`\nSummary: ${summary.successCount}/${summary.total} succeeded.`);
|
|
193
|
+
for (const result of results) {
|
|
194
|
+
if (result.ok) {
|
|
195
|
+
console.error(` ✓ ${result.instance} -> ${result.configDir}`);
|
|
196
|
+
} else {
|
|
197
|
+
console.error(` ✗ ${result.instance} -> ${result.configDir} (exit ${result.exitCode})`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return summary.failureCount === 0 ? 0 : 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function main(argv = process.argv.slice(2)) {
|
|
205
|
+
const [command, ...rest] = argv;
|
|
206
|
+
|
|
207
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
208
|
+
const subcommand = rest[0];
|
|
209
|
+
if (subcommand === 'add') {
|
|
210
|
+
printAddHelp();
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
if (subcommand === 'instances') {
|
|
214
|
+
printInstancesHelp();
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
printMainHelp();
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
222
|
+
console.log(pkg.version);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
switch (command) {
|
|
227
|
+
case 'instances':
|
|
228
|
+
return handleInstances(rest);
|
|
229
|
+
case 'add':
|
|
230
|
+
return handleAdd(rest);
|
|
231
|
+
default:
|
|
232
|
+
throw new Error(`Unknown command: ${command}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
main,
|
|
238
|
+
};
|
package/src/lib.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
|
|
6
|
+
const NPX_BIN = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
7
|
+
const DEFAULT_CC_MIRROR_ROOT = path.join(os.homedir(), '.cc-mirror');
|
|
8
|
+
|
|
9
|
+
function expandHome(input) {
|
|
10
|
+
if (!input) return input;
|
|
11
|
+
if (input === '~') return os.homedir();
|
|
12
|
+
if (input.startsWith('~/') || input.startsWith('~\\')) {
|
|
13
|
+
return path.join(os.homedir(), input.slice(2));
|
|
14
|
+
}
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function unique(values) {
|
|
19
|
+
return [...new Set(values.filter(Boolean))];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatCommand(command, args, envPatch) {
|
|
23
|
+
const envPrefix = envPatch
|
|
24
|
+
? Object.entries(envPatch)
|
|
25
|
+
.map(([key, value]) => `${key}=${JSON.stringify(String(value))}`)
|
|
26
|
+
.join(' ')
|
|
27
|
+
: '';
|
|
28
|
+
return [envPrefix, command, ...args].filter(Boolean).join(' ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runCommand(command, args, options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
env,
|
|
34
|
+
cwd,
|
|
35
|
+
stdio = 'pipe',
|
|
36
|
+
verbose = false,
|
|
37
|
+
} = options;
|
|
38
|
+
|
|
39
|
+
if (verbose) {
|
|
40
|
+
console.error(`$ ${formatCommand(command, args)}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = spawnSync(command, args, {
|
|
44
|
+
env,
|
|
45
|
+
cwd,
|
|
46
|
+
stdio,
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (result.error) {
|
|
51
|
+
throw result.error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseLooseJsonArray(stdout) {
|
|
58
|
+
const text = String(stdout || '').trim();
|
|
59
|
+
if (!text) return [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(text);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const firstBracket = text.indexOf('[');
|
|
65
|
+
const lastBracket = text.lastIndexOf(']');
|
|
66
|
+
|
|
67
|
+
if (firstBracket !== -1 && lastBracket !== -1 && lastBracket > firstBracket) {
|
|
68
|
+
return JSON.parse(text.slice(firstBracket, lastBracket + 1));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readJsonIfExists(filePath) {
|
|
76
|
+
if (!fs.existsSync(filePath)) return null;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sanitizeDiscoveredInstances(instances, ccRoot) {
|
|
85
|
+
const root = expandHome(ccRoot || DEFAULT_CC_MIRROR_ROOT);
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const result = [];
|
|
88
|
+
|
|
89
|
+
for (const instance of Array.isArray(instances) ? instances : []) {
|
|
90
|
+
if (!instance || typeof instance !== 'object') continue;
|
|
91
|
+
const name = typeof instance.name === 'string' ? instance.name.trim() : '';
|
|
92
|
+
if (!name || seen.has(name)) continue;
|
|
93
|
+
seen.add(name);
|
|
94
|
+
|
|
95
|
+
const derivedConfigDir = path.join(root, name, 'config');
|
|
96
|
+
result.push({
|
|
97
|
+
name,
|
|
98
|
+
provider: instance.provider ?? null,
|
|
99
|
+
claudeVersion: instance.claudeVersion ?? null,
|
|
100
|
+
wrapperPath: instance.wrapperPath ?? null,
|
|
101
|
+
configDir: instance.configDir ? expandHome(instance.configDir) : derivedConfigDir,
|
|
102
|
+
raw: instance,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function discoverInstancesFromFilesystem(ccRoot) {
|
|
110
|
+
const root = expandHome(ccRoot || DEFAULT_CC_MIRROR_ROOT);
|
|
111
|
+
if (!fs.existsSync(root)) return [];
|
|
112
|
+
|
|
113
|
+
const entries = fs
|
|
114
|
+
.readdirSync(root, { withFileTypes: true })
|
|
115
|
+
.filter((entry) => entry.isDirectory())
|
|
116
|
+
.filter((entry) => entry.name !== 'bin' && !entry.name.startsWith('.'));
|
|
117
|
+
|
|
118
|
+
const instances = [];
|
|
119
|
+
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const variantDir = path.join(root, entry.name);
|
|
122
|
+
const variantJsonPath = path.join(variantDir, 'variant.json');
|
|
123
|
+
const configDir = path.join(variantDir, 'config');
|
|
124
|
+
const meta = readJsonIfExists(variantJsonPath);
|
|
125
|
+
|
|
126
|
+
if (!meta && !fs.existsSync(configDir)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
instances.push({
|
|
131
|
+
name: entry.name,
|
|
132
|
+
provider: meta?.provider ?? null,
|
|
133
|
+
claudeVersion: meta?.nativeVersion ?? null,
|
|
134
|
+
wrapperPath: meta?.wrapperPath ?? null,
|
|
135
|
+
configDir: meta?.configDir ? expandHome(meta.configDir) : configDir,
|
|
136
|
+
raw: meta,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return sanitizeDiscoveredInstances(instances, root);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function discoverInstances(options = {}) {
|
|
144
|
+
const { ccRoot, ccBinDir, verbose = false } = options;
|
|
145
|
+
const resolvedRoot = expandHome(ccRoot || DEFAULT_CC_MIRROR_ROOT);
|
|
146
|
+
const resolvedBinDir = ccBinDir ? expandHome(ccBinDir) : undefined;
|
|
147
|
+
|
|
148
|
+
let lastError = null;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const args = ['-y', 'cc-mirror', 'list', '--json'];
|
|
152
|
+
if (ccRoot) args.push('--root', resolvedRoot);
|
|
153
|
+
if (resolvedBinDir) args.push('--bin-dir', resolvedBinDir);
|
|
154
|
+
|
|
155
|
+
const result = runCommand(NPX_BIN, args, {
|
|
156
|
+
stdio: 'pipe',
|
|
157
|
+
verbose,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (result.status === 0) {
|
|
161
|
+
const parsed = parseLooseJsonArray(result.stdout);
|
|
162
|
+
return sanitizeDiscoveredInstances(parsed, resolvedRoot);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lastError = new Error(result.stderr || result.stdout || `cc-mirror exited with code ${result.status}`);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
lastError = error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const fallback = discoverInstancesFromFilesystem(resolvedRoot);
|
|
171
|
+
if (fallback.length > 0) {
|
|
172
|
+
if (verbose) {
|
|
173
|
+
console.error('Falling back to filesystem discovery from CC-MIRROR root.');
|
|
174
|
+
}
|
|
175
|
+
return fallback;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (lastError) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Unable to discover CC-MIRROR instances via \`npx cc-mirror list --json\`: ${lastError.message}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function selectInstances(discoveredInstances, options) {
|
|
188
|
+
const discovered = Array.isArray(discoveredInstances) ? discoveredInstances : [];
|
|
189
|
+
if (options.allInstances) return discovered;
|
|
190
|
+
|
|
191
|
+
const wanted = unique(options.instances || []);
|
|
192
|
+
const byName = new Map(discovered.map((instance) => [instance.name, instance]));
|
|
193
|
+
const selected = [];
|
|
194
|
+
const missing = [];
|
|
195
|
+
|
|
196
|
+
for (const name of wanted) {
|
|
197
|
+
const instance = byName.get(name);
|
|
198
|
+
if (instance) {
|
|
199
|
+
selected.push(instance);
|
|
200
|
+
} else {
|
|
201
|
+
missing.push(name);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (missing.length > 0) {
|
|
206
|
+
const available = discovered.map((instance) => instance.name).sort();
|
|
207
|
+
const availableText = available.length > 0 ? available.join(', ') : '(none found)';
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Unknown CC-MIRROR instance(s): ${missing.join(', ')}. Available instances: ${availableText}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return selected;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeSkillsAddArgs(args) {
|
|
217
|
+
const forwarded = [];
|
|
218
|
+
let hasGlobal = false;
|
|
219
|
+
let hasClaudeAgent = false;
|
|
220
|
+
|
|
221
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
222
|
+
const arg = args[index];
|
|
223
|
+
|
|
224
|
+
if (arg === '--all') {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'Do not pass `--all` through cc-skills because `skills --all` targets all agents. Use `--skill "*"` instead.'
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (arg === '-g' || arg === '--global') {
|
|
231
|
+
hasGlobal = true;
|
|
232
|
+
forwarded.push(arg);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (arg === '--agent') {
|
|
237
|
+
const value = args[index + 1];
|
|
238
|
+
if (!value) {
|
|
239
|
+
throw new Error('Missing value for `--agent`.');
|
|
240
|
+
}
|
|
241
|
+
index += 1;
|
|
242
|
+
if (value !== 'claude-code') {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`cc-skills only targets Claude Code instances. Remove \`--agent ${value}\` or change it to \`--agent claude-code\`.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
hasClaudeAgent = true;
|
|
248
|
+
forwarded.push('--agent', value);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (arg.startsWith('--agent=')) {
|
|
253
|
+
const value = arg.slice('--agent='.length);
|
|
254
|
+
if (value !== 'claude-code') {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`cc-skills only targets Claude Code instances. Remove \`${arg}\` or change it to \`--agent=claude-code\`.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
hasClaudeAgent = true;
|
|
260
|
+
forwarded.push(arg);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (arg === '-a') {
|
|
265
|
+
const value = args[index + 1];
|
|
266
|
+
if (!value) {
|
|
267
|
+
throw new Error('Missing value for `-a`.');
|
|
268
|
+
}
|
|
269
|
+
index += 1;
|
|
270
|
+
if (value !== 'claude-code') {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`cc-skills only targets Claude Code instances. Remove \`-a ${value}\` or change it to \`-a claude-code\`.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
hasClaudeAgent = true;
|
|
276
|
+
forwarded.push('-a', value);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
forwarded.push(arg);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!hasGlobal) {
|
|
284
|
+
forwarded.unshift('-g');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!hasClaudeAgent) {
|
|
288
|
+
forwarded.unshift('claude-code');
|
|
289
|
+
forwarded.unshift('-a');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return forwarded;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function buildSkillsAddNpxArgs(source, normalizedSkillsArgs) {
|
|
296
|
+
return ['-y', 'skills', 'add', source, ...normalizedSkillsArgs];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function hasListFlag(args) {
|
|
300
|
+
return args.includes('--list') || args.includes('-l');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseInstancesArgs(argv) {
|
|
304
|
+
const options = {
|
|
305
|
+
ccRoot: undefined,
|
|
306
|
+
ccBinDir: undefined,
|
|
307
|
+
json: false,
|
|
308
|
+
verbose: false,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
312
|
+
const arg = argv[index];
|
|
313
|
+
|
|
314
|
+
switch (arg) {
|
|
315
|
+
case '--cc-root': {
|
|
316
|
+
const value = argv[index + 1];
|
|
317
|
+
if (!value) throw new Error('Missing value for `--cc-root`.');
|
|
318
|
+
options.ccRoot = value;
|
|
319
|
+
index += 1;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case '--cc-bin-dir': {
|
|
323
|
+
const value = argv[index + 1];
|
|
324
|
+
if (!value) throw new Error('Missing value for `--cc-bin-dir`.');
|
|
325
|
+
options.ccBinDir = value;
|
|
326
|
+
index += 1;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case '--json':
|
|
330
|
+
options.json = true;
|
|
331
|
+
break;
|
|
332
|
+
case '--verbose':
|
|
333
|
+
options.verbose = true;
|
|
334
|
+
break;
|
|
335
|
+
case '--help':
|
|
336
|
+
case '-h':
|
|
337
|
+
options.help = true;
|
|
338
|
+
break;
|
|
339
|
+
default:
|
|
340
|
+
throw new Error(`Unknown option for \`instances\`: ${arg}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return options;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseAddArgs(argv) {
|
|
348
|
+
const options = {
|
|
349
|
+
source: undefined,
|
|
350
|
+
instances: [],
|
|
351
|
+
allInstances: false,
|
|
352
|
+
ccRoot: undefined,
|
|
353
|
+
ccBinDir: undefined,
|
|
354
|
+
dryRun: false,
|
|
355
|
+
failFast: false,
|
|
356
|
+
verbose: false,
|
|
357
|
+
help: false,
|
|
358
|
+
skillsArgs: [],
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
let passthroughMode = false;
|
|
362
|
+
|
|
363
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
364
|
+
const arg = argv[index];
|
|
365
|
+
|
|
366
|
+
if (passthroughMode) {
|
|
367
|
+
options.skillsArgs.push(arg);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (arg === '--') {
|
|
372
|
+
passthroughMode = true;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!options.source && !arg.startsWith('-')) {
|
|
377
|
+
options.source = arg;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
switch (arg) {
|
|
382
|
+
case '--instance':
|
|
383
|
+
case '-i': {
|
|
384
|
+
const value = argv[index + 1];
|
|
385
|
+
if (!value) throw new Error(`Missing value for \`${arg}\`.`);
|
|
386
|
+
options.instances.push(value);
|
|
387
|
+
index += 1;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case '--all-instances':
|
|
391
|
+
options.allInstances = true;
|
|
392
|
+
break;
|
|
393
|
+
case '--cc-root': {
|
|
394
|
+
const value = argv[index + 1];
|
|
395
|
+
if (!value) throw new Error('Missing value for `--cc-root`.');
|
|
396
|
+
options.ccRoot = value;
|
|
397
|
+
index += 1;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case '--cc-bin-dir': {
|
|
401
|
+
const value = argv[index + 1];
|
|
402
|
+
if (!value) throw new Error('Missing value for `--cc-bin-dir`.');
|
|
403
|
+
options.ccBinDir = value;
|
|
404
|
+
index += 1;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case '--dry-run':
|
|
408
|
+
options.dryRun = true;
|
|
409
|
+
break;
|
|
410
|
+
case '--fail-fast':
|
|
411
|
+
options.failFast = true;
|
|
412
|
+
break;
|
|
413
|
+
case '--verbose':
|
|
414
|
+
options.verbose = true;
|
|
415
|
+
break;
|
|
416
|
+
case '--help':
|
|
417
|
+
case '-h':
|
|
418
|
+
options.help = true;
|
|
419
|
+
break;
|
|
420
|
+
default:
|
|
421
|
+
options.skillsArgs.push(arg);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
options.instances = unique(options.instances);
|
|
427
|
+
|
|
428
|
+
if (!options.help) {
|
|
429
|
+
if (!options.source) {
|
|
430
|
+
throw new Error('Missing required <source> for `cc-skills add`.');
|
|
431
|
+
}
|
|
432
|
+
if (options.allInstances && options.instances.length > 0) {
|
|
433
|
+
throw new Error('Use either `--all-instances` or `--instance`, not both.');
|
|
434
|
+
}
|
|
435
|
+
if (!options.allInstances && options.instances.length === 0) {
|
|
436
|
+
throw new Error('Choose a target with `--instance <name>` or `--all-instances`.');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return options;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function summarizeResults(results) {
|
|
444
|
+
const successes = results.filter((result) => result.ok);
|
|
445
|
+
const failures = results.filter((result) => !result.ok);
|
|
446
|
+
return {
|
|
447
|
+
total: results.length,
|
|
448
|
+
successCount: successes.length,
|
|
449
|
+
failureCount: failures.length,
|
|
450
|
+
successes,
|
|
451
|
+
failures,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
module.exports = {
|
|
456
|
+
DEFAULT_CC_MIRROR_ROOT,
|
|
457
|
+
NPX_BIN,
|
|
458
|
+
buildSkillsAddNpxArgs,
|
|
459
|
+
discoverInstances,
|
|
460
|
+
discoverInstancesFromFilesystem,
|
|
461
|
+
expandHome,
|
|
462
|
+
formatCommand,
|
|
463
|
+
hasListFlag,
|
|
464
|
+
normalizeSkillsAddArgs,
|
|
465
|
+
parseAddArgs,
|
|
466
|
+
parseInstancesArgs,
|
|
467
|
+
parseLooseJsonArray,
|
|
468
|
+
runCommand,
|
|
469
|
+
selectInstances,
|
|
470
|
+
summarizeResults,
|
|
471
|
+
unique,
|
|
472
|
+
};
|