@cli-skill/cli 0.0.1-beta.20260402075508 → 0.0.1-beta.20260402142556

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
@@ -2,13 +2,14 @@
2
2
 
3
3
  `@cli-skill/cli` 是 `cli-skill` 的平台命令行入口。
4
4
 
5
- 它不是某一个具体 skill 的运行时,而是整个 skill 生命周期的管理入口。
5
+ 它不是某一个具体 skill 的运行时,而是整个 skill 生命周期和本地注册表的管理入口。
6
6
 
7
7
  ## 主要职责
8
8
 
9
9
  - 创建新的 skill 项目
10
- - 提供统一的 skill 作用域命令入口
11
- - 构建 `skill/` 产物
10
+ - 维护本地 skill 注册表
11
+ - 提供当前目录命令和已注册 skill 执行入口
12
+ - 生成 `skill/` 产物
12
13
  - 安装和卸载已发布 skill
13
14
  - 挂载和取消挂载本地 skill
14
15
  - 发布本地 skill
@@ -21,34 +22,36 @@
21
22
  ```bash
22
23
  cli-skill create <skillName> --cli-name <cliName> [--template <templateName>]
23
24
  cli-skill list
25
+ cli-skill tools <skillName>
24
26
  cli-skill install <skillName> [--packageName <packageName>]
25
27
  cli-skill uninstall <packageName>
26
28
  cli-skill config get [keyPath]
27
29
  cli-skill config set <keyPath> <value>
30
+ cli-skill exec <skillName> <toolName> [rawInput]
28
31
  ```
29
32
 
30
- ### skill 作用域命令
33
+ ### 当前目录命令
31
34
 
32
35
  ```bash
33
- cli-skill <skillName> list
34
- cli-skill <skillName> run <toolName> [rawInput]
35
- cli-skill <skillName> config get [keyPath]
36
- cli-skill <skillName> config set <keyPath> <value>
37
- cli-skill <skillName> config unset <keyPath>
38
- cli-skill <skillName> mount [targetPath]
39
- cli-skill <skillName> unmount [targetPath]
40
- cli-skill <skillName> build
41
- cli-skill <skillName> publish [--dry-run] [--tag <tag>]
36
+ cli-skill tools
37
+ cli-skill run <toolName> [rawInput]
38
+ cli-skill config get [keyPath]
39
+ cli-skill config set <keyPath> <value>
40
+ cli-skill config unset <keyPath>
41
+ cli-skill build
42
+ cli-skill mount [targetPath]
43
+ cli-skill unmount [targetPath]
44
+ cli-skill publish [--dry-run] [--tag <tag>]
42
45
  ```
43
46
 
44
47
  ## 典型工作流
45
48
 
46
49
  ```bash
47
50
  cli-skill create my-skill --cli-name my-skill
48
- cd ~/.cli-skill/skills/my-skill
51
+ cd ./my-skill
49
52
  bun install
50
- cli-skill my-skill build
51
- cli-skill my-skill mount
53
+ cli-skill build
54
+ cli-skill mount
52
55
  ```
53
56
 
54
57
  ## skill 项目结构
@@ -68,7 +71,14 @@ cli-skill my-skill mount
68
71
  - `src/skill/*`
69
72
  - 文档模板源目录
70
73
 
71
- 执行 `build` 后会生成:
74
+ 模板生成出来的 skill 包会同时依赖:
75
+
76
+ - `@cli-skill/core`
77
+ - 运行 skill
78
+ - `@cli-skill/cli`
79
+ - 在项目目录内调用 `cli-skill build`、`cli-skill mount` 等命令
80
+
81
+ 执行 `cli-skill build` 后会生成:
72
82
 
73
83
  - `skill/SKILL.md`
74
84
  - `skill/agents/openai.yaml`
@@ -80,7 +90,7 @@ cli-skill my-skill mount
80
90
 
81
91
  ## build 输入
82
92
 
83
- `cli-skill <skillName> build` 会读取:
93
+ `cli-skill build` 会读取:
84
94
 
85
95
  - `src/index.ts`
86
96
  - `src/skill/`
@@ -97,13 +107,17 @@ cli-skill my-skill mount
97
107
 
98
108
  ## 安装模型
99
109
 
100
- 本地 skill 默认放在:
110
+ 本地创建的 skill 默认放在当前目录:
111
+
112
+ - `./<skill-name>`
113
+
114
+ 已安装 skill 默认放在:
101
115
 
102
116
  - `~/.cli-skill/skills`
103
117
 
104
- 托管安装目录默认是:
118
+ 本地注册表默认放在:
105
119
 
106
- - `~/.cli-skill/installed`
120
+ - `~/.cli-skill/registry.json`
107
121
 
108
122
  默认 agent 目录是:
109
123
 
@@ -121,3 +135,26 @@ cli-skill my-skill mount
121
135
  ```bash
122
136
  cli-skill install fx --packageName @scope/cli-skill-fx
123
137
  ```
138
+
139
+ ## skill bin 的行为
140
+
141
+ 生成出来的 skill 自带一个很薄的 bin。
142
+
143
+ 例如:
144
+
145
+ ```bash
146
+ my-skill list
147
+ my-skill open_page '{"url":"https://example.com"}'
148
+ ```
149
+
150
+ 它适合做:
151
+
152
+ - 查看这个 skill 的 tool 列表
153
+ - 直接执行某个 tool
154
+
155
+ 它不承担项目管理命令。以下操作仍然只通过 `cli-skill` 使用:
156
+
157
+ - `build`
158
+ - `mount`
159
+ - `unmount`
160
+ - `publish`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cli-skill/cli",
3
- "version": "0.0.1-beta.20260402075508",
3
+ "version": "0.0.1-beta.20260402142556",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -14,13 +14,12 @@
14
14
  }
15
15
  },
16
16
  "scripts": {
17
- "build": "tsc -p tsconfig.json",
18
17
  "test": "tsc -p tsconfig.test.json && bun test ./test"
19
18
  },
20
19
  "dependencies": {
21
- "@cli-skill/core": "^0.0.1-beta.20260402075508",
20
+ "@cli-skill/core": "^0.0.1-beta.20260402142556",
22
21
  "cac": "^6.7.14",
23
22
  "lodash": "^4.17.21",
24
- "zod": "^3.25.76"
23
+ "zod": "^4.3.6"
25
24
  }
26
25
  }
package/skill/SKILL.md CHANGED
@@ -23,18 +23,32 @@ description: 当任务涉及 cli skill 的创建、挂载、安装、发布或
23
23
 
24
24
  ## 工作模型
25
25
 
26
- 处理 cli skill 时,默认按两层命令来理解:
26
+ 处理 cli skill 时,默认按三类命令来理解:
27
27
 
28
28
  - 平台命令
29
29
  - `cli-skill create`
30
30
  - `cli-skill list`
31
+ - `cli-skill tools <skill-name>`
31
32
  - `cli-skill install`
32
33
  - `cli-skill uninstall`
33
34
  - `cli-skill config ...`
34
- - skill 作用域命令
35
- - `cli-skill <skill-name> ...`
35
+ - 当前目录命令
36
+ - `cli-skill tools`
37
+ - `cli-skill run`
38
+ - `cli-skill build`
39
+ - `cli-skill mount`
40
+ - `cli-skill publish`
41
+ - 已注册 skill 执行命令
42
+ - `cli-skill exec <skill-name> ...`
36
43
 
37
- skill 自己的 bin 只是一个转发入口,仍然会落到 `cli-skill <skill-name> ...`。
44
+ skill 自己的 bin 只是一个转发入口:
45
+
46
+ - `skill-name list`
47
+ - 查看这个 skill 的 tools
48
+ - `skill-name <tool>`
49
+ - 执行这个 skill 的 tool
50
+
51
+ 项目级命令仍然通过 `cli-skill` 使用,不通过 skill bin 使用。
38
52
 
39
53
  ## 默认流程
40
54
 
@@ -42,9 +56,10 @@ skill 自己的 bin 只是一个转发入口,仍然会落到 `cli-skill <skill
42
56
 
43
57
  ```bash
44
58
  cli-skill create <skill-name> --cli-name <cli-name>
59
+ cd ./<skill-name>
45
60
  bun install
46
- cli-skill <skill-name> build
47
- cli-skill <skill-name> mount
61
+ cli-skill build
62
+ cli-skill mount
48
63
  ```
49
64
 
50
65
  如果没有提供 `cli-name`,默认令它等于 `skill-name`。
@@ -66,13 +81,7 @@ cli-skill <skill-name> mount
66
81
  - `src/skill/*`
67
82
  - 文档模板源目录
68
83
 
69
- 执行:
70
-
71
- ```bash
72
- cli-skill <skill-name> build
73
- ```
74
-
75
- 会生成:
84
+ 执行 `cli-skill build` 会生成:
76
85
 
77
86
  - `skill/SKILL.md`
78
87
  - `skill/agents/openai.yaml`
@@ -83,17 +92,19 @@ cli-skill <skill-name> build
83
92
  | --- | --- |
84
93
  | 创建 skill | `cli-skill create <skill-name> --cli-name <cli-name> [--template <templateName>]` |
85
94
  | 查看 skill 列表 | `cli-skill list` |
95
+ | 查看已注册 skill 的 tool 列表 | `cli-skill tools <skill-name>` |
86
96
  | 安装已发布 skill | `cli-skill install <skill-name>` |
87
97
  | 卸载已发布 skill | `cli-skill uninstall <package-name>` |
88
- | 查看 tool 列表 | `cli-skill <skill-name> list` |
89
- | 运行 tool | `cli-skill <skill-name> run <tool-name> [rawInput]` |
90
- | 读取 skill 配置 | `cli-skill <skill-name> config get [keyPath]` |
91
- | 写入 skill 配置 | `cli-skill <skill-name> config set <keyPath> <value>` |
92
- | 删除 skill 配置 | `cli-skill <skill-name> config unset <keyPath>` |
93
- | 挂载 skill | `cli-skill <skill-name> mount [targetPath]` |
94
- | 取消挂载 skill | `cli-skill <skill-name> unmount [targetPath]` |
95
- | 构建 skill 产物 | `cli-skill <skill-name> build` |
96
- | 发布本地 skill | `cli-skill <skill-name> publish [--dry-run] [--tag <tag>]` |
98
+ | 查看 tool 列表 | `cli-skill tools` |
99
+ | 运行当前目录 tool | `cli-skill run <tool-name> [rawInput]` |
100
+ | 运行已注册 skill tool | `cli-skill exec <skill-name> <tool-name> [rawInput]` |
101
+ | 读取当前 skill 配置 | `cli-skill config get [keyPath]` |
102
+ | 写入当前 skill 配置 | `cli-skill config set <keyPath> <value>` |
103
+ | 删除当前 skill 配置 | `cli-skill config unset <keyPath>` |
104
+ | 挂载当前 skill | `cli-skill mount [targetPath]` |
105
+ | 取消挂载当前 skill | `cli-skill unmount [targetPath]` |
106
+ | 构建 skill 产物 | `cli-skill build` |
107
+ | 发布当前 skill | `cli-skill publish [--dry-run] [--tag <tag>]` |
97
108
  | 读取全局配置 | `cli-skill config get [keyPath]` |
98
109
  | 写入全局配置 | `cli-skill config set <keyPath> <value>` |
99
110
 
@@ -106,14 +117,20 @@ cli-skill <skill-name> build
106
117
  - 注册根目录 `skill/` 到 agent 目录
107
118
  - `mount` 不应隐式执行 `install`。
108
119
  - `install` / `uninstall` 面向已发布 skill。
109
- - `publish` 只针对本地 skill。
120
+ - `publish` 只针对当前目录的本地 skill。
121
+ - skill bin 只负责:
122
+ - `list`
123
+ - 直接执行 tool
124
+ - `config get/set/unset`
110
125
 
111
126
  默认目录:
112
127
 
113
- - 本地 skill:
128
+ - 当前目录创建的 skill:
129
+ - `./<skill-name>`
130
+ - 已安装 skill:
114
131
  - `~/.cli-skill/skills/<skill-name>`
115
- - 托管安装目录:
116
- - `~/.cli-skill/installed`
132
+ - 本地注册表:
133
+ - `~/.cli-skill/registry.json`
117
134
  - agent 默认目录:
118
135
  - `~/.agents/skills/<skill-name>`
119
136
 
@@ -132,8 +149,9 @@ skill 作用域配置路径:
132
149
  ```bash
133
150
  cli-skill config get
134
151
  cli-skill config set skillsRoot ~/.cli-skill/skills
135
- cli-skill <skill-name> config get
136
- cli-skill <skill-name> config set baseUrl https://example.com
152
+ cd ./<skill-name>
153
+ cli-skill config get
154
+ cli-skill config set baseUrl https://example.com
137
155
  ```
138
156
 
139
157
  ## 文档规则
@@ -143,7 +161,7 @@ cli-skill <skill-name> config set baseUrl https://example.com
143
161
  - tool 或配置变更后,应重新执行:
144
162
 
145
163
  ```bash
146
- cli-skill <skill-name> build
164
+ cli-skill build
147
165
  ```
148
166
 
149
167
  ## 不要做的事
@@ -151,4 +169,5 @@ cli-skill <skill-name> build
151
169
  - 不要把 `create` 当成“已经可执行”
152
170
  - 不要手动改 `~/.agents/skills`
153
171
  - 不要把 skill bin 当成独立平台
172
+ - 不要期待 skill bin 提供 `build` / `mount` / `publish`
154
173
  - 不要在修改已有 skill 时重新 `create`
@@ -14,11 +14,11 @@
14
14
  - 真正执行动作的最小单元
15
15
  - `skill`
16
16
  - 对 tools 的组织与描述
17
- - `build` 产物
17
+ - `skill/` 产物
18
18
  - 给 agent 使用的 `skill/` 目录
19
19
 
20
20
  源码不是直接写给 agent 的。
21
- 源码先定义 skill 和 tools,然后再由 `build` 生成 agent 真正消费的产物。
21
+ 源码先定义 skill 和 tools,然后再由 `cli-skill build` 生成 agent 真正消费的产物。
22
22
 
23
23
  ## skill 项目源码结构
24
24
 
@@ -40,7 +40,7 @@
40
40
  执行:
41
41
 
42
42
  ```bash
43
- cli-skill <skill-name> build
43
+ cli-skill build
44
44
  ```
45
45
 
46
46
  之后,根目录会生成:
@@ -67,6 +67,8 @@ cli-skill <skill-name> build
67
67
 
68
68
  也就是说,skill 只做“组织”和“说明”,不直接承担运行时能力注入。
69
69
 
70
+ 命令解析也不在 `core`。`core` 只负责类型、runtime、tool 执行。
71
+
70
72
  ## `defineTool` 负责什么
71
73
 
72
74
  `defineTool` 才是运行能力的核心。
@@ -179,11 +181,11 @@ export default defineSkill({
179
181
 
180
182
  - `tool` 负责声明自己要 `browserPlugin`
181
183
  - `skill` 只负责把它组织起来
182
- - `build` 再负责生成 `skill/` 产物
184
+ - `cli-skill build` 再负责生成 `skill/` 产物
183
185
 
184
186
  ## 文档模板怎么写
185
187
 
186
- `src/skill/*` 下的 `.md/.yaml/.yml` 文件会在 build 时做变量替换。
188
+ `src/skill/*` 下的 `.md/.yaml/.yml` 文件会在 `cli-skill build` 时做变量替换。
187
189
 
188
190
  当前常用变量有:
189
191
 
@@ -211,13 +213,19 @@ export default defineSkill({
211
213
 
212
214
  这也是为什么生成出来的 skill 模板会把 `zod` 放在自己的 `dependencies` 里。
213
215
 
216
+ 生成出来的 skill 模板还会把 `@cli-skill/cli` 放进 `devDependencies`,用于在 skill 项目目录里直接执行:
217
+
218
+ - `cli-skill build`
219
+ - `cli-skill mount`
220
+ - `cli-skill publish`
221
+
214
222
  ## 什么时候继续往下看
215
223
 
216
224
  如果你需要的是:
217
225
 
218
226
  - 怎么创建 skill
219
227
  - 怎么 mount / install / publish
220
- - 怎么 build 产物
228
+ - 怎么生成 `skill/` 产物
221
229
 
222
230
  优先回到主 skill 文档:
223
231
 
package/src/app.ts CHANGED
@@ -3,7 +3,7 @@ import { registerConfigCommand } from "./commands/config";
3
3
  import { registerCreateCommand } from "./commands/create";
4
4
  import { registerInstallCommand } from "./commands/install";
5
5
  import { registerListCommand } from "./commands/list";
6
- import { registerSkillCommand } from "./commands/skill";
6
+ import { registerSkillCommands } from "./commands/skill";
7
7
  import { registerUninstallCommand } from "./commands/uninstall";
8
8
 
9
9
  export function createApp(version: string) {
@@ -14,7 +14,7 @@ export function createApp(version: string) {
14
14
  registerListCommand(cli);
15
15
  registerInstallCommand(cli);
16
16
  registerUninstallCommand(cli);
17
- registerSkillCommand(cli);
17
+ registerSkillCommands(cli);
18
18
 
19
19
  cli.help();
20
20
  cli.version(version);
package/src/build.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { type ZodRawShape, type ZodTypeAny } from "zod";
3
+ import { type ZodTypeAny } from "zod";
4
4
  import type { SkillDefinition } from "@cli-skill/core";
5
5
 
6
6
  interface FieldInfo {
@@ -15,27 +15,39 @@ interface DocRow {
15
15
  notes: string;
16
16
  }
17
17
 
18
+ type ShapeLike = Record<string, ZodTypeAny>;
19
+
20
+ function getSchemaDef(schema: ZodTypeAny): Record<string, unknown> | undefined {
21
+ return (schema as ZodTypeAny & { _def?: Record<string, unknown>; def?: Record<string, unknown> })._def
22
+ ?? (schema as ZodTypeAny & { def?: Record<string, unknown> }).def;
23
+ }
24
+
18
25
  function unwrapField(schema: ZodTypeAny): FieldInfo {
19
26
  let current = schema;
20
27
  let optional = false;
21
28
  let defaultValue: unknown;
22
29
 
23
30
  while (true) {
24
- if (current?._def?.typeName === "ZodOptional") {
31
+ const def = getSchemaDef(current);
32
+ const typeName = def?.typeName;
33
+ const type = def?.type;
34
+
35
+ if ((typeName === "ZodOptional" || type === "optional") && def?.innerType) {
25
36
  optional = true;
26
- current = current._def.innerType;
37
+ current = def.innerType as ZodTypeAny;
27
38
  continue;
28
39
  }
29
40
 
30
- if (current?._def?.typeName === "ZodDefault") {
41
+ if ((typeName === "ZodDefault" || type === "default") && def?.innerType) {
31
42
  optional = true;
32
- defaultValue = current._def.defaultValue();
33
- current = current._def.innerType;
43
+ const rawDefault = def.defaultValue;
44
+ defaultValue = typeof rawDefault === "function" ? rawDefault() : rawDefault;
45
+ current = def.innerType as ZodTypeAny;
34
46
  continue;
35
47
  }
36
48
 
37
- if (current?._def?.typeName === "ZodNullable") {
38
- current = current._def.innerType;
49
+ if ((typeName === "ZodNullable" || type === "nullable") && def?.innerType) {
50
+ current = def.innerType as ZodTypeAny;
39
51
  continue;
40
52
  }
41
53
 
@@ -46,25 +58,30 @@ function unwrapField(schema: ZodTypeAny): FieldInfo {
46
58
  }
47
59
 
48
60
  function getTypeName(schema: ZodTypeAny): string | undefined {
49
- return schema?._def?.typeName;
61
+ const def = getSchemaDef(schema);
62
+ return (def?.typeName as string | undefined) ?? (def?.type as string | undefined);
50
63
  }
51
64
 
52
- function getObjectShape(schema: ZodTypeAny): ZodRawShape | null {
53
- if (getTypeName(schema) !== "ZodObject") {
65
+ function getObjectShape(schema: ZodTypeAny): ShapeLike | null {
66
+ const typeName = getTypeName(schema);
67
+ if (typeName !== "ZodObject" && typeName !== "object") {
54
68
  return null;
55
69
  }
56
70
 
57
71
  const objectSchema = schema as ZodTypeAny & {
58
- _def: { shape?: (() => ZodRawShape) | ZodRawShape };
59
- shape?: ZodRawShape;
72
+ _def?: { shape?: (() => ShapeLike) | ShapeLike };
73
+ def?: { shape?: (() => ShapeLike) | ShapeLike };
74
+ shape?: ShapeLike;
60
75
  };
76
+ const def = getSchemaDef(objectSchema);
77
+ const shapeValue = def?.shape as (() => ShapeLike) | ShapeLike | undefined;
61
78
 
62
- if (typeof objectSchema._def.shape === "function") {
63
- return objectSchema._def.shape();
79
+ if (typeof shapeValue === "function") {
80
+ return shapeValue();
64
81
  }
65
82
 
66
- if (objectSchema._def.shape) {
67
- return objectSchema._def.shape;
83
+ if (shapeValue) {
84
+ return shapeValue;
68
85
  }
69
86
 
70
87
  return objectSchema.shape ?? null;
@@ -87,21 +104,32 @@ function describeType(schema: ZodTypeAny): string {
87
104
  return "boolean";
88
105
  }
89
106
 
90
- if (typeName === "ZodLiteral") {
91
- return JSON.stringify(base._def.value);
107
+ const def = getSchemaDef(base);
108
+
109
+ if (typeName === "ZodLiteral" || typeName === "literal") {
110
+ const values = def?.values;
111
+ if (Array.isArray(values) && values.length === 1) {
112
+ return JSON.stringify(values[0]);
113
+ }
114
+
115
+ return JSON.stringify(def?.value);
92
116
  }
93
117
 
94
- if (typeName === "ZodEnum") {
95
- return (base as ZodTypeAny & { options: string[] }).options
96
- .map((item: string) => JSON.stringify(item))
118
+ if (typeName === "ZodEnum" || typeName === "enum") {
119
+ const entries = (def?.entries as Record<string, string> | undefined)
120
+ ?? ((base as ZodTypeAny & { options?: string[] }).options
121
+ ? Object.fromEntries(((base as ZodTypeAny & { options: string[] }).options).map((item) => [item, item]))
122
+ : undefined);
123
+ return Object.values(entries ?? {})
124
+ .map((item) => JSON.stringify(item))
97
125
  .join(" | ");
98
126
  }
99
127
 
100
- if (typeName === "ZodArray") {
101
- return `array<${describeType(base._def.type)}>`;
128
+ if (typeName === "ZodArray" || typeName === "array") {
129
+ return `array<${describeType((def?.type ?? def?.element) as ZodTypeAny)}>`;
102
130
  }
103
131
 
104
- if (typeName === "ZodObject") {
132
+ if (typeName === "ZodObject" || typeName === "object") {
105
133
  return "object";
106
134
  }
107
135
 
@@ -123,7 +151,7 @@ function describeNotes(schema: ZodTypeAny): string {
123
151
  return notes.join("; ");
124
152
  }
125
153
 
126
- function collectShapeRows(shape: ZodRawShape, prefix = ""): DocRow[] {
154
+ function collectShapeRows(shape: ShapeLike, prefix = ""): DocRow[] {
127
155
  const rows: DocRow[] = [];
128
156
 
129
157
  for (const [key, schema] of Object.entries(shape)) {
@@ -180,7 +208,7 @@ function renderTable(headers: string[], rows: string[][]): string {
180
208
  }
181
209
 
182
210
  function renderConfigSection(skill: SkillDefinition): string {
183
- const rows = collectShapeRows(skill.config).map((row) => [row.path, row.type, row.notes || ""]);
211
+ const rows = collectShapeRows(skill.config as ShapeLike).map((row) => [row.path, row.type, row.notes || ""]);
184
212
  return renderTable(["字段", "类型", "说明"], rows.length > 0 ? rows : [["-", "-", "-"]]);
185
213
  }
186
214
 
package/src/bun.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, spawn } from "node:child_process";
2
2
  import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { promisify } from "node:util";
5
6
 
@@ -19,6 +20,33 @@ export async function runBun(
19
20
  });
20
21
  }
21
22
 
23
+ export async function runBunStreaming(
24
+ args: string[],
25
+ cwd?: string,
26
+ envOverrides?: Record<string, string | undefined>,
27
+ ): Promise<void> {
28
+ await new Promise<void>((resolve, reject) => {
29
+ const child = spawn("bun", args, {
30
+ cwd,
31
+ env: {
32
+ ...process.env,
33
+ ...envOverrides,
34
+ },
35
+ stdio: "inherit",
36
+ });
37
+
38
+ child.on("error", reject);
39
+ child.on("exit", (code, signal) => {
40
+ if (code === 0) {
41
+ resolve();
42
+ return;
43
+ }
44
+
45
+ reject(new Error(`bun ${args.join(" ")} failed with code ${code ?? "null"}${signal ? ` signal ${signal}` : ""}`));
46
+ });
47
+ });
48
+ }
49
+
22
50
  export async function runBunx(args: string[], cwd?: string): Promise<string> {
23
51
  const { stdout } = await execFileAsync("bunx", args, {
24
52
  cwd,
@@ -36,7 +64,9 @@ export async function runBunAndCapture(args: string[], cwd?: string): Promise<st
36
64
  }
37
65
 
38
66
  export async function getBunGlobalBinDir(): Promise<string> {
39
- return runBunAndCapture(["pm", "bin", "-g"]);
67
+ const home = process.env.HOME || os.homedir();
68
+ const bunInstall = process.env.BUN_INSTALL || path.join(home, ".bun");
69
+ return path.join(bunInstall, "bin");
40
70
  }
41
71
 
42
72
  export async function installPackageToDirectory(packageSpec: string, installDir: string): Promise<void> {
@@ -6,7 +6,9 @@ import {
6
6
  parseConfigCliValue,
7
7
  saveBrowserSkillCliConfig,
8
8
  setConfigValue,
9
+ unsetConfigValue,
9
10
  } from "../config";
11
+ import { getCurrentSkillProject } from "../registry";
10
12
 
11
13
  function printConfigValue(value: unknown): void {
12
14
  if (typeof value === "string") {
@@ -25,13 +27,24 @@ function printConfigValue(value: unknown): void {
25
27
  export function registerConfigCommand(cli: CAC): void {
26
28
  cli
27
29
  .command("config [...args]", "Manage cli-skill config")
28
- .usage("config get [keyPath]\n cli-skill config set <keyPath> <value>")
30
+ .usage("config get [keyPath]\n cli-skill config set <keyPath> <value>\n cli-skill config unset <keyPath>")
29
31
  .action(async (args: string[] = []) => {
30
32
  const [subcommand, keyPath, rawValue] = args;
33
+ let currentSkillName: string | null = null;
34
+
35
+ try {
36
+ currentSkillName = (await getCurrentSkillProject()).skillName;
37
+ } catch {}
38
+
39
+ const scopedKeyPath = currentSkillName && keyPath
40
+ ? `skillConfig.${currentSkillName}.${keyPath}`
41
+ : currentSkillName
42
+ ? `skillConfig.${currentSkillName}`
43
+ : keyPath;
31
44
 
32
45
  if (subcommand === "get") {
33
46
  const currentConfig = await loadBrowserSkillCliConfig();
34
- const value = getConfigValue(currentConfig, keyPath);
47
+ const value = getConfigValue(currentConfig, scopedKeyPath);
35
48
  printConfigValue(value);
36
49
  return;
37
50
  }
@@ -42,12 +55,28 @@ export function registerConfigCommand(cli: CAC): void {
42
55
  }
43
56
 
44
57
  const currentConfig = await loadBrowserSkillCliConfig();
45
- const nextConfig = setConfigValue(currentConfig, keyPath, parseConfigCliValue(rawValue));
58
+ const nextConfig = setConfigValue(
59
+ currentConfig,
60
+ scopedKeyPath!,
61
+ parseConfigCliValue(rawValue),
62
+ );
63
+ await saveBrowserSkillCliConfig(nextConfig);
64
+ console.log(getBrowserSkillConfigPath());
65
+ return;
66
+ }
67
+
68
+ if (subcommand === "unset") {
69
+ if (!keyPath) {
70
+ throw new Error("Usage: cli-skill config unset <keyPath>");
71
+ }
72
+
73
+ const currentConfig = await loadBrowserSkillCliConfig();
74
+ const nextConfig = unsetConfigValue(currentConfig, scopedKeyPath!);
46
75
  await saveBrowserSkillCliConfig(nextConfig);
47
76
  console.log(getBrowserSkillConfigPath());
48
77
  return;
49
78
  }
50
79
 
51
- throw new Error("Usage: cli-skill config get [keyPath] | cli-skill config set <keyPath> <value>");
80
+ throw new Error("Usage: cli-skill config get [keyPath] | cli-skill config set <keyPath> <value> | cli-skill config unset <keyPath>");
52
81
  });
53
82
  }
@@ -1,6 +1,7 @@
1
1
  import type { CAC } from "cac";
2
2
  import { DEFAULT_TEMPLATE_NAME } from "../constants";
3
3
  import { createSkillProject } from "../project";
4
+ import { registerLocalSkillProject } from "../registry";
4
5
 
5
6
  function resolveTemplateName(templateOption?: string): string {
6
7
  return templateOption ?? DEFAULT_TEMPLATE_NAME;
@@ -20,6 +21,7 @@ export function registerCreateCommand(cli: CAC): void {
20
21
  options.cliName ?? skillName,
21
22
  resolveTemplateName(options.template),
22
23
  );
24
+ await registerLocalSkillProject(targetDir);
23
25
  console.log(targetDir);
24
26
  });
25
27
  }