codemini-cli 0.4.0 → 0.4.2
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/OPERATIONS.md +4 -2
- package/README.md +89 -11
- package/deployment.md +14 -7
- package/package.json +1 -2
- package/src/cli.js +1 -1
- package/src/commands/skill.js +145 -53
- package/src/core/agent-loop.js +18 -311
- package/src/core/chat-runtime.js +389 -53
- package/src/core/command-loader.js +12 -5
- package/src/core/config-store.js +2 -0
- package/src/core/context-compact.js +34 -9
- package/src/core/default-system-prompt.js +5 -5
- package/src/core/dream-audit.js +12 -0
- package/src/core/dream-consolidate.js +131 -59
- package/src/core/dream-evaluator.js +86 -0
- package/src/core/fff-adapter.js +1 -1
- package/src/core/memory-store.js +145 -10
- package/src/core/provider/openai-compatible.js +40 -5
- package/src/core/reflect-skill.js +178 -0
- package/src/core/shell-profile.js +8 -8
- package/src/core/tool-args.js +181 -0
- package/src/core/tool-result-store.js +206 -0
- package/src/core/tools.js +144 -190
- package/src/tui/chat-app.js +270 -28
- package/src/tui/tool-activity/presenters/misc.js +14 -0
- package/src/core/provider/anthropic.sdk-backup.js +0 -439
- package/src/core/provider/openai-compatible.sdk-backup.js +0 -412
package/OPERATIONS.md
CHANGED
|
@@ -202,8 +202,10 @@ or revise/discard before execution:
|
|
|
202
202
|
```powershell
|
|
203
203
|
codemini skill list
|
|
204
204
|
codemini skill inspect superpowers-lite
|
|
205
|
-
codemini skill
|
|
206
|
-
codemini skill
|
|
205
|
+
codemini skill install .\my-skill
|
|
206
|
+
codemini skill install --scope=global .\my-skill
|
|
207
|
+
codemini skill enable my-skill
|
|
208
|
+
codemini skill disable my-skill
|
|
207
209
|
codemini skill reindex
|
|
208
210
|
```
|
|
209
211
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ CodeMini CLI is a terminal coding assistant built for teams that want a sharper,
|
|
|
16
16
|
|
|
17
17
|
It is designed around a deliberate idea: most coding workflows do not need a huge default tool surface or unrestricted shell behavior. Instead, CodeMini starts with a compact core, loads advanced tools on demand, and keeps the agent grounded in structured code operations, session todos, lightweight project indexing, and shell-aware safety rules.
|
|
18
18
|
|
|
19
|
-
**Contents** — [Why CodeMini CLI](#why-codemini-cli) · [Installation](#installation) · [Quick Start](#quick-start) · [Commands](#commands) · [Personalities (Souls)](#personalities-souls) · [Tool Model](#how-the-tool-model-works) · [Core Capabilities](#core-capabilities) · [Dream Loop (Built-in Memory Evolution)](#dream-loop-built-in-memory-evolution) · [Project Index](#project-index) · [Good Fit](#good-fit) · [Documentation](#documentation) · [Development](#development) · [License](#license)
|
|
19
|
+
**Contents** — [Why CodeMini CLI](#why-codemini-cli) · [Installation](#installation) · [Quick Start](#quick-start) · [Commands](#commands) · [Personalities (Souls)](#personalities-souls) · [Tool Model](#how-the-tool-model-works) · [Core Capabilities](#core-capabilities) · [Reflect Skills](#reflect-skills) · [Dream Loop (Built-in Memory Evolution)](#dream-loop-built-in-memory-evolution) · [Project Index](#project-index) · [Good Fit](#good-fit) · [Documentation](#documentation) · [Development](#development) · [License](#license)
|
|
20
20
|
|
|
21
21
|
### Why CodeMini CLI
|
|
22
22
|
|
|
@@ -67,6 +67,17 @@ CodeMini CLI can optionally use `fff-mcp` as a faster backend for `grep`, `glob`
|
|
|
67
67
|
- This means `fff-mcp` is an enhancement, not a hard dependency.
|
|
68
68
|
- `codemini doctor` now reports `FFF MCP availability` so you can verify whether it is active.
|
|
69
69
|
|
|
70
|
+
### Optional: Playwright Web Rendering
|
|
71
|
+
|
|
72
|
+
`web_fetch` uses a lightweight `fetch` + HTML parser path by default, so Playwright is not installed as a default dependency.
|
|
73
|
+
|
|
74
|
+
For JavaScript-rendered pages, install Playwright separately to enable richer browser-rendered fallback:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -g playwright
|
|
78
|
+
playwright install chromium
|
|
79
|
+
```
|
|
80
|
+
|
|
70
81
|
### Commands
|
|
71
82
|
|
|
72
83
|
| Command | Description |
|
|
@@ -106,10 +117,14 @@ Skills are reusable workflow patterns that guide how the agent approaches differ
|
|
|
106
117
|
Skills are installed and managed via `codemini skill`:
|
|
107
118
|
|
|
108
119
|
```bash
|
|
109
|
-
codemini skill list
|
|
110
|
-
codemini skill
|
|
120
|
+
codemini skill list # List builtin, project, and global skills
|
|
121
|
+
codemini skill install <path> # Install to .codemini/skills by default
|
|
122
|
+
codemini skill install --scope=global <path> # Install to the global skills directory
|
|
123
|
+
codemini skill inspect <name> # Inspect a skill's details
|
|
111
124
|
```
|
|
112
125
|
|
|
126
|
+
Bundled skills are built in, always enabled, and cannot be disabled or overwritten. Third-party skills live either in the project at `.codemini/skills/<name>/SKILL.md` or globally at `<base-config-dir>/skills/<name>/SKILL.md`, matching `/reflect`.
|
|
127
|
+
|
|
113
128
|
### How The Tool Model Works
|
|
114
129
|
|
|
115
130
|
CodeMini CLI intentionally separates tools into two layers:
|
|
@@ -144,11 +159,35 @@ Typical flow:
|
|
|
144
159
|
- Unified shell execution model:
|
|
145
160
|
- one-off commands via `run`
|
|
146
161
|
- long-running commands via `run` with `run_in_background=true`
|
|
147
|
-
- Lightweight project index under `.codemini
|
|
162
|
+
- Lightweight project index under `.codemini/`
|
|
148
163
|
- Tree-sitter based structured editing for function, class, and method-level changes
|
|
149
164
|
- Reply language control via `ui.reply_language`
|
|
150
165
|
- Safe mode enabled by default
|
|
151
166
|
|
|
167
|
+
### Reflect Skills
|
|
168
|
+
|
|
169
|
+
`/reflect` turns a successful workflow from the current session into a reviewed, reusable `SKILL.md` draft.
|
|
170
|
+
|
|
171
|
+
It is separate from the dream loop: reflect creates a skill draft, waits for review, and writes only after approval. It does not write inbox memories or run dream consolidation.
|
|
172
|
+
|
|
173
|
+
Common forms:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
/reflect
|
|
177
|
+
/reflect <what to preserve>
|
|
178
|
+
/reflect --scope=global <what to preserve>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `/reflect` is exploratory. CodeMini reviews recent context and proposes a skill only when there is a reusable pattern worth saving.
|
|
182
|
+
- `/reflect <what to preserve>` is directed. Use it when you already know which successful chain should become a skill, such as `/reflect preserve the provider tool-call recovery workflow`.
|
|
183
|
+
- `/reflect --scope=global <request>` writes the approved draft to the global skills directory instead of the current project.
|
|
184
|
+
- The draft is previewed first. Use `/yes` to write it, `/edit <feedback>` to revise it, or `/no` to discard it.
|
|
185
|
+
|
|
186
|
+
Approved skills are written to the same locations used by third-party skill install:
|
|
187
|
+
|
|
188
|
+
- Project scope: `.codemini/skills/<skill-name>/SKILL.md`
|
|
189
|
+
- Global scope: `<base-config-dir>/skills/<skill-name>/SKILL.md`
|
|
190
|
+
|
|
152
191
|
### Dream Loop (Built-in Memory Evolution)
|
|
153
192
|
|
|
154
193
|
Dream loop is built into the runtime as native tools and slash commands (not a skill-only workflow).
|
|
@@ -179,7 +218,7 @@ Execution mode behavior:
|
|
|
179
218
|
|
|
180
219
|
### Project Index
|
|
181
220
|
|
|
182
|
-
CodeMini CLI maintains a lightweight project index inside `.codemini
|
|
221
|
+
CodeMini CLI maintains a lightweight project index inside `.codemini/`:
|
|
183
222
|
|
|
184
223
|
- `project-map.json` — high-level repository facts such as languages, source roots, test roots, and entry candidates
|
|
185
224
|
- `file-index.json` — per-file structure such as imports, exports, functions, classes, and lightweight symbol hints
|
|
@@ -191,7 +230,7 @@ The index is initialized when entering a project and refreshed incrementally aft
|
|
|
191
230
|
|
|
192
231
|
- Global session state: `<base-config-dir>/sessions/`
|
|
193
232
|
- Project workspace state: `.codemini/`
|
|
194
|
-
- Lightweight project index: `.codemini
|
|
233
|
+
- Lightweight project index: `.codemini/`
|
|
195
234
|
- Bundled repo skills: `skills/<name>/SKILL.md`
|
|
196
235
|
- Project-scoped skills: `.codemini/skills/<name>/SKILL.md`
|
|
197
236
|
- Global installed skills: `<base-config-dir>/skills/<name>/SKILL.md`
|
|
@@ -300,6 +339,17 @@ CodeMini CLI 可以可选地使用 `fff-mcp` 作为 `grep`、`glob` 和部分 `l
|
|
|
300
339
|
- 这意味着 `fff-mcp` 是增强项,不是硬依赖。
|
|
301
340
|
- 现在可以通过 `codemini doctor` 里的 `FFF MCP availability` 看到它是否可用。
|
|
302
341
|
|
|
342
|
+
### 可选:Playwright 网页渲染
|
|
343
|
+
|
|
344
|
+
`web_fetch` 默认使用轻量的 `fetch` + HTML 解析路径,因此 Playwright 不再作为默认依赖安装。
|
|
345
|
+
|
|
346
|
+
如果经常读取 JavaScript 渲染页面,可以单独安装 Playwright,让 `web_fetch` 在需要时回退到浏览器渲染:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
npm install -g playwright
|
|
350
|
+
playwright install chromium
|
|
351
|
+
```
|
|
352
|
+
|
|
303
353
|
### 命令概览
|
|
304
354
|
|
|
305
355
|
| 命令 | 说明 |
|
|
@@ -339,10 +389,14 @@ Skill 是可复用的工作流模式,指导 agent 如何处理不同类型的
|
|
|
339
389
|
通过 `codemini skill` 管理技能:
|
|
340
390
|
|
|
341
391
|
```bash
|
|
342
|
-
codemini skill list
|
|
343
|
-
codemini skill
|
|
392
|
+
codemini skill list # 列出内置、项目级、全局 skill
|
|
393
|
+
codemini skill install <path> # 默认安装到 .codemini/skills
|
|
394
|
+
codemini skill install --scope=global <path> # 安装到全局 skills 目录
|
|
395
|
+
codemini skill inspect <name> # 查看某个 skill 的详细信息
|
|
344
396
|
```
|
|
345
397
|
|
|
398
|
+
内置 skill 是运行时能力,默认启用,不能禁用或被同名第三方 skill 覆盖。第三方 skill 分为项目级 `.codemini/skills/<name>/SKILL.md` 和全局 `<base-config-dir>/skills/<name>/SKILL.md`,与 `/reflect` 的写入位置一致。
|
|
399
|
+
|
|
346
400
|
### 工具模型怎么设计
|
|
347
401
|
|
|
348
402
|
CodeMini CLI 把工具分成两层:
|
|
@@ -377,11 +431,35 @@ CodeMini CLI 把工具分成两层:
|
|
|
377
431
|
- 统一的 shell 执行模型:
|
|
378
432
|
- 一次性命令直接 `run`
|
|
379
433
|
- 长运行命令通过 `run` + `run_in_background=true`
|
|
380
|
-
- 在 `.codemini
|
|
434
|
+
- 在 `.codemini/` 下维护轻量项目索引,帮助模型更快理解仓库
|
|
381
435
|
- 基于 Tree-sitter 的结构化编辑能力,适合函数级、类级、方法级改动
|
|
382
436
|
- 支持通过 `ui.reply_language` 控制回复语言
|
|
383
437
|
- safe mode 默认开启
|
|
384
438
|
|
|
439
|
+
### Reflect Skills(复盘沉淀 Skill)
|
|
440
|
+
|
|
441
|
+
`/reflect` 可以把当前会话中已经跑通的成功链路沉淀成一个可审阅、可复用的 `SKILL.md` 草稿。
|
|
442
|
+
|
|
443
|
+
它和 dream loop 是分开的:reflect 只生成 skill 草稿,先让用户审阅,确认后才写文件;不会写入 inbox,也不会触发 dream consolidation。
|
|
444
|
+
|
|
445
|
+
常用形式:
|
|
446
|
+
|
|
447
|
+
```text
|
|
448
|
+
/reflect
|
|
449
|
+
/reflect <要沉淀的用户要求>
|
|
450
|
+
/reflect --scope=global <要沉淀的用户要求>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
- `/reflect` 是探索模式。CodeMini 会查看近期上下文,只有在确实有可复用模式时才提出 skill 草稿。
|
|
454
|
+
- `/reflect <用户要求>` 是定向模式。适合你已经知道要沉淀哪条成功链路,例如 `/reflect 把刚才 provider tool_call 恢复链路沉淀成 skill`。
|
|
455
|
+
- `/reflect --scope=global <用户要求>` 会把确认后的草稿写到全局 skill 目录,而不是当前项目。
|
|
456
|
+
- 草稿会先预览。用 `/yes` 写入,用 `/edit <反馈>` 修改,用 `/no` 放弃。
|
|
457
|
+
|
|
458
|
+
确认后的 skill 写入位置和第三方 skill 安装保持一致:
|
|
459
|
+
|
|
460
|
+
- 项目级:`.codemini/skills/<skill-name>/SKILL.md`
|
|
461
|
+
- 全局级:`<base-config-dir>/skills/<skill-name>/SKILL.md`
|
|
462
|
+
|
|
385
463
|
### Dream Loop(内置记忆演化)
|
|
386
464
|
|
|
387
465
|
Dream loop 是运行时内置能力,不依赖 skill 才能使用。
|
|
@@ -412,7 +490,7 @@ Inbox 和持久记忆的区别:
|
|
|
412
490
|
|
|
413
491
|
### 项目索引
|
|
414
492
|
|
|
415
|
-
CodeMini CLI 会在 `.codemini
|
|
493
|
+
CodeMini CLI 会在 `.codemini/` 下维护一份轻量项目索引:
|
|
416
494
|
|
|
417
495
|
- `project-map.json` — 记录仓库的高层结构事实,比如语言、源码目录、测试目录、入口候选
|
|
418
496
|
- `file-index.json` — 记录文件级结构信息,比如 imports、exports、functions、classes 和轻量 symbol 提示
|
|
@@ -424,7 +502,7 @@ CodeMini CLI 会在 `.codemini-project/` 下维护一份轻量项目索引:
|
|
|
424
502
|
|
|
425
503
|
- 全局会话状态:`<base-config-dir>/sessions/`
|
|
426
504
|
- 项目工作区状态:`.codemini/`
|
|
427
|
-
- 轻量项目索引:`.codemini
|
|
505
|
+
- 轻量项目索引:`.codemini/`
|
|
428
506
|
- 仓库内置 skill:`skills/<name>/SKILL.md`
|
|
429
507
|
- 项目级 skill:`.codemini/skills/<name>/SKILL.md`
|
|
430
508
|
- 全局已安装 skill:`<base-config-dir>/skills/<name>/SKILL.md`
|
package/deployment.md
CHANGED
|
@@ -13,13 +13,13 @@ npm pack
|
|
|
13
13
|
Expected output:
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
|
-
codemini-cli-0.4.
|
|
16
|
+
codemini-cli-0.4.2.tgz
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
If you want to verify the package contents:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
tar -tf codemini-cli-0.4.
|
|
22
|
+
tar -tf codemini-cli-0.4.2.tgz
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## 2. Copy To The Target Machine
|
|
@@ -34,7 +34,7 @@ Copy the generated `.tgz` file to the Win10 machine by one of these methods:
|
|
|
34
34
|
Recommended target path:
|
|
35
35
|
|
|
36
36
|
```powershell
|
|
37
|
-
C:\temp\codemini-cli-0.4.
|
|
37
|
+
C:\temp\codemini-cli-0.4.2.tgz
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## 3. Environment Requirements
|
|
@@ -58,7 +58,7 @@ npm -v
|
|
|
58
58
|
Global install:
|
|
59
59
|
|
|
60
60
|
```powershell
|
|
61
|
-
npm install -g C:\temp\codemini-cli-0.4.
|
|
61
|
+
npm install -g C:\temp\codemini-cli-0.4.2.tgz
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
If global install is blocked by company policy, install in a working directory instead:
|
|
@@ -66,7 +66,7 @@ If global install is blocked by company policy, install in a working directory i
|
|
|
66
66
|
```powershell
|
|
67
67
|
mkdir C:\temp\coder-test
|
|
68
68
|
cd C:\temp\coder-test
|
|
69
|
-
npm install C:\temp\codemini-cli-0.4.
|
|
69
|
+
npm install C:\temp\codemini-cli-0.4.2.tgz
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## 5. Confirm Installation
|
|
@@ -158,6 +158,7 @@ Typical contents:
|
|
|
158
158
|
- `config.json`
|
|
159
159
|
- `sessions\`
|
|
160
160
|
- `skills\`
|
|
161
|
+
- project skills are stored per workspace under `.codemini\skills\`
|
|
161
162
|
- `input-history.json`
|
|
162
163
|
|
|
163
164
|
## 9. Skills
|
|
@@ -168,13 +169,19 @@ List installed skills:
|
|
|
168
169
|
codemini skill list
|
|
169
170
|
```
|
|
170
171
|
|
|
171
|
-
Install a local skill:
|
|
172
|
+
Install a local skill into the current project:
|
|
172
173
|
|
|
173
174
|
```powershell
|
|
174
175
|
codemini skill install C:\path\to\skill-folder
|
|
175
176
|
```
|
|
176
177
|
|
|
177
|
-
|
|
178
|
+
Install a local skill globally:
|
|
179
|
+
|
|
180
|
+
```powershell
|
|
181
|
+
codemini skill install --scope=global C:\path\to\skill-folder
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Rebuild the global registry:
|
|
178
185
|
|
|
179
186
|
```powershell
|
|
180
187
|
codemini skill reindex
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codemini-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"cli-truncate": "^6.0.0",
|
|
52
52
|
"duck-duck-scrape": "^2.2.7",
|
|
53
53
|
"ink": "^7.0.0",
|
|
54
|
-
"playwright": "^1.54.2",
|
|
55
54
|
"react": "^19.2.5",
|
|
56
55
|
"strip-ansi": "^7.2.0",
|
|
57
56
|
"web-tree-sitter": "^0.26.8"
|
package/src/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ Usage:
|
|
|
17
17
|
codemini run --pipeline <task> [--model <name>]
|
|
18
18
|
codemini config set|get|list <key> [value]
|
|
19
19
|
codemini doctor
|
|
20
|
-
codemini skill list|install|enable|disable|inspect|reindex
|
|
20
|
+
codemini skill list|install|enable|disable|inspect|reindex [--scope=project|global]
|
|
21
21
|
codemini --version
|
|
22
22
|
codemini --help`);
|
|
23
23
|
}
|
package/src/commands/skill.js
CHANGED
|
@@ -3,7 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { copyRecursive } from '../core/fs-utils.js';
|
|
6
|
-
import {
|
|
6
|
+
import { loadConfig, saveConfig } from '../core/config-store.js';
|
|
7
|
+
import { loadCommandsAndSkills } from '../core/command-loader.js';
|
|
8
|
+
import { getProjectSkillsDir, getSkillsDir } from '../core/paths.js';
|
|
7
9
|
import {
|
|
8
10
|
computeFileSha256,
|
|
9
11
|
readSkillRegistry,
|
|
@@ -11,17 +13,85 @@ import {
|
|
|
11
13
|
writeSkillRegistry
|
|
12
14
|
} from '../core/skill-registry.js';
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
function parseScopeArgs(args = [], { defaultScope = 'project', allowAll = false } = {}) {
|
|
17
|
+
let scope = defaultScope;
|
|
18
|
+
const rest = [];
|
|
19
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
20
|
+
const arg = String(args[index] || '');
|
|
21
|
+
if (arg === '--global') {
|
|
22
|
+
scope = 'global';
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (arg === '--project') {
|
|
26
|
+
scope = 'project';
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (arg === '--scope') {
|
|
30
|
+
const next = String(args[index + 1] || '').toLowerCase();
|
|
31
|
+
if (['project', 'global', ...(allowAll ? ['all', 'builtin'] : [])].includes(next)) {
|
|
32
|
+
scope = next;
|
|
33
|
+
index += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (arg.startsWith('--scope=')) {
|
|
38
|
+
const value = arg.slice('--scope='.length).toLowerCase();
|
|
39
|
+
if (['project', 'global', ...(allowAll ? ['all', 'builtin'] : [])].includes(value)) {
|
|
40
|
+
scope = value;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
rest.push(arg);
|
|
45
|
+
}
|
|
46
|
+
return { scope, rest };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function baseDirForScope(scope, cwd = process.cwd()) {
|
|
50
|
+
return scope === 'global' ? getSkillsDir() : getProjectSkillsDir(cwd);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scopeFromSource(source = '') {
|
|
54
|
+
if (source === 'bundled-skill') return 'builtin';
|
|
55
|
+
if (source === 'project-skill') return 'project';
|
|
56
|
+
if (source === 'global-skill' || source === 'registry-skill') return 'global';
|
|
57
|
+
return source || 'unknown';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function setSkillEnabledConfig(name, enabled) {
|
|
61
|
+
const config = await loadConfig();
|
|
62
|
+
config.skills = config.skills || {};
|
|
63
|
+
config.skills.enabled = config.skills.enabled || {};
|
|
64
|
+
config.skills.enabled[name] = enabled;
|
|
65
|
+
await saveConfig(config);
|
|
21
66
|
}
|
|
22
67
|
|
|
23
|
-
async function
|
|
24
|
-
const
|
|
68
|
+
async function listSkillEntries({ scope = 'all', cwd = process.cwd() } = {}) {
|
|
69
|
+
const commands = await loadCommandsAndSkills(cwd);
|
|
70
|
+
const config = await loadConfig();
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const command of commands.values()) {
|
|
73
|
+
if (command.metadata?.type !== 'skill') continue;
|
|
74
|
+
const itemScope = scopeFromSource(command.source);
|
|
75
|
+
if (scope !== 'all' && itemScope !== scope) continue;
|
|
76
|
+
entries.push({
|
|
77
|
+
name: command.name,
|
|
78
|
+
version: command.metadata?.version || '0.0.0',
|
|
79
|
+
description: command.metadata?.description || '',
|
|
80
|
+
scope: itemScope,
|
|
81
|
+
path: command.path,
|
|
82
|
+
enabled: itemScope === 'builtin' ? true : config.skills?.enabled?.[command.name] !== false
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return entries.sort((a, b) => `${a.scope}:${a.name}`.localeCompare(`${b.scope}:${b.name}`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function readSkillMeta(name, { scope = 'all', cwd = process.cwd() } = {}) {
|
|
89
|
+
const entries = await listSkillEntries({ scope, cwd });
|
|
90
|
+
const found = entries.find((item) => item.name === name);
|
|
91
|
+
if (!found) {
|
|
92
|
+
return { exists: false, path: '', preview: '', manifest: null };
|
|
93
|
+
}
|
|
94
|
+
const dir = path.dirname(found.path);
|
|
25
95
|
const manifestPath = path.join(dir, 'manifest.json');
|
|
26
96
|
let manifest = null;
|
|
27
97
|
try {
|
|
@@ -30,13 +100,13 @@ async function readSkillMeta(name) {
|
|
|
30
100
|
manifest = null;
|
|
31
101
|
}
|
|
32
102
|
const entryFile = manifest?.entry || 'SKILL.md';
|
|
33
|
-
const skillPath = path.join(dir, entryFile);
|
|
103
|
+
const skillPath = found.path || path.join(dir, entryFile);
|
|
34
104
|
try {
|
|
35
105
|
const content = await fs.readFile(skillPath, 'utf8');
|
|
36
106
|
const firstLines = content.split('\n').slice(0, 20).join('\n');
|
|
37
|
-
return { exists: true, path: skillPath, preview: firstLines, manifest };
|
|
107
|
+
return { exists: true, path: skillPath, preview: firstLines, manifest, scope: found.scope };
|
|
38
108
|
} catch {
|
|
39
|
-
return { exists: false, path: skillPath, preview: '', manifest };
|
|
109
|
+
return { exists: false, path: skillPath, preview: '', manifest, scope: found.scope };
|
|
40
110
|
}
|
|
41
111
|
}
|
|
42
112
|
|
|
@@ -104,11 +174,15 @@ async function resolveSkillSourceDir(sourcePath) {
|
|
|
104
174
|
throw new Error('skill install supports <skill-dir>, <SKILL.md>, or <skill.tgz>');
|
|
105
175
|
}
|
|
106
176
|
|
|
107
|
-
async function installSkill(sourcePath) {
|
|
177
|
+
async function installSkill(sourcePath, { scope = 'project', cwd = process.cwd() } = {}) {
|
|
108
178
|
const resolved = await resolveSkillSourceDir(sourcePath);
|
|
109
179
|
const manifest = await readManifestSafe(resolved.dir);
|
|
110
180
|
const folderName = manifest?.name || path.basename(resolved.dir);
|
|
111
|
-
const
|
|
181
|
+
const bundled = (await listSkillEntries({ scope: 'builtin', cwd })).find((item) => item.name === folderName);
|
|
182
|
+
if (bundled) {
|
|
183
|
+
throw new Error(`cannot install over builtin skill: ${folderName}`);
|
|
184
|
+
}
|
|
185
|
+
const targetDir = path.join(baseDirForScope(scope, cwd), folderName);
|
|
112
186
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
113
187
|
await copyRecursive(resolved.dir, targetDir);
|
|
114
188
|
|
|
@@ -117,16 +191,19 @@ async function installSkill(sourcePath) {
|
|
|
117
191
|
await fs.access(entryPath);
|
|
118
192
|
|
|
119
193
|
const hash = await computeFileSha256(entryPath);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
194
|
+
if (scope === 'global') {
|
|
195
|
+
await upsertSkillRegistryEntry(undefined, {
|
|
196
|
+
name: folderName,
|
|
197
|
+
version: manifest?.version || '0.0.0',
|
|
198
|
+
description: manifest?.description || '',
|
|
199
|
+
enabled: true,
|
|
200
|
+
source: sourcePath,
|
|
201
|
+
entryFile,
|
|
202
|
+
sha256: hash,
|
|
203
|
+
installedAt: new Date().toISOString()
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
await setSkillEnabledConfig(folderName, true);
|
|
130
207
|
|
|
131
208
|
if (resolved.cleanupDir) {
|
|
132
209
|
await fs.rm(resolved.cleanupDir, { recursive: true, force: true });
|
|
@@ -135,19 +212,28 @@ async function installSkill(sourcePath) {
|
|
|
135
212
|
return folderName;
|
|
136
213
|
}
|
|
137
214
|
|
|
138
|
-
async function setEnabled(name, enabled) {
|
|
215
|
+
async function setEnabled(name, enabled, { cwd = process.cwd() } = {}) {
|
|
216
|
+
const entries = await listSkillEntries({ scope: 'all', cwd });
|
|
217
|
+
const found = entries.find((item) => item.name === name);
|
|
218
|
+
if (!found) {
|
|
219
|
+
throw new Error(`skill not found: ${name}`);
|
|
220
|
+
}
|
|
221
|
+
if (found.scope === 'builtin') {
|
|
222
|
+
throw new Error(`builtin skill cannot be ${enabled ? 'enabled' : 'disabled'}: ${name}`);
|
|
223
|
+
}
|
|
224
|
+
await setSkillEnabledConfig(name, enabled);
|
|
139
225
|
const registry = await readSkillRegistry();
|
|
140
226
|
const idx = registry.skills.findIndex((s) => s.name === name);
|
|
141
|
-
if (idx
|
|
142
|
-
|
|
227
|
+
if (idx !== -1) {
|
|
228
|
+
registry.skills[idx].enabled = enabled;
|
|
229
|
+
await writeSkillRegistry(undefined, registry);
|
|
143
230
|
}
|
|
144
|
-
registry.skills[idx].enabled = enabled;
|
|
145
|
-
await writeSkillRegistry(undefined, registry);
|
|
146
231
|
}
|
|
147
232
|
|
|
148
|
-
async function reindexSkills() {
|
|
149
|
-
|
|
150
|
-
|
|
233
|
+
async function reindexSkills({ scope = 'global', cwd = process.cwd() } = {}) {
|
|
234
|
+
const baseDir = baseDirForScope(scope, cwd);
|
|
235
|
+
await fs.mkdir(baseDir, { recursive: true });
|
|
236
|
+
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
151
237
|
const registry = await readSkillRegistry();
|
|
152
238
|
const byName = new Map((registry.skills || []).map((s) => [s.name, s]));
|
|
153
239
|
const rebuilt = [];
|
|
@@ -155,7 +241,7 @@ async function reindexSkills() {
|
|
|
155
241
|
for (const entry of entries) {
|
|
156
242
|
if (!entry.isDirectory()) continue;
|
|
157
243
|
const name = entry.name;
|
|
158
|
-
const dir = path.join(
|
|
244
|
+
const dir = path.join(baseDir, name);
|
|
159
245
|
const manifest = await readManifestSafe(dir);
|
|
160
246
|
const entryFile = manifest?.entry || 'SKILL.md';
|
|
161
247
|
const entryPath = path.join(dir, entryFile);
|
|
@@ -178,22 +264,24 @@ async function reindexSkills() {
|
|
|
178
264
|
});
|
|
179
265
|
}
|
|
180
266
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
267
|
+
if (scope === 'global') {
|
|
268
|
+
await writeSkillRegistry(undefined, {
|
|
269
|
+
version: 1,
|
|
270
|
+
skills: rebuilt
|
|
271
|
+
});
|
|
272
|
+
}
|
|
185
273
|
|
|
186
274
|
return rebuilt.length;
|
|
187
275
|
}
|
|
188
276
|
|
|
189
277
|
function usage() {
|
|
190
278
|
console.log(`Usage:
|
|
191
|
-
codemini skill list
|
|
192
|
-
codemini skill install <path>
|
|
279
|
+
codemini skill list [--scope=all|project|global|builtin]
|
|
280
|
+
codemini skill install [--scope=project|global] <path>
|
|
193
281
|
codemini skill enable <name>
|
|
194
282
|
codemini skill disable <name>
|
|
195
|
-
codemini skill inspect <name>
|
|
196
|
-
codemini skill reindex`);
|
|
283
|
+
codemini skill inspect [--scope=all|project|global|builtin] <name>
|
|
284
|
+
codemini skill reindex [--scope=project|global]`);
|
|
197
285
|
}
|
|
198
286
|
|
|
199
287
|
export async function handleSkill(args) {
|
|
@@ -204,26 +292,27 @@ export async function handleSkill(args) {
|
|
|
204
292
|
}
|
|
205
293
|
|
|
206
294
|
if (sub === 'list') {
|
|
207
|
-
const
|
|
295
|
+
const { scope } = parseScopeArgs(rest, { defaultScope: 'all', allowAll: true });
|
|
296
|
+
const entries = await listSkillEntries({ scope });
|
|
208
297
|
if (entries.length === 0) {
|
|
209
298
|
console.log('No installed skills');
|
|
210
299
|
return;
|
|
211
300
|
}
|
|
212
301
|
for (const item of entries) {
|
|
213
|
-
const state = item.enabled !== false ? 'enabled' : 'disabled';
|
|
214
|
-
console.log(`${item.name}@${item.version || '0.0.0'} (${state})`);
|
|
302
|
+
const state = item.scope === 'builtin' ? 'builtin/default' : (item.enabled !== false ? 'enabled' : 'disabled');
|
|
303
|
+
console.log(`${item.name}@${item.version || '0.0.0'} [${item.scope}] (${state})`);
|
|
215
304
|
}
|
|
216
305
|
return;
|
|
217
306
|
}
|
|
218
307
|
|
|
219
308
|
if (sub === 'install') {
|
|
220
|
-
const
|
|
309
|
+
const { scope, rest: positional } = parseScopeArgs(rest, { defaultScope: 'project' });
|
|
310
|
+
const sourcePath = positional[0];
|
|
221
311
|
if (!sourcePath) {
|
|
222
312
|
throw new Error('skill install requires <path>');
|
|
223
313
|
}
|
|
224
|
-
const installedName = await installSkill(sourcePath);
|
|
225
|
-
|
|
226
|
-
console.log(`Installed skill: ${installedName}`);
|
|
314
|
+
const installedName = await installSkill(sourcePath, { scope });
|
|
315
|
+
console.log(`Installed skill: ${installedName} (${scope})`);
|
|
227
316
|
return;
|
|
228
317
|
}
|
|
229
318
|
|
|
@@ -238,25 +327,28 @@ export async function handleSkill(args) {
|
|
|
238
327
|
}
|
|
239
328
|
|
|
240
329
|
if (sub === 'inspect') {
|
|
241
|
-
const
|
|
330
|
+
const { scope, rest: positional } = parseScopeArgs(rest, { defaultScope: 'all', allowAll: true });
|
|
331
|
+
const name = positional[0];
|
|
242
332
|
if (!name) {
|
|
243
333
|
throw new Error('skill inspect requires <name>');
|
|
244
334
|
}
|
|
245
|
-
const meta = await readSkillMeta(name);
|
|
335
|
+
const meta = await readSkillMeta(name, { scope });
|
|
246
336
|
if (!meta.exists) {
|
|
247
337
|
throw new Error(`skill not found: ${name}`);
|
|
248
338
|
}
|
|
249
339
|
if (meta.manifest) {
|
|
250
340
|
console.log(`Manifest: ${JSON.stringify(meta.manifest, null, 2)}\n`);
|
|
251
341
|
}
|
|
342
|
+
console.log(`Scope: ${meta.scope}\n`);
|
|
252
343
|
console.log(`Path: ${meta.path}\n`);
|
|
253
344
|
console.log(meta.preview);
|
|
254
345
|
return;
|
|
255
346
|
}
|
|
256
347
|
|
|
257
348
|
if (sub === 'reindex') {
|
|
258
|
-
const
|
|
259
|
-
|
|
349
|
+
const { scope } = parseScopeArgs(rest, { defaultScope: 'global' });
|
|
350
|
+
const count = await reindexSkills({ scope });
|
|
351
|
+
console.log(`Reindexed skills: ${count} (${scope})`);
|
|
260
352
|
return;
|
|
261
353
|
}
|
|
262
354
|
|