skvlt 0.9.9
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 +199 -0
- package/README.zh.md +199 -0
- package/package.json +55 -0
- package/src/cli.ts +211 -0
- package/src/commands/backup.ts +224 -0
- package/src/commands/completion.ts +197 -0
- package/src/commands/doctor.ts +257 -0
- package/src/commands/restore.ts +689 -0
- package/src/internal/args/parse-backup-args.ts +77 -0
- package/src/internal/args/parse-completion-args.ts +30 -0
- package/src/internal/args/parse-doctor-args.ts +40 -0
- package/src/internal/args/parse-restore-args.ts +119 -0
- package/src/internal/cli/theme.ts +136 -0
- package/src/internal/install/global-skill-state.ts +102 -0
- package/src/internal/install/resolve-install-concurrency.ts +74 -0
- package/src/internal/install/run-with-concurrency.ts +72 -0
- package/src/internal/install/skills-add-command.ts +53 -0
- package/src/internal/manifest/build-manifest.ts +78 -0
- package/src/internal/manifest/manifest-types.ts +27 -0
- package/src/internal/manifest/parse-manifest.ts +200 -0
- package/src/internal/paths/defaults.ts +19 -0
- package/src/internal/paths/format-cli-path.ts +13 -0
- package/src/internal/process/list-installed-skill-names.ts +27 -0
- package/src/internal/process/run-bunx.ts +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xi Xu
|
|
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,199 @@
|
|
|
1
|
+
# Skills Vault
|
|
2
|
+
|
|
3
|
+
**_[汉语](./README.zh.md)_**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/skvlt)
|
|
6
|
+
[](https://github.com/xixu-me/skills-vault/actions/workflows/ci.yml)
|
|
7
|
+
[](https://bun.sh/)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
Skills Vault is a CLI for backing up and restoring [Agent Skills](https://agentskills.io).
|
|
11
|
+
|
|
12
|
+
It is built for the common round-trip:
|
|
13
|
+
|
|
14
|
+
1. snapshot what is installed today
|
|
15
|
+
2. commit or move the manifest
|
|
16
|
+
3. restore the same set of skill sources on another machine
|
|
17
|
+
|
|
18
|
+
> [!IMPORTANT]
|
|
19
|
+
> Bun-only runtime. Skills Vault is designed to be installed and run with [Bun](https://bun.com).
|
|
20
|
+
|
|
21
|
+
## Why Skills Vault
|
|
22
|
+
|
|
23
|
+
Skills Vault exists to fill the gap described in [vercel-labs/skills#729](https://github.com/vercel-labs/skills/issues/729): a declarative manifest for portable, reproducible skill setups across machines and teams.
|
|
24
|
+
|
|
25
|
+
- Back up installed skills into a deterministic `skvlt.yaml` file.
|
|
26
|
+
- Restore from that manifest by source, by agent, or all at once.
|
|
27
|
+
- Preview installs before making changes with `--dry-run`.
|
|
28
|
+
- Check local setup quickly with `doctor`.
|
|
29
|
+
- Generate completion scripts for `bash`, `zsh`, and `powershell`.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
Back up your current skills:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bunx skvlt backup
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Preview what a restore would run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bunx skvlt restore --dry-run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Restore everything recorded in the manifest:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bunx skvlt restore --all
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If you want a curated, ready-to-use manifest, see [xixu-me/skvlt](https://github.com/xixu-me/skvlt), a maintained collection of `skvlt.yaml` snapshots.
|
|
52
|
+
|
|
53
|
+
The default manifest is `./skvlt.yaml`. A typical file looks like this:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
total_sources: 2
|
|
57
|
+
total_skills: 3
|
|
58
|
+
scope: "global"
|
|
59
|
+
|
|
60
|
+
sources:
|
|
61
|
+
"alpha/source":
|
|
62
|
+
count: 1
|
|
63
|
+
skills:
|
|
64
|
+
- "beta"
|
|
65
|
+
|
|
66
|
+
"beta/source":
|
|
67
|
+
count: 2
|
|
68
|
+
skills:
|
|
69
|
+
- "alpha"
|
|
70
|
+
- "zulu"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Commands
|
|
74
|
+
|
|
75
|
+
### `backup`
|
|
76
|
+
|
|
77
|
+
Snapshot installed skills into `skvlt.yaml`.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bunx skvlt backup
|
|
81
|
+
bunx skvlt backup --dry-run
|
|
82
|
+
bunx skvlt backup --output ./skvlt.yaml
|
|
83
|
+
bunx skvlt backup --project-scope --lock-file ./skills-lock.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`backup` reads installed skill names, joins them with lock-file metadata, and writes a grouped manifest keyed by source.
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> Global backup reads from `~/.agents/.skill-lock.json` by default. Project-scope backup currently requires `--lock-file`.
|
|
90
|
+
|
|
91
|
+
### `restore`
|
|
92
|
+
|
|
93
|
+
Install skills from a manifest.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bunx skvlt restore --all
|
|
97
|
+
bunx skvlt restore --only-source xixu-me/skills
|
|
98
|
+
bunx skvlt restore --project-scope
|
|
99
|
+
bunx skvlt restore --dry-run
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
By default, restore respects the scope recorded in the manifest. Use `--project-scope` to force a project install even when the manifest was created from global state.
|
|
103
|
+
|
|
104
|
+
`--dry-run` prints the derived `bunx skills add ...` commands without running them. Live installs run in lock-safe mode and are serialized to avoid global lock-file races.
|
|
105
|
+
|
|
106
|
+
For a curated manifest source, you can start from [xixu-me/skvlt](https://github.com/xixu-me/skvlt) and restore from the `skvlt.yaml` maintained there.
|
|
107
|
+
|
|
108
|
+
### `doctor`
|
|
109
|
+
|
|
110
|
+
Inspect the local Skills Vault environment and global skill state.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bunx skvlt doctor
|
|
114
|
+
bunx skvlt doctor --manifest ./skvlt.yaml
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`doctor` checks:
|
|
118
|
+
|
|
119
|
+
- the Bun runtime
|
|
120
|
+
- `bunx skills --help`
|
|
121
|
+
- whether the manifest exists
|
|
122
|
+
- whether the global lock file and skills directory are present
|
|
123
|
+
- whether tracked and installed global skills still match
|
|
124
|
+
|
|
125
|
+
### `completion`
|
|
126
|
+
|
|
127
|
+
Print shell completion scripts.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bunx skvlt completion bash
|
|
131
|
+
bunx skvlt completion zsh
|
|
132
|
+
bunx skvlt completion powershell
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## JSON Output
|
|
136
|
+
|
|
137
|
+
All top-level commands support `--json` for structured output:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
bunx skvlt --json doctor
|
|
141
|
+
bunx skvlt --json backup --dry-run
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Successful responses include `ok`, `command`, and `data`. Failures include `ok`, `command`, and a stable `error.code` plus message.
|
|
145
|
+
|
|
146
|
+
## Local Development
|
|
147
|
+
|
|
148
|
+
Install dependencies:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bun install --frozen-lockfile
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Useful commands:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
bun run ./src/cli.ts --help
|
|
158
|
+
bun run ./src/cli.ts doctor
|
|
159
|
+
bun run backup
|
|
160
|
+
bun run restore
|
|
161
|
+
bun run test
|
|
162
|
+
bun run check
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`bun run check` is the main verification gate. It runs formatting checks, the Bun test suite, and `npm pack --dry-run --json`.
|
|
166
|
+
|
|
167
|
+
If you change workflow files, also run:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
bunx prettier --check ".github/**/*.yml"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Package And Release Notes
|
|
174
|
+
|
|
175
|
+
The npm package publishes the `skvlt` bin from `src/cli.ts` and intentionally ships the `src` directory as its runtime payload.
|
|
176
|
+
|
|
177
|
+
Before publishing, verify the tarball contents locally:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm pack --dry-run --json
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The release workflow uses trusted publishing. The publish step is:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm publish --access public
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Troubleshooting
|
|
190
|
+
|
|
191
|
+
- If restore reports a global state mismatch, inspect `~/.agents/.skill-lock.json` and `~/.agents/skills`.
|
|
192
|
+
- If environment checks fail, start with `bunx skvlt doctor`.
|
|
193
|
+
- If packaging checks fail, run `bun run check` and review the tarball output from `npm pack --dry-run --json`.
|
|
194
|
+
|
|
195
|
+
For contribution, support, and security policy details, see [`CONTRIBUTING.md`](./CONTRIBUTING.md), [`SUPPORT.md`](./SUPPORT.md), and [`SECURITY.md`](./SECURITY.md).
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
Licensed under MIT. See [`LICENSE`](./LICENSE).
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Skills Vault
|
|
2
|
+
|
|
3
|
+
**_[English](./README.md)_**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/skvlt)
|
|
6
|
+
[](https://github.com/xixu-me/skills-vault/actions/workflows/ci.yml)
|
|
7
|
+
[](https://bun.sh/)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
Skills Vault 是一个用于备份和恢复 [Agent Skills](https://agentskills.io) 的 CLI。
|
|
11
|
+
|
|
12
|
+
它主要服务于这样一个常见流程:
|
|
13
|
+
|
|
14
|
+
1. 把当前机器上已安装的 skills 快照出来
|
|
15
|
+
2. 提交或移动这个 manifest
|
|
16
|
+
3. 在另一台机器上恢复同一组 skill source
|
|
17
|
+
|
|
18
|
+
> [!IMPORTANT]
|
|
19
|
+
> 仅支持 Bun 运行时。Skills Vault 设计上就是通过 [Bun](https://bun.com) 安装和运行的。
|
|
20
|
+
|
|
21
|
+
## 为什么需要 Skills Vault
|
|
22
|
+
|
|
23
|
+
Skills Vault 的存在,是为了补上 [vercel-labs/skills#729](https://github.com/vercel-labs/skills/issues/729) 里提到的缺口:缺少一种声明式 manifest,来支持可移植、可复现的 skills 配置。
|
|
24
|
+
|
|
25
|
+
- 把已安装的 skills 备份为确定性的 `skvlt.yaml`
|
|
26
|
+
- 按 source、按 agent,或者一次性从 manifest 恢复
|
|
27
|
+
- 通过 `--dry-run` 在真正改动前先预览
|
|
28
|
+
- 用 `doctor` 快速检查本地环境
|
|
29
|
+
- 为 `bash`、`zsh` 和 `powershell` 生成补全脚本
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
备份当前已安装的 skills:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bunx skvlt backup
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
预览一次 restore 将会执行什么:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bunx skvlt restore --dry-run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
恢复 manifest 中记录的全部内容:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bunx skvlt restore --all
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
如果你想直接使用一个精选好的 manifest,可以查看 [xixu-me/skvlt](https://github.com/xixu-me/skvlt),这是一个持续维护的 `skvlt.yaml` 集合。
|
|
52
|
+
|
|
53
|
+
默认 manifest 路径是 `./skvlt.yaml`。一个典型文件如下:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
total_sources: 2
|
|
57
|
+
total_skills: 3
|
|
58
|
+
scope: "global"
|
|
59
|
+
|
|
60
|
+
sources:
|
|
61
|
+
"alpha/source":
|
|
62
|
+
count: 1
|
|
63
|
+
skills:
|
|
64
|
+
- "beta"
|
|
65
|
+
|
|
66
|
+
"beta/source":
|
|
67
|
+
count: 2
|
|
68
|
+
skills:
|
|
69
|
+
- "alpha"
|
|
70
|
+
- "zulu"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 命令
|
|
74
|
+
|
|
75
|
+
### `backup`
|
|
76
|
+
|
|
77
|
+
把已安装的 skills 快照到 `skvlt.yaml`。
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bunx skvlt backup
|
|
81
|
+
bunx skvlt backup --dry-run
|
|
82
|
+
bunx skvlt backup --output ./skvlt.yaml
|
|
83
|
+
bunx skvlt backup --project-scope --lock-file ./skills-lock.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`backup` 会先读取已安装的 skill 名称,再结合 lock file 元数据,最终按 source 分组写出 manifest。
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> 全局备份默认读取 `~/.agents/.skill-lock.json`。项目级备份目前仍然需要显式传入 `--lock-file`。
|
|
90
|
+
|
|
91
|
+
### `restore`
|
|
92
|
+
|
|
93
|
+
从 manifest 安装 skills。
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bunx skvlt restore --all
|
|
97
|
+
bunx skvlt restore --only-source xixu-me/skills
|
|
98
|
+
bunx skvlt restore --project-scope
|
|
99
|
+
bunx skvlt restore --dry-run
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
默认情况下,`restore` 会遵循 manifest 中记录的 scope。如果希望即使 manifest 来自全局状态,也强制恢复到项目作用域,可以使用 `--project-scope`。
|
|
103
|
+
|
|
104
|
+
`--dry-run` 会打印推导出的 `bunx skills add ...` 命令,但不会真正执行。实际安装时会采用 lock-safe 模式串行执行,以避免全局 lock file 竞争。
|
|
105
|
+
|
|
106
|
+
如果你想从一个精选 manifest 起步,也可以直接使用 [xixu-me/skvlt](https://github.com/xixu-me/skvlt) 中维护的 `skvlt.yaml`。
|
|
107
|
+
|
|
108
|
+
### `doctor`
|
|
109
|
+
|
|
110
|
+
检查本地 Skills Vault 环境和全局 skill 状态。
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bunx skvlt doctor
|
|
114
|
+
bunx skvlt doctor --manifest ./skvlt.yaml
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`doctor` 会检查:
|
|
118
|
+
|
|
119
|
+
- Bun 运行时
|
|
120
|
+
- `bunx skills --help`
|
|
121
|
+
- manifest 是否存在
|
|
122
|
+
- 全局 lock file 和 skills 目录是否存在
|
|
123
|
+
- lock file 中追踪的技能与实际安装状态是否一致
|
|
124
|
+
|
|
125
|
+
### `completion`
|
|
126
|
+
|
|
127
|
+
输出 shell 补全脚本。
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bunx skvlt completion bash
|
|
131
|
+
bunx skvlt completion zsh
|
|
132
|
+
bunx skvlt completion powershell
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## JSON 输出
|
|
136
|
+
|
|
137
|
+
所有顶层命令都支持 `--json` 结构化输出:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
bunx skvlt --json doctor
|
|
141
|
+
bunx skvlt --json backup --dry-run
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
成功时会返回 `ok`、`command` 和 `data`。失败时会返回 `ok`、`command`,以及稳定的 `error.code` 和错误消息。
|
|
145
|
+
|
|
146
|
+
## 本地开发
|
|
147
|
+
|
|
148
|
+
安装依赖:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bun install --frozen-lockfile
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
常用命令:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
bun run ./src/cli.ts --help
|
|
158
|
+
bun run ./src/cli.ts doctor
|
|
159
|
+
bun run backup
|
|
160
|
+
bun run restore
|
|
161
|
+
bun run test
|
|
162
|
+
bun run check
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`bun run check` 是主要的发布前校验入口。它会运行格式检查、完整的 Bun 测试,以及 `npm pack --dry-run --json`。
|
|
166
|
+
|
|
167
|
+
如果你修改了 workflow 文件,也请额外运行:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
bunx prettier --check ".github/**/*.yml"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 打包与发布说明
|
|
174
|
+
|
|
175
|
+
npm 包会把 `src/cli.ts` 作为 `skvlt` 可执行入口,并且有意只将 `src` 目录作为运行时载荷发布。
|
|
176
|
+
|
|
177
|
+
发布前,可以先在本地检查 tarball 内容:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm pack --dry-run --json
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Release workflow 使用 trusted publishing。实际发布步骤为:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm publish --access public
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 故障排查
|
|
190
|
+
|
|
191
|
+
- 如果 restore 报告全局状态不一致,请检查 `~/.agents/.skill-lock.json` 和 `~/.agents/skills`
|
|
192
|
+
- 如果环境检查失败,先运行 `bunx skvlt doctor`
|
|
193
|
+
- 如果打包检查失败,运行 `bun run check`,并检查 `npm pack --dry-run --json` 的输出
|
|
194
|
+
|
|
195
|
+
关于贡献、支持和安全策略,请参阅 [`CONTRIBUTING.md`](./CONTRIBUTING.md)、[`SUPPORT.md`](./SUPPORT.md) 和 [`SECURITY.md`](./SECURITY.md)。
|
|
196
|
+
|
|
197
|
+
## 许可证
|
|
198
|
+
|
|
199
|
+
基于 MIT 许可证发布。详见 [`LICENSE`](./LICENSE)。
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skvlt",
|
|
3
|
+
"version": "0.9.9",
|
|
4
|
+
"description": "The CLI for backing up and restoring Agent Skills",
|
|
5
|
+
"author": "Xi Xu",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skvlt": "src/cli.ts"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"cli",
|
|
12
|
+
"skills",
|
|
13
|
+
"agents",
|
|
14
|
+
"backup",
|
|
15
|
+
"restore",
|
|
16
|
+
"bun"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/xixu-me/skills-vault#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/xixu-me/skills-vault/issues"
|
|
21
|
+
},
|
|
22
|
+
"funding": {
|
|
23
|
+
"url": "https://xi-xu.me/#sponsorships"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/xixu-me/skills-vault.git"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"bun": ">=1.3.11"
|
|
34
|
+
},
|
|
35
|
+
"packageManager": "bun@1.3.11",
|
|
36
|
+
"private": false,
|
|
37
|
+
"files": [
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"backup": "bun run ./src/cli.ts backup",
|
|
42
|
+
"backup:dry-run": "bun run ./src/cli.ts backup --dry-run",
|
|
43
|
+
"check": "bun run format:check && bun test && npm pack --dry-run --json",
|
|
44
|
+
"format": "prettier --write \"package.json\" \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
45
|
+
"format:check": "prettier --check \"package.json\" \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
46
|
+
"publish:check": "bun run check",
|
|
47
|
+
"prepublishOnly": "bun run publish:check",
|
|
48
|
+
"restore": "bun run ./src/cli.ts restore",
|
|
49
|
+
"restore:dry-run": "bun run ./src/cli.ts restore --dry-run",
|
|
50
|
+
"test": "bun test"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"prettier": "^3.8.1"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
4
|
+
import { runBackup } from "./commands/backup";
|
|
5
|
+
import { runCompletion } from "./commands/completion";
|
|
6
|
+
import { runDoctor } from "./commands/doctor";
|
|
7
|
+
import { runRestore } from "./commands/restore";
|
|
8
|
+
import {
|
|
9
|
+
bold,
|
|
10
|
+
dimGray,
|
|
11
|
+
textGray,
|
|
12
|
+
helpFooter,
|
|
13
|
+
helpExample,
|
|
14
|
+
helpHeading,
|
|
15
|
+
page,
|
|
16
|
+
stripAnsi,
|
|
17
|
+
} from "./internal/cli/theme";
|
|
18
|
+
|
|
19
|
+
type CliResult = {
|
|
20
|
+
exitCode: number;
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
errorCode?: string;
|
|
24
|
+
payload?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type RunCliOptions = {
|
|
28
|
+
reportProgress?: (chunk: string) => void;
|
|
29
|
+
streamOutput?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function printHelp(): string {
|
|
33
|
+
return `\n${[
|
|
34
|
+
"The CLI for backing up and restoring Agent Skills",
|
|
35
|
+
"",
|
|
36
|
+
helpHeading("Usage"),
|
|
37
|
+
" bunx skvlt <command> [options]",
|
|
38
|
+
"",
|
|
39
|
+
helpHeading("Commands"),
|
|
40
|
+
" backup Backup installed skills into a manifest",
|
|
41
|
+
" completion Print shell completion scripts",
|
|
42
|
+
" doctor Inspect the local environment and skill state",
|
|
43
|
+
" restore Restore skills from a manifest",
|
|
44
|
+
"",
|
|
45
|
+
helpHeading("Global Options"),
|
|
46
|
+
" -h, --help Show top-level help",
|
|
47
|
+
" -j, --json Print structured JSON output",
|
|
48
|
+
" -v, --version Print the installed package version",
|
|
49
|
+
"",
|
|
50
|
+
helpHeading("Examples"),
|
|
51
|
+
helpExample("bunx skvlt backup --output ./skvlt.yaml"),
|
|
52
|
+
helpExample("bunx skvlt completion bash"),
|
|
53
|
+
helpExample("bunx skvlt doctor"),
|
|
54
|
+
helpExample("bunx skvlt restore --all"),
|
|
55
|
+
helpExample("bunx skvlt restore --only-source xixu-me/skills"),
|
|
56
|
+
helpFooter("https://github.com/xixu-me/skills-vault"),
|
|
57
|
+
].join("\n")}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function landingCommand(command: string, description: string): string {
|
|
61
|
+
return ` ${dimGray("$")} ${textGray(command.padEnd(28))} ${dimGray(description)}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printLanding(): string {
|
|
65
|
+
return `\n${[
|
|
66
|
+
dimGray("The CLI for backing up and restoring Agent Skills"),
|
|
67
|
+
"",
|
|
68
|
+
landingCommand("bunx skvlt backup", "Backup installed skills"),
|
|
69
|
+
landingCommand("bunx skvlt restore", "Restore from skvlt.yaml"),
|
|
70
|
+
landingCommand("bunx skvlt doctor", "Inspect local setup"),
|
|
71
|
+
landingCommand("bunx skvlt completion", "Print shell completions"),
|
|
72
|
+
"",
|
|
73
|
+
`${dimGray("Explore the open-source repo at")} ${textGray("https://github.com/xixu-me/skills-vault")}\n`,
|
|
74
|
+
].join("\n")}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractGlobalFlags(argv: string[]) {
|
|
78
|
+
const positional: string[] = [];
|
|
79
|
+
let json = false;
|
|
80
|
+
|
|
81
|
+
for (const arg of argv) {
|
|
82
|
+
if (arg === "--json" || arg === "-j") {
|
|
83
|
+
json = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
positional.push(arg);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { argv: positional, json };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function toJsonResult(
|
|
94
|
+
command: string,
|
|
95
|
+
result: CliResult,
|
|
96
|
+
fallbackData?: unknown,
|
|
97
|
+
): CliResult {
|
|
98
|
+
const body =
|
|
99
|
+
result.exitCode === 0
|
|
100
|
+
? {
|
|
101
|
+
ok: true,
|
|
102
|
+
command,
|
|
103
|
+
data: result.payload ?? fallbackData ?? null,
|
|
104
|
+
}
|
|
105
|
+
: {
|
|
106
|
+
ok: false,
|
|
107
|
+
command,
|
|
108
|
+
error: {
|
|
109
|
+
code: result.errorCode ?? "CLI_ERROR",
|
|
110
|
+
message: stripAnsi(result.stderr || result.stdout).trim(),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...result,
|
|
116
|
+
stdout: `${JSON.stringify(body, null, 2)}\n`,
|
|
117
|
+
stderr: "",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Dispatches the top-level CLI command and normalizes text or JSON output.
|
|
123
|
+
*/
|
|
124
|
+
export async function runCli(
|
|
125
|
+
argv: string[],
|
|
126
|
+
options: RunCliOptions = {},
|
|
127
|
+
): Promise<CliResult> {
|
|
128
|
+
const extracted = extractGlobalFlags(argv);
|
|
129
|
+
const [command] = extracted.argv;
|
|
130
|
+
if (!command) {
|
|
131
|
+
const result: CliResult = {
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
stdout: `${printLanding()}\n`,
|
|
134
|
+
stderr: "",
|
|
135
|
+
payload: { text: stripAnsi(printLanding()) },
|
|
136
|
+
};
|
|
137
|
+
return extracted.json ? toJsonResult("landing", result) : result;
|
|
138
|
+
}
|
|
139
|
+
if (command === "--help" || command === "-h") {
|
|
140
|
+
const result: CliResult = { exitCode: 0, stdout: printHelp(), stderr: "" };
|
|
141
|
+
return extracted.json
|
|
142
|
+
? toJsonResult("help", result, { text: printHelp().trimEnd() })
|
|
143
|
+
: result;
|
|
144
|
+
}
|
|
145
|
+
if (command === "--version" || command === "-v") {
|
|
146
|
+
const result: CliResult = {
|
|
147
|
+
exitCode: 0,
|
|
148
|
+
stdout: `${packageJson.version}\n`,
|
|
149
|
+
stderr: "",
|
|
150
|
+
payload: { version: packageJson.version },
|
|
151
|
+
};
|
|
152
|
+
return extracted.json ? toJsonResult("version", result) : result;
|
|
153
|
+
}
|
|
154
|
+
if (command === "backup") {
|
|
155
|
+
const result = await runBackup(extracted.argv.slice(1), {
|
|
156
|
+
reportProgress: extracted.json ? undefined : options.reportProgress,
|
|
157
|
+
streamOutput: extracted.json ? false : options.streamOutput,
|
|
158
|
+
});
|
|
159
|
+
return extracted.json ? toJsonResult("backup", result) : result;
|
|
160
|
+
}
|
|
161
|
+
if (command === "doctor") {
|
|
162
|
+
const result = await runDoctor(extracted.argv.slice(1));
|
|
163
|
+
return extracted.json ? toJsonResult("doctor", result) : result;
|
|
164
|
+
}
|
|
165
|
+
if (command === "completion") {
|
|
166
|
+
const result = await runCompletion(extracted.argv.slice(1));
|
|
167
|
+
return extracted.json ? toJsonResult("completion", result) : result;
|
|
168
|
+
}
|
|
169
|
+
if (command === "restore") {
|
|
170
|
+
const result = await runRestore(extracted.argv.slice(1), {
|
|
171
|
+
reportProgress: extracted.json ? undefined : options.reportProgress,
|
|
172
|
+
streamOutput: extracted.json ? false : options.streamOutput,
|
|
173
|
+
});
|
|
174
|
+
return extracted.json ? toJsonResult("restore", result) : result;
|
|
175
|
+
}
|
|
176
|
+
const result: CliResult = {
|
|
177
|
+
exitCode: 1,
|
|
178
|
+
stdout: "",
|
|
179
|
+
stderr: page(
|
|
180
|
+
`Unknown command: ${command}\nRun ${bold("bunx skvlt --help")} for usage.`,
|
|
181
|
+
),
|
|
182
|
+
errorCode: "UNKNOWN_COMMAND",
|
|
183
|
+
};
|
|
184
|
+
return extracted.json ? toJsonResult(command, result) : result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Executes the CLI entrypoint and writes buffered output to the process streams.
|
|
189
|
+
*/
|
|
190
|
+
export async function main(
|
|
191
|
+
argv: string[] = process.argv.slice(2),
|
|
192
|
+
): Promise<number> {
|
|
193
|
+
const result = await runCli(argv, {
|
|
194
|
+
reportProgress: (chunk) => {
|
|
195
|
+
process.stdout.write(chunk);
|
|
196
|
+
},
|
|
197
|
+
streamOutput: true,
|
|
198
|
+
});
|
|
199
|
+
if (result.stdout) {
|
|
200
|
+
process.stdout.write(result.stdout);
|
|
201
|
+
}
|
|
202
|
+
if (result.stderr) {
|
|
203
|
+
console.error(result.stderr.trimEnd());
|
|
204
|
+
}
|
|
205
|
+
return result.exitCode;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (import.meta.main) {
|
|
209
|
+
const exitCode = await main();
|
|
210
|
+
process.exit(exitCode);
|
|
211
|
+
}
|