@wangkh/skill-sync-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/README.zh-CN.md +15 -4
- package/RELEASING.md +8 -13
- package/bin/skill-sync.mjs +15 -0
- package/package.json +1 -1
- package/src/commands.mjs +17 -3
- package/src/remote.mjs +51 -0
package/README.md
CHANGED
|
@@ -91,6 +91,18 @@ default user file. An explicit missing or invalid file produces an error when
|
|
|
91
91
|
reading; it never falls back to another manifest. Clone a remote configuration
|
|
92
92
|
repository first, then pass the path to its local JSON file.
|
|
93
93
|
|
|
94
|
+
To bootstrap from a manifest that is already published online, pass its file URL
|
|
95
|
+
to `init --from`. Gitee and GitHub `blob` page URLs are converted to raw file
|
|
96
|
+
URLs automatically, and the download is validated before anything is written:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx @wangkh/skill-sync-cli init --from https://gitee.com/ai_1024/skill-sync/blob/main/skills.json
|
|
100
|
+
npx @wangkh/skill-sync-cli init --from https://gitee.com/ai_1024/skill-sync/blob/main/skills.json --config ./my-skills/skills.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Without `--config`, the manifest is written to the default user location;
|
|
104
|
+
existing files still require `--force`.
|
|
105
|
+
|
|
94
106
|
The `npx skills` package itself does not discover these files. This wrapper
|
|
95
107
|
reads the selected manifest, then delegates each installation to `npx skills`.
|
|
96
108
|
|
|
@@ -198,6 +210,9 @@ npx @wangkh/skill-sync-cli plan --config ./my-skills/skills.json
|
|
|
198
210
|
npx @wangkh/skill-sync-cli sync --config ./my-skills/skills.json
|
|
199
211
|
```
|
|
200
212
|
|
|
213
|
+
You can skip the clone and initialize directly from the published file URL with
|
|
214
|
+
`npx @wangkh/skill-sync-cli init --from <url>`.
|
|
215
|
+
|
|
201
216
|
After committing and pushing future manifest edits, update each existing clone:
|
|
202
217
|
|
|
203
218
|
```bash
|
|
@@ -263,7 +278,7 @@ npm run check
|
|
|
263
278
|
npm run test:package
|
|
264
279
|
```
|
|
265
280
|
|
|
266
|
-
This creates `dist/wangkh-skill-sync-cli-0.
|
|
281
|
+
This creates `dist/wangkh-skill-sync-cli-0.2.0.tgz` and `dist/package-check.json`. The
|
|
267
282
|
package check runs real npx commands offline, with a temporary home directory
|
|
268
283
|
and a fresh npm cache. It verifies initialization, external manifests, previews, and
|
|
269
284
|
error handling, then removes the temporary environment. The archive and report
|
|
@@ -272,9 +287,9 @@ remain in `dist/`.
|
|
|
272
287
|
You can run that archive directly. Replace the path with its absolute location:
|
|
273
288
|
|
|
274
289
|
```bash
|
|
275
|
-
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.
|
|
276
|
-
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.
|
|
277
|
-
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.
|
|
290
|
+
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.2.0.tgz skill-sync-cli --help
|
|
291
|
+
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.2.0.tgz skill-sync-cli init
|
|
292
|
+
npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.2.0.tgz skill-sync-cli plan
|
|
278
293
|
```
|
|
279
294
|
|
|
280
295
|
These manual commands use your normal user configuration directory. To select a
|
package/README.zh-CN.md
CHANGED
|
@@ -48,6 +48,15 @@ npx @wangkh/skill-sync-cli path --config ./my-skills/skills.json
|
|
|
48
48
|
|
|
49
49
|
每次使用仓库清单时都传入 `--config`;省略时使用默认用户配置。远程配置仓库先用 Git 克隆到本机,再把本地 JSON 文件路径交给这个参数。
|
|
50
50
|
|
|
51
|
+
如果清单已经发布在 Gitee 或 GitHub 上,可以用 `init --from` 直接按链接初始化;`blob` 页面链接会自动转换为 raw 文件地址,下载后会先校验清单格式再写入:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx @wangkh/skill-sync-cli init --from https://gitee.com/ai_1024/skill-sync/blob/main/skills.json
|
|
55
|
+
npx @wangkh/skill-sync-cli init --from https://gitee.com/ai_1024/skill-sync/blob/main/skills.json --config ./my-skills/skills.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
不传 `--config` 时,清单写入默认用户配置位置;目标文件已存在时仍需显式加 `--force`。
|
|
59
|
+
|
|
51
60
|
## 清单格式
|
|
52
61
|
|
|
53
62
|
```json
|
|
@@ -150,6 +159,8 @@ npx @wangkh/skill-sync-cli plan --config ./my-skills/skills.json
|
|
|
150
159
|
npx @wangkh/skill-sync-cli sync --config ./my-skills/skills.json
|
|
151
160
|
```
|
|
152
161
|
|
|
162
|
+
也可以不克隆,直接用 `npx @wangkh/skill-sync-cli init --from <文件链接>` 按链接初始化。
|
|
163
|
+
|
|
153
164
|
以后修改清单并提交、推送后,在已经克隆仓库的电脑上执行:
|
|
154
165
|
|
|
155
166
|
```bash
|
|
@@ -190,14 +201,14 @@ npm run check
|
|
|
190
201
|
npm run test:package
|
|
191
202
|
```
|
|
192
203
|
|
|
193
|
-
命令会生成 `dist/wangkh-skill-sync-cli-0.
|
|
204
|
+
命令会生成 `dist/wangkh-skill-sync-cli-0.2.0.tgz` 和验证报告 `dist/package-check.json`。安装包验证通过真实 npx 命令离线执行,使用临时用户目录和全新的 npm 缓存,检查初始化、外部清单、预览和错误处理。验证结束后清理临时环境,保留安装包及报告。
|
|
194
205
|
|
|
195
206
|
也可以直接用 npx 运行安装包。请把路径替换为安装包的实际绝对路径:
|
|
196
207
|
|
|
197
208
|
```powershell
|
|
198
|
-
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.
|
|
199
|
-
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.
|
|
200
|
-
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.
|
|
209
|
+
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.2.0.tgz" skill-sync-cli --help
|
|
210
|
+
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.2.0.tgz" skill-sync-cli init
|
|
211
|
+
npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.2.0.tgz" skill-sync-cli plan
|
|
201
212
|
```
|
|
202
213
|
|
|
203
214
|
这些手动命令默认使用正常的用户配置目录。使用仓库清单时,在子命令后加上 `--config "C:\path\to\my-skills\skills.json"`。
|
package/RELEASING.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
# 发布 0.
|
|
1
|
+
# 发布 0.2.0
|
|
2
2
|
|
|
3
3
|
## 发布信息
|
|
4
4
|
|
|
5
5
|
| 项目 | 值 |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| npm 包名 | `@wangkh/skill-sync-cli` |
|
|
8
|
-
| 版本 | `0.
|
|
8
|
+
| 版本 | `0.2.0` |
|
|
9
9
|
| 命令入口 | `npx @wangkh/skill-sync-cli`;安装后的可执行命令为 `skill-sync-cli` |
|
|
10
10
|
| 计划 GitHub 仓库 | `https://github.com/yudaiyan/skill-sync-cli` |
|
|
11
11
|
| 默认上游 CLI | `skills@1.5.22` |
|
|
12
12
|
| 默认配置位置 | `~/.config/skill-sync/skills.json` |
|
|
13
13
|
| 仓库清单 | `--config /path/to/my-skills/skills.json`,也支持 `-c` |
|
|
14
14
|
|
|
15
|
-
2026-09-10
|
|
15
|
+
0.1.0 已于 2026-09-10 发布到 npm;首次尝试非作用域包名 `skill-sync-cli` 时被拒(与已存在的 `skillsync-cli` 过于相似),随后改用 `@wangkh/skill-sync-cli`。发布需要 npm 账号的 2FA。GitHub 仓库已创建:`https://github.com/yudaiyan/skill-sync-cli`。0.2.0 新增 `init --from <url>`,支持从 Gitee / GitHub 文件链接初始化清单。
|
|
16
16
|
|
|
17
17
|
## 生成并验证安装包
|
|
18
18
|
|
|
@@ -25,22 +25,17 @@ npm run test:package
|
|
|
25
25
|
|
|
26
26
|
`test:package` 会实际执行 `npm pack`,随后用独立的用户目录和 npm 缓存离线运行安装包。输出:
|
|
27
27
|
|
|
28
|
-
- `dist/wangkh-skill-sync-cli-0.
|
|
28
|
+
- `dist/wangkh-skill-sync-cli-0.2.0.tgz`:可分发的 npm 安装包。
|
|
29
29
|
- `dist/package-check.json`:包摘要、文件清单、执行环境和通过的检查。
|
|
30
30
|
|
|
31
31
|
`dist/` 已加入 Git 忽略规则。修改代码或文档后,需要重新生成安装包,使报告对应最终内容。
|
|
32
32
|
|
|
33
33
|
## 正式发布流程
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
gh repo create yudaiyan/skill-sync-cli --public --source . --remote origin --push
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
确认 npm 登录身份,然后从项目源码目录发布:
|
|
35
|
+
确认改动已提交并推送 `main`,然后确认 npm 登录身份,从项目源码目录发布:
|
|
42
36
|
|
|
43
37
|
```bash
|
|
38
|
+
git push origin main
|
|
44
39
|
npm whoami --registry=https://registry.npmjs.org/
|
|
45
40
|
npm publish
|
|
46
41
|
```
|
|
@@ -51,8 +46,8 @@ npm publish
|
|
|
51
46
|
|
|
52
47
|
```bash
|
|
53
48
|
npm view @wangkh/skill-sync-cli version
|
|
54
|
-
npx --yes @wangkh/skill-sync-cli@0.
|
|
55
|
-
npx --yes @wangkh/skill-sync-cli@0.
|
|
49
|
+
npx --yes @wangkh/skill-sync-cli@0.2.0 --version
|
|
50
|
+
npx --yes @wangkh/skill-sync-cli@0.2.0 --help
|
|
56
51
|
```
|
|
57
52
|
|
|
58
53
|
## 后续版本
|
package/bin/skill-sync.mjs
CHANGED
|
@@ -25,6 +25,7 @@ Usage:
|
|
|
25
25
|
|
|
26
26
|
Options:
|
|
27
27
|
-c, --config <file> Use this JSON manifest (relative or absolute path)
|
|
28
|
+
--from <url> Initialize from a remote manifest URL (init only)
|
|
28
29
|
--dry-run Print commands without downloading anything
|
|
29
30
|
--continue-on-error Continue after a skill installation fails
|
|
30
31
|
--force Replace the selected manifest with init
|
|
@@ -38,6 +39,7 @@ Examples:
|
|
|
38
39
|
npx ${COMMAND_NAME} sync
|
|
39
40
|
npx ${COMMAND_NAME} init --config ./my-skills/skills.json
|
|
40
41
|
npx ${COMMAND_NAME} sync --config ./my-skills/skills.json
|
|
42
|
+
npx ${COMMAND_NAME} init --from https://gitee.com/ai_1024/skill-sync/blob/main/skills.json
|
|
41
43
|
|
|
42
44
|
Without --config, use ~/.config/skill-sync/skills.json.
|
|
43
45
|
Relative config paths and project installations use the current working directory.
|
|
@@ -74,6 +76,16 @@ function parseArgs(argv) {
|
|
|
74
76
|
throw new Error("--config requires a file path")
|
|
75
77
|
}
|
|
76
78
|
options.configFile = value
|
|
79
|
+
} else if (arg === "--from" || arg.startsWith("--from=")) {
|
|
80
|
+
if (options.from !== undefined) {
|
|
81
|
+
throw new Error("Specify --from only once")
|
|
82
|
+
}
|
|
83
|
+
const inline = arg.startsWith("--from=")
|
|
84
|
+
const value = inline ? arg.slice("--from=".length) : argv[++index]
|
|
85
|
+
if (value === undefined || value.trim() === "" || (!inline && value.startsWith("-"))) {
|
|
86
|
+
throw new Error("--from requires a URL")
|
|
87
|
+
}
|
|
88
|
+
options.from = value
|
|
77
89
|
} else if (arg.startsWith("-")) {
|
|
78
90
|
throw new Error(`Unknown option: ${arg}`)
|
|
79
91
|
} else {
|
|
@@ -104,6 +116,9 @@ async function main() {
|
|
|
104
116
|
if (options.positional.length > 1) {
|
|
105
117
|
throw new Error(`Unexpected argument: ${options.positional[1]}`)
|
|
106
118
|
}
|
|
119
|
+
if (options.from !== undefined && command !== "init") {
|
|
120
|
+
throw new Error("--from is only supported by init")
|
|
121
|
+
}
|
|
107
122
|
|
|
108
123
|
const filename = resolveConfigPath(options.configFile)
|
|
109
124
|
if (command === "init") {
|
package/package.json
CHANGED
package/src/commands.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
runCommand,
|
|
12
12
|
statusCode,
|
|
13
13
|
} from "./runner.mjs"
|
|
14
|
+
import { downloadManifest } from "./remote.mjs"
|
|
14
15
|
|
|
15
16
|
const SAMPLE_MANIFEST = {
|
|
16
17
|
version: 1,
|
|
@@ -109,7 +110,7 @@ export async function syncCommand(manifestFilename, options = {}) {
|
|
|
109
110
|
return 0
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
export async function initCommand(manifestFilename, { force = false } = {}) {
|
|
113
|
+
export async function initCommand(manifestFilename, { force = false, from, fetchImpl } = {}) {
|
|
113
114
|
const filename = path.resolve(manifestFilename)
|
|
114
115
|
if (!force) {
|
|
115
116
|
try {
|
|
@@ -120,12 +121,25 @@ export async function initCommand(manifestFilename, { force = false } = {}) {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
let text = `${JSON.stringify(SAMPLE_MANIFEST, null, 2)}\n`
|
|
125
|
+
let source
|
|
126
|
+
if (from !== undefined) {
|
|
127
|
+
const downloaded = await downloadManifest(from, { fetchImpl })
|
|
128
|
+
source = downloaded.url
|
|
129
|
+
try {
|
|
130
|
+
normalizeManifest(JSON.parse(downloaded.text))
|
|
131
|
+
} catch (error) {
|
|
132
|
+
throw new Error(`Remote manifest at ${source} is invalid: ${error.message}`)
|
|
133
|
+
}
|
|
134
|
+
text = downloaded.text.endsWith("\n") ? downloaded.text : `${downloaded.text}\n`
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
await mkdir(path.dirname(filename), { recursive: true })
|
|
124
|
-
await writeFile(filename,
|
|
138
|
+
await writeFile(filename, text, {
|
|
125
139
|
encoding: "utf8",
|
|
126
140
|
flag: force ? "w" : "wx",
|
|
127
141
|
})
|
|
128
|
-
console.log(`Created ${filename}`)
|
|
142
|
+
console.log(source ? `Created ${filename} from ${source}` : `Created ${filename}`)
|
|
129
143
|
return 0
|
|
130
144
|
}
|
|
131
145
|
|
package/src/remote.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const MAX_MANIFEST_BYTES = 1024 * 1024
|
|
2
|
+
|
|
3
|
+
export function normalizeManifestUrl(input) {
|
|
4
|
+
let parsed
|
|
5
|
+
try {
|
|
6
|
+
parsed = new URL(input)
|
|
7
|
+
} catch {
|
|
8
|
+
return input
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (parsed.hostname === "gitee.com") {
|
|
12
|
+
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+)\/blob\/(.+)$/)
|
|
13
|
+
if (match) return `${parsed.origin}/${match[1]}/${match[2]}/raw/${match[3]}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (parsed.hostname === "github.com") {
|
|
17
|
+
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+)\/blob\/(.+)$/)
|
|
18
|
+
if (match) return `https://raw.githubusercontent.com/${match[1]}/${match[2]}/${match[3]}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return input
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function downloadManifest(url, { fetchImpl = fetch, timeoutMs = 20000 } = {}) {
|
|
25
|
+
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) {
|
|
26
|
+
throw new Error("--from requires an http(s) URL")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const normalized = normalizeManifestUrl(url)
|
|
30
|
+
let response
|
|
31
|
+
try {
|
|
32
|
+
response = await fetchImpl(normalized, {
|
|
33
|
+
redirect: "follow",
|
|
34
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
35
|
+
headers: { "user-agent": "skill-sync-cli" },
|
|
36
|
+
})
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Cannot download manifest from ${normalized}: ${error.message}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Cannot download manifest from ${normalized}: HTTP ${response.status}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const text = await response.text()
|
|
46
|
+
if (Buffer.byteLength(text, "utf8") > MAX_MANIFEST_BYTES) {
|
|
47
|
+
throw new Error(`Remote manifest is too large (limit ${MAX_MANIFEST_BYTES} bytes): ${normalized}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { url: normalized, text: text.replace(/^\uFEFF/, "") }
|
|
51
|
+
}
|