byteplan-cli 1.3.1 → 1.3.3

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/README.md CHANGED
@@ -43,16 +43,46 @@ byteplan model columns <modelCode>
43
43
  byteplan data query <modelCode>
44
44
  ```
45
45
 
46
- ### Dimension & LOV Commands
46
+ ### Skills Commands
47
+
48
+ BytePlan CLI 提供了一系列 Skills,可以安装到多个 AI CLI 平台。
49
+
50
+ **命令列表**:
51
+
52
+ ```bash
53
+ # 查看可用的 Skills
54
+ byteplan skills list
55
+
56
+ # 安装所有 Skills
57
+ byteplan skills install -p claude-code
58
+
59
+ # 查看已安装的 Skills
60
+ byteplan skills installed -p claude-code
61
+ ```
62
+
63
+ **支持的平台**:
64
+
65
+ | Platform | 目录 |
66
+ |----------|------|
67
+ | `claude-code` (alias: `claude`) | `~/.claude/commands` |
68
+ | `codex` | `~/.codex` |
69
+ | `openclaw` | `~/.openclaw/skills` |
70
+
71
+ **安装示例**:
47
72
 
48
73
  ```bash
49
- # Get dimension values
50
- byteplan dim <dimCode>
74
+ # 安装到单个平台
75
+ byteplan skills install -p claude-code
76
+
77
+ # 安装到多个平台
78
+ byteplan skills install -p claude-code,codex,openclaw
51
79
 
52
- # Get LOV values
53
- byteplan lov <lovCode>
80
+ # 使用自定义目录
81
+ byteplan skills install -d ~/.claude/commands
54
82
  ```
55
83
 
84
+ **Windows 用户**:`~` 路径会自动展开,也可使用 `%USERPROFILE%` 环境变量。
85
+
56
86
  ## Options
57
87
 
58
88
  - `-e, --env <environment>` - Environment (uat, prod), default: uat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byteplan-cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "BytePlan CLI - Command line tool for BytePlan API",
5
5
  "keywords": [
6
6
  "byteplan",
@@ -12,18 +12,73 @@ import { createRequire } from 'module';
12
12
 
13
13
  const require = createRequire(import.meta.url);
14
14
 
15
+ // 展开 ~ 路径,支持跨平台
16
+ function expandHomeDir(dirPath) {
17
+ if (!dirPath) return dirPath;
18
+
19
+ // 处理 ~ 开头的路径
20
+ if (dirPath.startsWith('~')) {
21
+ return path.join(homedir(), dirPath.slice(1));
22
+ }
23
+
24
+ // Windows: 处理 %USERPROFILE% 环境变量
25
+ if (dirPath.includes('%USERPROFILE%')) {
26
+ return dirPath.replace('%USERPROFILE%', homedir());
27
+ }
28
+
29
+ return dirPath;
30
+ }
31
+
32
+ // 平台默认目录映射
33
+ const PLATFORM_DIRS = {
34
+ 'claude-code': path.join(homedir(), '.claude', 'commands'),
35
+ 'claude': path.join(homedir(), '.claude', 'commands'), // alias
36
+ 'codex': path.join(homedir(), '.codex'),
37
+ 'openclaw': path.join(homedir(), '.openclaw', 'skills'),
38
+ };
39
+
15
40
  // Skills 源目录(在 npm 包中)
16
41
  const getSkillsSourceDir = () => {
17
- // 获取当前模块所在目录,然后向上找到项目根目录
18
42
  const currentDir = path.dirname(new URL(import.meta.url).pathname);
19
- // 从 src/commands/skills.js -> src -> 项目根目录 -> skills
20
43
  return path.join(currentDir, '..', '..', 'skills');
21
44
  };
22
45
 
23
- // Skills 默认目标目录(仅用于提示)
24
- const getDefaultSkillsTargetDir = () => {
25
- return path.join(homedir(), '.claude', 'skills');
26
- };
46
+ // 解析平台和目录参数
47
+ function resolveTargetDirs(platformArg, dirArg) {
48
+ const targets = [];
49
+ const expandedDirArg = expandHomeDir(dirArg);
50
+
51
+ if (platformArg) {
52
+ const platforms = platformArg.split(',').map(p => p.trim());
53
+ for (const platform of platforms) {
54
+ const normalizedName = platform.toLowerCase();
55
+ const defaultDir = PLATFORM_DIRS[normalizedName];
56
+ if (!defaultDir) {
57
+ return {
58
+ error: true,
59
+ message: `Unknown platform: ${platform}`,
60
+ available: Object.keys(PLATFORM_DIRS).filter(k => !k.includes('-')),
61
+ };
62
+ }
63
+ targets.push({
64
+ platform: normalizedName,
65
+ targetDir: expandedDirArg || defaultDir,
66
+ });
67
+ }
68
+ } else if (expandedDirArg) {
69
+ targets.push({
70
+ platform: 'custom',
71
+ targetDir: expandedDirArg,
72
+ });
73
+ } else {
74
+ return {
75
+ error: true,
76
+ message: 'Must specify at least one of --platform or --dir',
77
+ };
78
+ }
79
+
80
+ return { targets };
81
+ }
27
82
 
28
83
  function printJSON(data) {
29
84
  console.log(JSON.stringify(data, null, 2));
@@ -37,17 +92,11 @@ function printError(error) {
37
92
  // 获取 skill 信息
38
93
  function getSkillInfo(skillPath) {
39
94
  const skillMdPath = path.join(skillPath, 'SKILL.md');
40
- if (!existsSync(skillMdPath)) {
41
- return null;
42
- }
95
+ if (!existsSync(skillMdPath)) return null;
43
96
 
44
97
  const content = readFileSync(skillMdPath, 'utf-8');
45
-
46
- // 解析 frontmatter
47
98
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
48
- if (!frontmatterMatch) {
49
- return null;
50
- }
99
+ if (!frontmatterMatch) return null;
51
100
 
52
101
  const frontmatter = frontmatterMatch[1];
53
102
  const nameMatch = frontmatter.match(/name:\s*(.+)/);
@@ -56,220 +105,183 @@ function getSkillInfo(skillPath) {
56
105
  return {
57
106
  name: nameMatch ? nameMatch[1].trim() : path.basename(skillPath),
58
107
  description: descMatch ? descMatch[1].trim() : '',
59
- path: skillPath,
60
108
  };
61
109
  }
62
110
 
63
111
  // 获取所有可用 skills
64
112
  function getAvailableSkills() {
65
113
  const sourceDir = getSkillsSourceDir();
66
- if (!existsSync(sourceDir)) {
67
- return [];
68
- }
114
+ if (!existsSync(sourceDir)) return [];
69
115
 
70
116
  const skills = [];
71
117
  const dirs = readdirSync(sourceDir, { withFileTypes: true });
72
-
73
118
  for (const dir of dirs) {
74
119
  if (dir.isDirectory()) {
75
- const skillPath = path.join(sourceDir, dir.name);
76
- const info = getSkillInfo(skillPath);
77
- if (info) {
78
- skills.push(info);
79
- }
120
+ const info = getSkillInfo(path.join(sourceDir, dir.name));
121
+ if (info) skills.push(info);
80
122
  }
81
123
  }
82
-
83
124
  return skills;
84
125
  }
85
126
 
86
- // 获取已安装的 skills(支持自定义目录)
127
+ // 获取已安装的 skills
87
128
  function getInstalledSkills(targetDir) {
88
- if (!existsSync(targetDir)) {
89
- return [];
90
- }
129
+ if (!existsSync(targetDir)) return [];
91
130
 
92
131
  const skills = [];
93
132
  const dirs = readdirSync(targetDir, { withFileTypes: true });
94
-
95
133
  for (const dir of dirs) {
96
134
  if (dir.isDirectory() && dir.name.startsWith('byteplan-')) {
97
- const skillPath = path.join(targetDir, dir.name);
98
- const info = getSkillInfo(skillPath);
99
- if (info) {
100
- skills.push(info);
101
- }
135
+ const info = getSkillInfo(path.join(targetDir, dir.name));
136
+ if (info) skills.push(info);
102
137
  }
103
138
  }
104
-
105
139
  return skills;
106
140
  }
107
141
 
142
+ // 安装所有 skills
143
+ function installAllSkills(targetDirs) {
144
+ const sourceDir = getSkillsSourceDir();
145
+ const availableSkills = getAvailableSkills();
146
+ const toInstall = availableSkills.map(s => s.name);
147
+ const results = {};
148
+
149
+ for (const target of targetDirs) {
150
+ if (!existsSync(target.targetDir)) {
151
+ mkdirSync(target.targetDir, { recursive: true });
152
+ }
153
+
154
+ const installed = [];
155
+ for (const skillName of toInstall) {
156
+ cpSync(
157
+ path.join(sourceDir, skillName),
158
+ path.join(target.targetDir, skillName),
159
+ { recursive: true }
160
+ );
161
+ installed.push(skillName);
162
+ }
163
+
164
+ results[target.platform] = {
165
+ targetDir: target.targetDir,
166
+ installed: installed,
167
+ count: installed.length,
168
+ };
169
+ }
170
+
171
+ return { results, totalSkills: toInstall.length };
172
+ }
173
+
108
174
  // Skills 命令
109
175
  const skillsCmd = new Command('skills')
110
- .description('Manage BytePlan Claude Code skills')
111
- .requiredOption('-d, --dir <directory>', 'Target installation directory (required)')
176
+ .description('Manage BytePlan skills (install, list, check installed)')
177
+ .option('-p, --platform <platforms>', 'Target platform(s): claude-code, codex, openclaw (comma-separated)')
178
+ .option('-d, --dir <directory>', 'Custom target directory')
112
179
  .addHelpText('after', `
113
180
  Commands:
114
- skills list List all available skills
115
- skills installed List installed skills (requires -d)
116
- skills install [names] Install skills to target directory (requires -d)
117
- skills install-all Install all BytePlan skills (requires -d)
181
+ skills install Install all BytePlan skills
182
+ skills list List all available skills
183
+ skills installed List installed skills
184
+
185
+ Platforms:
186
+ claude-code (claude) ~/.claude/commands
187
+ codex ~/.codex
188
+ openclaw ~/.openclaw/skills
118
189
 
119
190
  Examples:
120
- $ byteplan skills list
121
- $ byteplan skills installed -d ~/.claude/skills
122
- $ byteplan skills install byteplan-api byteplan-analysis -d ~/.claude/skills
123
- $ byteplan skills install-all -d ~/.claude/skills
124
- $ byteplan skills install-all -d /custom/path/skills
191
+ byteplan skills install -p claude-code
192
+ byteplan skills install -p claude-code,codex,openclaw
193
+ byteplan skills list
125
194
  `);
126
195
 
127
- // skills list 子命令
196
+ // skills install 子命令 - 安装全部
128
197
  skillsCmd
129
- .command('list')
130
- .description('List all available BytePlan skills')
198
+ .command('install')
199
+ .description('Install all BytePlan skills to target platform(s)')
200
+ .addHelpText('after', `
201
+ Options (from parent):
202
+ -p, --platform Target platform(s): claude-code, codex, openclaw
203
+ -d, --dir Custom target directory
204
+
205
+ Examples:
206
+ byteplan skills install -p claude-code
207
+ byteplan skills install -p claude-code,codex
208
+ byteplan skills install -d ~/.claude/commands
209
+ `)
131
210
  .action(() => {
132
211
  try {
133
- const skills = getAvailableSkills();
212
+ const parentOptions = skillsCmd.opts();
213
+ const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
214
+
215
+ if (resolved.error) {
216
+ printJSON(resolved);
217
+ process.exit(1);
218
+ }
219
+
220
+ const { results, totalSkills } = installAllSkills(resolved.targets);
134
221
 
135
222
  printJSON({
136
- skills: skills.map(s => ({
137
- name: s.name,
138
- description: s.description,
139
- })),
140
- total: skills.length,
141
- sourceDir: getSkillsSourceDir(),
223
+ success: true,
224
+ platforms: results,
225
+ totalPlatforms: resolved.targets.length,
226
+ totalSkills: totalSkills,
142
227
  });
143
228
  } catch (error) {
144
229
  printError(error);
145
230
  }
146
231
  });
147
232
 
148
- // skills installed 子命令
233
+ // skills list 子命令
149
234
  skillsCmd
150
- .command('installed')
151
- .description('List installed BytePlan skills in target directory')
235
+ .command('list')
236
+ .description('List all available BytePlan skills')
152
237
  .action(() => {
153
238
  try {
154
- // 获取父命令的 -d 选项(requiredOption 已确保必传)
155
- const parentOptions = skillsCmd.opts();
156
- const targetDir = parentOptions.dir;
157
- const skills = getInstalledSkills(targetDir);
158
-
239
+ const skills = getAvailableSkills();
159
240
  printJSON({
160
- skills: skills.map(s => ({
161
- name: s.name,
162
- description: s.description,
163
- })),
241
+ skills: skills.map(s => ({ name: s.name, description: s.description })),
164
242
  total: skills.length,
165
- targetDir: targetDir,
166
243
  });
167
244
  } catch (error) {
168
245
  printError(error);
169
246
  }
170
247
  });
171
248
 
172
- // skills install 子命令
249
+ // skills installed 子命令
173
250
  skillsCmd
174
- .command('install [names...]')
175
- .description('Install specified skills to target directory')
176
- .option('-a, --all', 'Install all BytePlan skills', false)
251
+ .command('installed')
252
+ .description('List installed BytePlan skills')
177
253
  .addHelpText('after', `
254
+ Options (from parent):
255
+ -p, --platform Target platform(s): claude-code, codex, openclaw
256
+ -d, --dir Custom target directory
257
+
178
258
  Examples:
179
- $ byteplan skills install byteplan-api byteplan-analysis -d ~/.claude/skills
180
- $ byteplan skills install --all -d ~/.claude/skills
181
- $ byteplan skills install byteplan-api -d /custom/path/skills
259
+ byteplan skills installed -p claude-code
260
+ byteplan skills installed -d ~/.claude/commands
182
261
  `)
183
- .action(async (names, options) => {
262
+ .action(() => {
184
263
  try {
185
- // 获取父命令的 -d 选项(requiredOption 已确保必传)
186
264
  const parentOptions = skillsCmd.opts();
187
- const targetDir = parentOptions.dir;
188
- const sourceDir = getSkillsSourceDir();
189
-
190
- // 确保目标目录存在
191
- if (!existsSync(targetDir)) {
192
- mkdirSync(targetDir, { recursive: true });
193
- }
194
-
195
- // 获取要安装的 skills
196
- let toInstall = names || [];
197
- if (options.all || toInstall.length === 0) {
198
- const available = getAvailableSkills();
199
- toInstall = available.map(s => s.name);
200
- }
201
-
202
- // 验证 skills 存在
203
- const availableSkills = getAvailableSkills();
204
- const availableNames = availableSkills.map(s => s.name);
265
+ const resolved = resolveTargetDirs(parentOptions.platform, parentOptions.dir);
205
266
 
206
- const invalid = toInstall.filter(n => !availableNames.includes(n));
207
- if (invalid.length > 0) {
208
- printJSON({
209
- error: true,
210
- message: `Unknown skills: ${invalid.join(', ')}`,
211
- available: availableNames,
212
- });
267
+ if (resolved.error) {
268
+ printJSON(resolved);
213
269
  process.exit(1);
214
270
  }
215
271
 
216
- // 安装 skills
217
- const installed = [];
218
- for (const skillName of toInstall) {
219
- const skillSourcePath = path.join(sourceDir, skillName);
220
- const skillTargetPath = path.join(targetDir, skillName);
221
-
222
- // 复制 skill 目录
223
- cpSync(skillSourcePath, skillTargetPath, { recursive: true });
224
- installed.push(skillName);
272
+ const results = {};
273
+ for (const target of resolved.targets) {
274
+ const skills = getInstalledSkills(target.targetDir);
275
+ results[target.platform] = {
276
+ targetDir: target.targetDir,
277
+ skills: skills.map(s => ({ name: s.name, description: s.description })),
278
+ count: skills.length,
279
+ };
225
280
  }
226
281
 
227
282
  printJSON({
228
- success: true,
229
- installed: installed,
230
- total: installed.length,
231
- targetDir: targetDir,
232
- });
233
- } catch (error) {
234
- printError(error);
235
- }
236
- });
237
-
238
- // skills install-all 子命令(快捷方式)
239
- skillsCmd
240
- .command('install-all')
241
- .description('Install all BytePlan skills to target directory')
242
- .action(async () => {
243
- try {
244
- // 获取父命令的 -d 选项(requiredOption 已确保必传)
245
- const parentOptions = skillsCmd.opts();
246
- const targetDir = parentOptions.dir;
247
- const sourceDir = getSkillsSourceDir();
248
-
249
- // 确保目标目录存在
250
- if (!existsSync(targetDir)) {
251
- mkdirSync(targetDir, { recursive: true });
252
- }
253
-
254
- // 获取所有 skills
255
- const availableSkills = getAvailableSkills();
256
-
257
- // 安装所有 skills
258
- const installed = [];
259
- for (const skill of availableSkills) {
260
- const skillSourcePath = path.join(sourceDir, skill.name);
261
- const skillTargetPath = path.join(targetDir, skill.name);
262
-
263
- // 复制 skill 目录
264
- cpSync(skillSourcePath, skillTargetPath, { recursive: true });
265
- installed.push(skill.name);
266
- }
267
-
268
- printJSON({
269
- success: true,
270
- installed: installed,
271
- total: installed.length,
272
- targetDir: targetDir,
283
+ platforms: results,
284
+ totalPlatforms: resolved.targets.length,
273
285
  });
274
286
  } catch (error) {
275
287
  printError(error);