@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 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.1.0.tgz` and `dist/package-check.json`. The
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.1.0.tgz skill-sync-cli --help
276
- npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.1.0.tgz skill-sync-cli init
277
- npx --yes --package /absolute/path/wangkh-skill-sync-cli-0.1.0.tgz skill-sync-cli plan
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.1.0.tgz` 和验证报告 `dist/package-check.json`。安装包验证通过真实 npx 命令离线执行,使用临时用户目录和全新的 npm 缓存,检查初始化、外部清单、预览和错误处理。验证结束后清理临时环境,保留安装包及报告。
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.1.0.tgz" skill-sync-cli --help
199
- npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.1.0.tgz" skill-sync-cli init
200
- npx --yes --package "C:\path\to\wangkh-skill-sync-cli-0.1.0.tgz" skill-sync-cli plan
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
1
+ # 发布 0.2.0
2
2
 
3
3
  ## 发布信息
4
4
 
5
5
  | 项目 | 值 |
6
6
  | --- | --- |
7
7
  | npm 包名 | `@wangkh/skill-sync-cli` |
8
- | 版本 | `0.1.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 首次发布时,npm 公共注册表拒绝了非作用域包名 `skill-sync-cli`,理由是它与已存在的 `skillsync-cli` 过于相似,并建议改用 `@wangkh/skill-sync-cli`;当前包名已采用该建议,发布需要 npm 账号的 2FA。GitHub CLI 登录账号为 `yudaiyan`,仓库地址按 `https://github.com/yudaiyan/skill-sync-cli` 准备。
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.1.0.tgz`:可分发的 npm 安装包。
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
- 完成发布准备提交后,创建计划仓库并推送 `main`。例如,选择公开仓库时:
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.1.0 --version
55
- npx --yes @wangkh/skill-sync-cli@0.1.0 --help
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
  ## 后续版本
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wangkh/skill-sync-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Sync declared agent skills across machines through the skills CLI",
5
5
  "author": "wkh",
6
6
  "repository": {
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, `${JSON.stringify(SAMPLE_MANIFEST, null, 2)}\n`, {
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
+ }