@viceme-ai/cli 0.1.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1] - 2026-07-20
4
+
5
+ ### Fixes
6
+
7
+ - add verified binary mirror fallback (`efb0d83`)
8
+
9
+ ### Other Changes
10
+
11
+ - add Feishu pull request notifications (`b6174b1`)
12
+
13
+ ## [0.2.0] - 2026-07-19
14
+
15
+ ### Features
16
+
17
+ - notify Feishu after CLI releases (`1584c09`)
18
+ - simplify CLI region and output contract (`021704e`)
19
+
20
+ ### Fixes
21
+
22
+ - publish through repository GitHub App (`7db30af`)
23
+ - prepare only on release intent (`a28c0b5`)
24
+ - use scoped deploy key for dev (`fcc7a0b`)
25
+ - support protected dev automation (`5f490fd`)
26
+ - make npm tests version agnostic (`01ef51f`)
27
+ - return direct CLI device authorization link (`6f125f3`)
28
+ - default CLI API to viceme.cn (`8ac5172`)
29
+ - retry npm registry reads after publish (`2c757af`)
30
+
31
+ ### Other Changes
32
+
33
+ - explain direct browser device authorization (`07a1cd9`)
34
+ - clarify Agent Skills and AI quick start (`60672b2`)
35
+ - add Chinese CLI guide (`aa892e0`)
36
+ - improve CLI quick start and safety guide (`3f5e9e3`)
37
+
3
38
  ## [0.1.0] - 2026-07-18
4
39
 
5
40
  ### Features
package/README.md CHANGED
@@ -1,70 +1,313 @@
1
1
  # Viceme CLI
2
2
 
3
- `viceme` is the deterministic client used by the bundled Viceme Agent Skill. Source parsing, LLM compilation, BuildRun materialization, and share publication stay server-side.
3
+ [![npm version](https://img.shields.io/npm/v/@viceme-ai/cli.svg)](https://www.npmjs.com/package/@viceme-ai/cli)
4
+ [![Go Version](https://img.shields.io/badge/go-%3E%3D1.23-blue.svg)](https://go.dev/)
5
+ [![CLI quality gates](https://github.com/ViceMe-AI/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/ViceMe-AI/cli/actions/workflows/ci.yml)
4
6
 
5
- ## Install
7
+ [中文版](./README.zh.md) | [English](./README.md)
6
8
 
7
- Node.js 18.20 or newer is required for the npm launcher.
9
+ The official command-line client and Agent Skill for publishing external Skills as stable, shareable Viceme Agents. It is built for AI coding tools such as Codex and Claude Code: the Agent Skill understands user intent, while the CLI provides the deterministic authentication, upload, publication, and status protocol.
10
+
11
+ [Install](#installation--quick-start) · [AI Agent Skills](#agent-skills) · [Auth](#authentication) · [Regions](#regions) · [Commands](#command-overview) · [Output contract](#json-output-contract) · [Security](#security-and-risk-controls) · [Development](#development)
12
+
13
+ > **Rollout status:** the Core publication transport and stable-link path are implemented behind a controlled allowlist. Public rollout remains blocked until the exact Candidate preview, test run, and result-confirmation gate is complete. The current `--yes` confirms the publication request; it is not proof that the user reviewed the final Candidate.
14
+
15
+ ## Why Viceme CLI?
16
+
17
+ - **Agent-native** — ships together with the official Viceme Agent Skill for Codex and Claude Code.
18
+ - **Deterministic boundary** — the CLI performs typed protocol actions; it does not start another conversational Agent loop.
19
+ - **Server-side compilation** — source parsing, LLM compilation, BuildRun materialization, and Release publication stay on Viceme infrastructure.
20
+ - **Stable publishing** — later releases of the same logical Agent keep the same share URL.
21
+ - **Multiple source types** — accepts GitHub Skills, pasted Xiaohongshu/RedSkill expressions, archives, and local Skill folders.
22
+ - **Secure by default** — credentials use the operating-system keychain, public mutations require confirmation, and downloaded binaries are checksum-verified.
23
+ - **Machine-stable output** — every data command uses the same JSON success/error envelope without an output-format flag.
24
+
25
+ ## Installation & Quick Start
26
+
27
+ ### Requirements
28
+
29
+ - Node.js 18.20 or newer
30
+ - macOS or Linux on `amd64`/`arm64`, or Windows on `amd64`/`arm64`
31
+
32
+ ### Quick Start (Human Users)
33
+
34
+ > **Note for AI assistants:** If you are an AI Agent helping the user install Viceme, jump directly to [Quick Start (AI Agent)](#quick-start-ai-agent). It defines the required cross-turn device login flow.
35
+
36
+ #### Install
37
+
38
+ Choose one of the following methods.
39
+
40
+ **Option 1 — One-shot npm install (recommended):**
41
+
42
+ ```bash
43
+ npx --yes @viceme-ai/cli@latest install
44
+ ```
45
+
46
+ **Option 2 — Install the npm launcher globally:**
8
47
 
9
48
  ```bash
10
49
  npm install --global @viceme-ai/cli
11
50
  viceme install
12
51
  ```
13
52
 
14
- For a one-shot bootstrap without a prior global install, use
15
- `npx --yes @viceme-ai/cli@latest install`.
53
+ Both methods persist the npm launcher, download the matching checksum-verified Go binary, and install the bundled Viceme Agent Skill. Binary downloads try GitHub Release first, then a configured non-default npm registry's `/-/binary/viceme-cli/` mirror, and finally the public npmmirror binary mirror. The launcher uses system `curl`, so standard proxy environment variables are honored. They default to the China service. For the international service:
54
+
55
+ ```bash
56
+ npx --yes @viceme-ai/cli@latest install --region global
57
+ ```
58
+
59
+ #### Authenticate and verify
60
+
61
+ If the installation result says authentication is required, start device login:
62
+
63
+ ```bash
64
+ viceme auth login --no-wait
65
+ ```
66
+
67
+ Open the returned `verification_url`; it normally links directly to the matching prefilled device request. Complete authorization, and then continue with the returned device code:
68
+
69
+ ```bash
70
+ viceme auth login --device-code <device-code>
71
+ viceme auth status
72
+ viceme skills doctor
73
+ ```
74
+
75
+ ## Quick Start (AI Agent)
76
+
77
+ > The following steps are for AI Agents. Browser authorization must be completed by the user in a separate turn; never wait indefinitely or ask the user for a token.
78
+
79
+ **Step 1 — Install**
80
+
81
+ Use the complete bootstrap command from the bundled Skill. The explicit npm registries are part of the installation trust boundary:
82
+
83
+ ```bash
84
+ npx --yes --registry=https://registry.npmjs.org --@viceme-ai:registry=https://registry.npmjs.org --package=@viceme-ai/cli@latest -- viceme install
85
+ ```
86
+
87
+ Read `data.authenticated` and `data.next_step` from the result. If authentication is already valid, continue to Step 4.
88
+
89
+ **Step 2 — Start device login when required**
90
+
91
+ ```bash
92
+ viceme auth login --no-wait
93
+ ```
94
+
95
+ Return the exact `data.verification_url`; the CLI normalizes it to the prefilled `verification_url_complete` browser link when available. Include `data.user_code` only as a fallback if the browser asks for it. Preserve `data.device_code` for the continuation command, then stop the current turn. Do not request, print, or place an access token in the conversation.
96
+
97
+ **Step 3 — Continue the same login in a later turn**
98
+
99
+ After the user confirms browser authorization:
100
+
101
+ ```bash
102
+ viceme auth login --device-code <device-code>
103
+ ```
104
+
105
+ If authorization is still pending, reuse the same device code before it expires. Do not start a second device flow unless the original one has expired.
106
+
107
+ **Step 4 — Verify**
108
+
109
+ ```bash
110
+ viceme auth status
111
+ viceme skills doctor
112
+ viceme skills list
113
+ ```
114
+
115
+ Continue only when authentication is valid and `skills doctor` reports a healthy, compatible installation.
116
+
117
+ **Step 5 — Inspect the first source**
118
+
119
+ ```bash
120
+ viceme skill inspect https://github.com/acme/poster-skill
121
+ ```
122
+
123
+ Inspection is read-only. Follow the bundled `viceme` Skill for source-specific handling, Target selection, confirmation, bounded job waiting, and result reporting. Public publication remains blocked until the exact Candidate confirmation gate described above is complete.
124
+
125
+ ## Regions
126
+
127
+ Viceme exposes one product-level region choice during installation:
128
+
129
+ | Region | Install command | API endpoint |
130
+ |---|---|---|
131
+ | China | `viceme install` | `https://api.viceme.cn` |
132
+ | International | `viceme install --region global` | `https://api.viceme.ai` |
133
+
134
+ The selected value is persisted as `region=cn|global`; later commands use it automatically. Credentials are isolated by region, so a China token is never reused against the international API, or vice versa.
135
+
136
+ There is no public API URL, profile, or output-format configuration. For local development only, set `VICEME_API_BASE_URL` in the terminal environment.
137
+
138
+ ## Agent Skills
139
+
140
+ The current release deliberately ships one platform-level Agent Skill:
141
+
142
+ | Skill | Description | Supported hosts |
143
+ |---|---|---|
144
+ | `viceme` | Install, inspect, convert, publish, update, or share external Skills as stable Viceme Agents; enforces authentication, source, Target, confirmation, job, and safety rules | Codex, Claude Code |
145
+
146
+ GitHub, Xiaohongshu/RedSkill, ZIP, and folder inputs are source types handled by the same `viceme` publication workflow, not separate Agent Skills. This keeps one consistent safety and stable-link contract across providers.
147
+
148
+ The CLI and `viceme` Agent Skill are released from this repository at the same version. `viceme install` installs the complete Skill bundle into detected supported hosts, while the binary embeds the agent-readable subset needed for deterministic self-inspection.
149
+
150
+ ```bash
151
+ viceme skills list
152
+ viceme skills read viceme
153
+ viceme skills read viceme references/commands.md
154
+ viceme skills doctor
155
+ ```
156
+
157
+ `skills doctor` checks the CLI version, Skill version, compatibility range, full bundle digest, and embedded-content digest independently. A modified or incompatible installation fails closed.
158
+
159
+ ## Authentication
160
+
161
+ | Command | Purpose |
162
+ |---|---|
163
+ | `viceme auth status` | Show whether the current region is authenticated |
164
+ | `viceme auth login --no-wait` | Start device authorization and return immediately |
165
+ | `viceme auth login --device-code <code>` | Complete a previously started authorization |
166
+ | `viceme auth logout` | Revoke and remove the current region credential |
167
+
168
+ Tokens are stored only in the operating-system keychain. There is no plaintext token fallback, and successful login output never contains the access or refresh token.
169
+
170
+ ## Supported Sources
171
+
172
+ ### GitHub or trusted provider
173
+
174
+ ```bash
175
+ viceme skill inspect https://github.com/acme/poster-skill
176
+ viceme skill publish --resolution-id <resolution-id> --yes
177
+ ```
178
+
179
+ ### Xiaohongshu or RedSkill copied expression
16
180
 
17
- The root `install` command performs the complete bootstrap: it atomically installs the matching Viceme Skill into Codex and/or Claude Code, creates a non-sensitive local config, checks authentication without printing credentials, and returns the device-login command when needed.
181
+ ```bash
182
+ viceme skill inspect --expression-stdin
183
+ viceme skill publish --resolution-id <resolution-id> --yes
184
+ ```
18
185
 
19
- The npm package is intentionally a small launcher, not six copies of the Go executable. It downloads the binary for the package's exact version from the corresponding GitHub Release, verifies the published SHA-256 checksum, writes a complete pair into a unique staging directory, and atomically publishes that directory as an immutable cache generation before executing it. Concurrent cold starts may repeat a download, but never share, overwrite, or delete a mutable binary/checksum pair; an interrupted process leaves only ignored staging state. Supported release targets are macOS and Linux on `amd64`/`arm64`, plus Windows on `amd64`/`arm64`.
186
+ The copied expression is untrusted data. Viceme extracts a locator and fetches the source through an approved connector; it never executes marketplace installation text.
20
187
 
21
- ## Authenticate and publish
188
+ ### Archive or local Skill folder
22
189
 
23
- The current Core records `--yes` as a versioned publication-admission confirmation and remains an internal, default-off allowlist probe. That admission permits the background compile, but it is not proof that the user reviewed the resulting immutable Release Candidate. Do not expose it as the public product flow: external rollout is blocked until T2 adds exact-candidate preview and confirmation before commit. The commands below exercise the transport and stable-link Core while that gate is implemented.
190
+ ```bash
191
+ viceme skill publish --file ./poster-skill.zip --new-target --target-alias poster --yes
192
+ viceme skill publish --dir ./poster-skill --new-target --target-alias poster --yes
193
+ ```
194
+
195
+ For later releases, resolve the existing Target and use optimistic concurrency. Never turn a conflict into a new share link:
24
196
 
25
197
  ```bash
26
- viceme auth login --no-wait --json
27
- viceme skill inspect https://github.com/acme/poster-skill --json
28
- viceme skill publish --resolution-id <resolution-id> --yes --json
29
- viceme job wait <publication-id> --timeout 60s --json
198
+ viceme skill target get target_123
199
+ viceme skill publish --file ./poster-skill-v2.zip \
200
+ --target-id target_123 --expected-target-version 4 --yes
30
201
  ```
31
202
 
32
- Credentials are stored only in the operating-system keychain. There is no plaintext token fallback.
203
+ ## Command Overview
204
+
205
+ | Command group | Purpose |
206
+ |---|---|
207
+ | `viceme install` | Install the persistent launcher, Agent Skill, and region configuration |
208
+ | `viceme auth` | Start, complete, inspect, or revoke device authentication |
209
+ | `viceme skill inspect` | Freeze and inspect a source candidate without publishing |
210
+ | `viceme skill publish` | Create or update a stable Skill Agent publication |
211
+ | `viceme skill target` | Resolve existing logical Agent Targets and versions |
212
+ | `viceme job` | Read, wait for, resume, or cancel a durable publication |
213
+ | `viceme skills` | Read, install, and diagnose the bundled Agent Skill |
214
+ | `viceme update` | Update the npm launcher, verified binary, and bundled Skill together |
215
+
216
+ Use `viceme <command> --help` for the exact flags. The release-checked machine-readable surface is stored in [`skills/viceme/references/command-manifest.json`](skills/viceme/references/command-manifest.json).
217
+
218
+ ## JSON Output Contract
33
219
 
34
- ## Diagnose and update
220
+ All data commands emit a stable JSON envelope by default.
221
+
222
+ Success is written to **stdout** with exit code `0`:
223
+
224
+ ```json
225
+ {
226
+ "ok": true,
227
+ "data": {},
228
+ "meta": {
229
+ "cli_version": "0.1.0",
230
+ "skill_version": "0.1.0"
231
+ }
232
+ }
233
+ ```
234
+
235
+ CLI execution errors are written to **stderr** with a non-zero exit code:
236
+
237
+ ```json
238
+ {
239
+ "ok": false,
240
+ "error": {
241
+ "type": "validation",
242
+ "subtype": "source_required",
243
+ "message": "provide exactly one source argument or --expression-stdin"
244
+ },
245
+ "meta": {
246
+ "cli_version": "0.1.0",
247
+ "skill_version": "0.1.0"
248
+ }
249
+ }
250
+ ```
251
+
252
+ Determine command success from the process exit code or `ok == true`. A successfully read publication may still contain a business terminal status such as `unsupported`, `rejected`, or `failed`; inspect `data.status` instead of treating those states as CLI transport failures.
253
+
254
+ | Exit code | Meaning |
255
+ |---|---|
256
+ | `0` | Command completed; inspect returned business status when applicable |
257
+ | `2` | Validation failure |
258
+ | `3` | Authentication or authorization failure |
259
+ | `4` | Retryable network failure |
260
+ | `5` | Internal or protocol failure |
261
+ | `6` | Policy rejection before publication creation |
262
+ | `10` | Explicit confirmation required |
263
+
264
+ ## Security and Risk Controls
265
+
266
+ - **No source execution** — the CLI and compiler do not execute third-party scripts, binaries, shell fragments, marketplace commands, or copied instructions.
267
+ - **Explicit public mutation** — publishing and cancellation require `--yes`; exit code `10` means the Agent must obtain confirmation, not silently retry.
268
+ - **Safe preview** — use `--dry-run` on inspect or publish when the user needs to review the planned request without network or publication side effects.
269
+ - **Credential isolation** — credentials stay in the OS keychain and are namespaced by region.
270
+ - **Immutable inputs** — inspection binds publication to an immutable source snapshot rather than re-reading a floating URL later.
271
+ - **Bounded waiting** — `job wait` has a maximum duration and returns the latest durable state without cancelling the workflow.
272
+ - **Verified distribution** — the npm launcher downloads the binary for its exact package version from GitHub or a binary mirror and verifies it against the checksum manifest bundled in the npm package before activation.
273
+
274
+ ## Diagnose and Update
35
275
 
36
276
  ```bash
37
- viceme skills doctor --json
38
- viceme update --check --json
39
- viceme update --json
277
+ viceme skills doctor
278
+ viceme update --check
279
+ viceme update
40
280
  ```
41
281
 
42
- Every Skill installation records its CLI version, Skill version, minimum compatible CLI version, full bundle digest, and agent-readable embedded digest. `skills doctor` validates each item independently.
282
+ `viceme update` installs one exact npm package version, acquires its verified Go binary, and refreshes the bundled Skill from that same release. A standalone development binary is never silently replaced.
43
283
 
44
- `viceme update` is supported for the npm launcher. It resolves the latest version, globally installs that exact package with lifecycle scripts disabled, and then invokes the same exact package to acquire its verified binary and reinstall its bundled Skill. A standalone development binary is not silently replaced; bootstrap it through npm first. Homebrew and signed standalone update manifests remain future distribution channels.
284
+ ## Development
45
285
 
46
- ## Development and quality gates
286
+ Go 1.23 or newer is required when building from source.
47
287
 
48
288
  ```bash
49
289
  make build
50
290
  make test
51
291
  make check
292
+ make skill-check
52
293
  make npm-package-check
53
294
  make quality-check
54
295
  ```
55
296
 
56
- `make npm-package-check` does not require a published GitHub Release. It injects the locally built Go executable through `VICEME_BINARY_PATH`, packs the npm tarball, and runs the real npm launcher through `install` in an isolated temporary home. The override is for development and CI only.
297
+ The main checked-in quality artifacts are:
298
+
299
+ - [`skills/viceme/references/command-manifest.json`](skills/viceme/references/command-manifest.json), generated from the Cobra command tree;
300
+ - [`quality/example-dry-runs.json`](quality/example-dry-runs.json), which executes documented source paths without network access;
301
+ - [`quality/release-manifest.json`](quality/release-manifest.json), which pins CLI/Skill compatibility and content digests.
57
302
 
58
- The checked-in quality artifacts are:
303
+ `make npm-package-check` builds the Go executable, packs the real npm tarball, and runs the launcher in isolated temporary homes. It does not require an already-published GitHub Release.
59
304
 
60
- - `skills/viceme/references/command-manifest.json`, generated from the Cobra command tree;
61
- - `quality/example-dry-runs.json`, which executes the documented inspect/publish paths without network access;
62
- - `quality/release-manifest.json`, which pins CLI/Skill compatibility and both Skill digests.
305
+ ## Releases
63
306
 
64
- Set `VICEME_API_BASE_URL` for local integration. Release asset names are fixed as `viceme_<version>_<goos>_<goarch>[.exe]` with a sibling `.sha256`. The workflow checks that exactly all six binary/checksum pairs exist and blocks npm publication until they have been uploaded to a completed GitHub Release.
307
+ Maintainers merge normal changes into `dev`; they do not manually edit versions, create tags, write changelog entries, or run `npm publish`. GitHub Actions maintains a single automated `dev -> main` Release PR. Merging that PR authorizes the reviewed version, tag, immutable GitHub Release assets, and npm trusted publication.
65
308
 
66
- Releases do not require a maintainer to edit versions, write a changelog, create a tag, or run `npm publish`. A push to `dev` runs `.github/workflows/release-pr.yml`, which selects the next stable semantic version from Conventional Commits, synchronizes npm/Go/Skill metadata and generated digests, updates `CHANGELOG.md`, runs the release quality gates, and creates or updates the `dev` to `main` Release PR. Merge that PR to approve the release; `.github/workflows/release.yml` then tags the exact reviewed head, reconnects the merged `main` commit into `dev`, creates or verifies the immutable GitHub Release, and publishes or verifies the npm package. See [`docs/releasing.md`](docs/releasing.md) for setup, recovery, and versioning details.
309
+ See [`docs/releasing.md`](docs/releasing.md) for repository setup, OIDC trusted publishing, recovery, and integrity rules.
67
310
 
68
- npm publishing uses a pinned OIDC-capable npm CLI and provenance. The steady-state authentication path is GitHub Actions trusted publishing (`id-token: write`): configure `@viceme-ai/cli` to trust `ViceMe-AI/cli` and `.github/workflows/release.yml`. If npm requires a credential for the package's first-ever publication, temporarily configure a granular publish token as the `NPM_TOKEN` repository secret; the workflow exposes it only as `NODE_AUTH_TOKEN` to the final publish-or-verify step. Remove the secret after trusted publishing is configured.
311
+ ## Contributing
69
312
 
70
- Release reruns are fail-closed. If the GitHub Release already exists, the workflow byte-compares every existing asset with the binaries/checksums rebuilt from the tag, uploads only assets missing after an interrupted release, and then verifies the complete 12-asset set. It never overwrites an existing asset. npm publication accepts stable `x.y.z` versions only. If the exact version already exists, its registry integrity must equal the local `npm pack` integrity; an older `latest` tag is safely promoted to that exact version, while rerunning an older release never demotes a newer `latest`. This lets either publication stage be retried without deleting or replacing immutable content.
313
+ Issues and pull requests are welcome. Changes to the public command surface must update the generated command manifest, bundled Skill examples, quality fixtures, and tests in the same PR.
package/README.zh.md ADDED
@@ -0,0 +1,313 @@
1
+ # Viceme CLI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@viceme-ai/cli.svg)](https://www.npmjs.com/package/@viceme-ai/cli)
4
+ [![Go Version](https://img.shields.io/badge/go-%3E%3D1.23-blue.svg)](https://go.dev/)
5
+ [![CLI quality gates](https://github.com/ViceMe-AI/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/ViceMe-AI/cli/actions/workflows/ci.yml)
6
+
7
+ [中文版](./README.zh.md) | [English](./README.md)
8
+
9
+ Viceme 官方命令行客户端与 Agent Skill,用于将外部 Skill 发布为稳定、可分享的 Viceme Agent。它面向 Codex、Claude Code 等 AI 编程工具:Agent Skill 负责理解用户意图,CLI 负责确定性的认证、上传、发布和状态协议。
10
+
11
+ [安装](#安装与快速开始) · [AI Agent Skills](#agent-skills) · [认证](#认证) · [区域](#区域) · [命令](#命令概览) · [输出契约](#json-输出契约) · [安全](#安全与风险控制) · [开发](#开发)
12
+
13
+ > **开放状态:** Core 发布传输和稳定链接链路已在受控白名单后实现。正式对外开放仍取决于精确 Candidate 预览、试运行和结果确认门的完成。当前 `--yes` 只确认发起发布请求,并不代表用户已经审阅最终 Candidate。
14
+
15
+ ## 为什么选择 Viceme CLI?
16
+
17
+ - **为 Agent 原生设计** — 与 Codex、Claude Code 可使用的官方 Viceme Agent Skill 一起发布。
18
+ - **确定性边界** — CLI 执行类型化协议操作,不会再启动一层对话式 Agent Loop。
19
+ - **服务端编译** — 来源解析、LLM 编译、BuildRun 固化和 Release 发布均在 Viceme 基础设施中完成。
20
+ - **稳定发布** — 同一个逻辑 Agent 后续发布新版本时继续使用同一个分享链接。
21
+ - **支持多种来源** — 支持 GitHub Skill、小红书或 RedSkill 复制口令、压缩包和本地 Skill 目录。
22
+ - **默认安全** — 凭证保存在操作系统密钥链中,公开变更需要确认,下载的二进制文件必须通过校验和验证。
23
+ - **机器稳定输出** — 所有数据命令统一使用 JSON 成功或错误信封,不需要输出格式参数。
24
+
25
+ ## 安装与快速开始
26
+
27
+ ### 环境要求
28
+
29
+ - Node.js 18.20 或更高版本
30
+ - macOS 或 Linux `amd64`/`arm64`,或 Windows `amd64`/`arm64`
31
+
32
+ ### 快速开始(人类用户)
33
+
34
+ > **AI 助手注意:** 如果你是正在帮助用户安装 Viceme 的 AI Agent,请直接跳转到[快速开始(AI Agent)](#快速开始ai-agent)。该章节定义了必须遵循的跨回合设备登录流程。
35
+
36
+ #### 安装
37
+
38
+ 以下两种方式任选其一。
39
+
40
+ **方式一 — 通过 npm 一次性安装(推荐):**
41
+
42
+ ```bash
43
+ npx --yes @viceme-ai/cli@latest install
44
+ ```
45
+
46
+ **方式二 — 全局安装 npm 启动器:**
47
+
48
+ ```bash
49
+ npm install --global @viceme-ai/cli
50
+ viceme install
51
+ ```
52
+
53
+ 两种方式都会持久化 npm 启动器、下载匹配且通过校验和验证的 Go 二进制文件,并安装随包发布的 Viceme Agent Skill。二进制下载依次尝试 GitHub Release、用户配置的非默认 npm registry 的 `/-/binary/viceme-cli/` 镜像,最后回退到公共 npmmirror binary 镜像。启动器使用系统 `curl`,因此会遵循标准代理环境变量。默认使用中国区服务。使用国际区服务:
54
+
55
+ ```bash
56
+ npx --yes @viceme-ai/cli@latest install --region global
57
+ ```
58
+
59
+ #### 认证并验证
60
+
61
+ 如果安装结果提示需要认证,启动设备登录:
62
+
63
+ ```bash
64
+ viceme auth login --no-wait
65
+ ```
66
+
67
+ 打开返回的 `verification_url`;它通常会直接进入已预填对应设备码的授权请求。完成授权,然后使用返回的 device code 继续:
68
+
69
+ ```bash
70
+ viceme auth login --device-code <device-code>
71
+ viceme auth status
72
+ viceme skills doctor
73
+ ```
74
+
75
+ ## 快速开始(AI Agent)
76
+
77
+ > 以下步骤面向 AI Agent。浏览器授权必须由用户在另一个回合完成;不要无限等待,也不要向用户索取令牌。
78
+
79
+ **第 1 步 — 安装**
80
+
81
+ 使用随包 Skill 中定义的完整引导命令。显式 npm registry 是安装信任边界的一部分:
82
+
83
+ ```bash
84
+ npx --yes --registry=https://registry.npmjs.org --@viceme-ai:registry=https://registry.npmjs.org --package=@viceme-ai/cli@latest -- viceme install
85
+ ```
86
+
87
+ 读取结果中的 `data.authenticated` 和 `data.next_step`。如果认证已经有效,直接进入第 4 步。
88
+
89
+ **第 2 步 — 需要时启动设备登录**
90
+
91
+ ```bash
92
+ viceme auth login --no-wait
93
+ ```
94
+
95
+ 向用户返回准确的 `data.verification_url`;存在 `verification_url_complete` 时,CLI 会把这个已预填设备码的浏览器直达链接规范化为 `verification_url`。只有浏览器要求输入时,才把 `data.user_code` 作为备用信息提供。保留 `data.device_code` 供后续命令使用,然后结束当前回合。不要在对话中索取、打印或传递访问令牌。
96
+
97
+ **第 3 步 — 在后续回合继续同一个登录流程**
98
+
99
+ 用户确认已在浏览器完成授权后:
100
+
101
+ ```bash
102
+ viceme auth login --device-code <device-code>
103
+ ```
104
+
105
+ 如果授权仍处于等待状态,应在过期前继续使用同一个 device code。只有原流程已过期时才能重新发起设备登录。
106
+
107
+ **第 4 步 — 验证**
108
+
109
+ ```bash
110
+ viceme auth status
111
+ viceme skills doctor
112
+ viceme skills list
113
+ ```
114
+
115
+ 只有认证有效且 `skills doctor` 报告安装健康、版本兼容时,才能继续。
116
+
117
+ **第 5 步 — 检查第一个来源**
118
+
119
+ ```bash
120
+ viceme skill inspect https://github.com/acme/poster-skill
121
+ ```
122
+
123
+ inspect 是只读操作。后续应按照随包发布的 `viceme` Skill 处理不同来源、Target 选择、用户确认、有界任务等待和结果返回。在上方所述的精确 Candidate 确认门完成前,公开发布流程仍保持关闭。
124
+
125
+ ## 区域
126
+
127
+ Viceme 在安装时只提供一个产品级区域选择:
128
+
129
+ | 区域 | 安装命令 | API 地址 |
130
+ |---|---|---|
131
+ | 中国区 | `viceme install` | `https://api.viceme.cn` |
132
+ | 国际区 | `viceme install --region global` | `https://api.viceme.ai` |
133
+
134
+ 选择结果保存为 `region=cn|global`,后续命令会自动使用。不同区域的凭证彼此隔离,中国区令牌不会用于国际区 API,反之亦然。
135
+
136
+ CLI 不提供公开的 API 地址、profile 或输出格式配置。本地开发时可以在终端环境中设置 `VICEME_API_BASE_URL`。
137
+
138
+ ## Agent Skills
139
+
140
+ 当前版本有意只提供一个平台级 Agent Skill:
141
+
142
+ | Skill | 说明 | 支持的宿主 |
143
+ |---|---|---|
144
+ | `viceme` | 将外部 Skill 安装、检查、转换、发布、更新或分享为稳定的 Viceme Agent;统一约束认证、来源、Target、确认、任务和安全规则 | Codex、Claude Code |
145
+
146
+ GitHub、小红书/RedSkill、ZIP 和目录是同一个 `viceme` 发布流程处理的来源类型,不是相互独立的 Agent Skills。这样可以让不同来源共享一致的安全边界和稳定链接合同。
147
+
148
+ CLI 与 `viceme` Agent Skill 从同一个仓库以相同版本发布。`viceme install` 会把完整 Skill Bundle 安装到检测到的受支持宿主中;二进制文件同时嵌入用于确定性自检、可供 Agent 阅读的内容子集。
149
+
150
+ ```bash
151
+ viceme skills list
152
+ viceme skills read viceme
153
+ viceme skills read viceme references/commands.md
154
+ viceme skills doctor
155
+ ```
156
+
157
+ `skills doctor` 会分别校验 CLI 版本、Skill 版本、兼容范围、完整 Bundle 摘要和嵌入内容摘要。安装内容被修改或版本不兼容时会直接失败。
158
+
159
+ ## 认证
160
+
161
+ | 命令 | 用途 |
162
+ |---|---|
163
+ | `viceme auth status` | 查看当前区域是否已认证 |
164
+ | `viceme auth login --no-wait` | 启动设备授权并立即返回 |
165
+ | `viceme auth login --device-code <code>` | 完成之前启动的设备授权 |
166
+ | `viceme auth logout` | 撤销并删除当前区域的凭证 |
167
+
168
+ 令牌只保存在操作系统密钥链中,不会回退到明文存储;登录成功的输出也不会包含访问令牌或刷新令牌。
169
+
170
+ ## 支持的来源
171
+
172
+ ### GitHub 或可信来源平台
173
+
174
+ ```bash
175
+ viceme skill inspect https://github.com/acme/poster-skill
176
+ viceme skill publish --resolution-id <resolution-id> --yes
177
+ ```
178
+
179
+ ### 小红书或 RedSkill 复制口令
180
+
181
+ ```bash
182
+ viceme skill inspect --expression-stdin
183
+ viceme skill publish --resolution-id <resolution-id> --yes
184
+ ```
185
+
186
+ 复制口令属于不可信数据。Viceme 只从中提取定位信息,并通过允许的连接器获取来源;不会执行市场安装文案中的命令。
187
+
188
+ ### 压缩包或本地 Skill 目录
189
+
190
+ ```bash
191
+ viceme skill publish --file ./poster-skill.zip --new-target --target-alias poster --yes
192
+ viceme skill publish --dir ./poster-skill --new-target --target-alias poster --yes
193
+ ```
194
+
195
+ 后续发布新版本时,应先解析现有 Target 并使用乐观并发控制。发生冲突时不能创建新的分享链接:
196
+
197
+ ```bash
198
+ viceme skill target get target_123
199
+ viceme skill publish --file ./poster-skill-v2.zip \
200
+ --target-id target_123 --expected-target-version 4 --yes
201
+ ```
202
+
203
+ ## 命令概览
204
+
205
+ | 命令组 | 用途 |
206
+ |---|---|
207
+ | `viceme install` | 安装持久化启动器、Agent Skill 和区域配置 |
208
+ | `viceme auth` | 启动、完成、检查或撤销设备认证 |
209
+ | `viceme skill inspect` | 固化并检查来源候选,不执行发布 |
210
+ | `viceme skill publish` | 创建或更新具有稳定链接的 Skill Agent 发布 |
211
+ | `viceme skill target` | 解析现有逻辑 Agent Target 及其版本 |
212
+ | `viceme job` | 读取、等待、恢复或取消持久化发布任务 |
213
+ | `viceme skills` | 读取、安装和诊断随包发布的 Agent Skill |
214
+ | `viceme update` | 同时更新 npm 启动器、已校验二进制文件和随包发布的 Skill |
215
+
216
+ 使用 `viceme <command> --help` 查看准确参数。经过发布检查的机器可读命令面存放在 [`skills/viceme/references/command-manifest.json`](skills/viceme/references/command-manifest.json)。
217
+
218
+ ## JSON 输出契约
219
+
220
+ 所有数据命令默认输出稳定的 JSON 信封。
221
+
222
+ 成功结果写入 **stdout**,退出码为 `0`:
223
+
224
+ ```json
225
+ {
226
+ "ok": true,
227
+ "data": {},
228
+ "meta": {
229
+ "cli_version": "0.1.0",
230
+ "skill_version": "0.1.0"
231
+ }
232
+ }
233
+ ```
234
+
235
+ CLI 执行错误写入 **stderr**,退出码非零:
236
+
237
+ ```json
238
+ {
239
+ "ok": false,
240
+ "error": {
241
+ "type": "validation",
242
+ "subtype": "source_required",
243
+ "message": "provide exactly one source argument or --expression-stdin"
244
+ },
245
+ "meta": {
246
+ "cli_version": "0.1.0",
247
+ "skill_version": "0.1.0"
248
+ }
249
+ }
250
+ ```
251
+
252
+ 应根据进程退出码或 `ok == true` 判断命令是否成功。成功读取发布任务时,业务状态仍可能是 `unsupported`、`rejected` 或 `failed`;这时应检查 `data.status`,不能把这些状态当成 CLI 传输失败。
253
+
254
+ | 退出码 | 含义 |
255
+ |---|---|
256
+ | `0` | 命令完成;适用时继续检查返回的业务状态 |
257
+ | `2` | 参数校验失败 |
258
+ | `3` | 认证或授权失败 |
259
+ | `4` | 可重试的网络失败 |
260
+ | `5` | 内部或协议失败 |
261
+ | `6` | 创建发布前被策略拒绝 |
262
+ | `10` | 需要明确确认 |
263
+
264
+ ## 安全与风险控制
265
+
266
+ - **不执行来源内容** — CLI 和编译器不会执行第三方脚本、二进制文件、shell 片段、市场命令或复制口令中的指令。
267
+ - **公开变更需要明确确认** — 发布和取消操作需要 `--yes`;退出码 `10` 表示 Agent 必须向用户取得确认,不能静默重试。
268
+ - **安全预览** — 用户需要检查计划请求时,可以对 inspect 或 publish 使用 `--dry-run`,不会产生网络请求或发布副作用。
269
+ - **凭证隔离** — 凭证保存在操作系统密钥链中,并按区域隔离。
270
+ - **不可变输入** — inspect 会把发布绑定到不可变来源快照,而不是在之后重新读取浮动 URL。
271
+ - **有界等待** — `job wait` 有最大等待时间;超时后返回最新持久化状态,不会取消工作流。
272
+ - **可信分发** — npm 启动器从 GitHub 或 binary 镜像下载与其准确包版本匹配的二进制文件,并在启用前使用 npm 包内置的校验清单验证 SHA-256。
273
+
274
+ ## 诊断与更新
275
+
276
+ ```bash
277
+ viceme skills doctor
278
+ viceme update --check
279
+ viceme update
280
+ ```
281
+
282
+ `viceme update` 会安装一个准确的 npm 包版本,获取对应的已校验 Go 二进制文件,并从同一版本刷新随包发布的 Skill。独立开发版二进制文件不会被静默替换。
283
+
284
+ ## 开发
285
+
286
+ 从源码构建需要 Go 1.23 或更高版本。
287
+
288
+ ```bash
289
+ make build
290
+ make test
291
+ make check
292
+ make skill-check
293
+ make npm-package-check
294
+ make quality-check
295
+ ```
296
+
297
+ 仓库中主要的质量产物包括:
298
+
299
+ - [`skills/viceme/references/command-manifest.json`](skills/viceme/references/command-manifest.json):从 Cobra 命令树生成;
300
+ - [`quality/example-dry-runs.json`](quality/example-dry-runs.json):在没有网络请求的情况下执行文档中的来源路径;
301
+ - [`quality/release-manifest.json`](quality/release-manifest.json):固定 CLI/Skill 兼容范围和内容摘要。
302
+
303
+ `make npm-package-check` 会构建 Go 可执行文件、打包真实 npm tarball,并在相互隔离的临时主目录中运行启动器,不依赖已经发布的 GitHub Release。
304
+
305
+ ## 发布
306
+
307
+ 维护者将普通改动合入 `dev`;不需要手工修改版本、创建 tag、编写 changelog 或执行 `npm publish`。GitHub Actions 持续维护唯一的 `dev -> main` 自动 Release PR。合并该 PR 即授权发布经过审阅的版本、tag、不可变 GitHub Release 产物及 npm trusted publication。
308
+
309
+ 仓库设置、OIDC trusted publishing、恢复和完整性规则参见 [`docs/releasing.md`](docs/releasing.md)。
310
+
311
+ ## 贡献
312
+
313
+ 欢迎提交 Issue 和 Pull Request。公共命令面发生变化时,必须在同一个 PR 中更新生成的命令清单、随包发布的 Skill 示例、质量样例和测试。
package/checksums.txt ADDED
@@ -0,0 +1,6 @@
1
+ d22d97438aa887b238ef6c506ccd19ffe049877754660d00e114069079ca2151 viceme_0.2.1_darwin_amd64
2
+ 2157e6ccdbe76c6404e238fb2d04ea17c79289a1b712bfff42ddc8b1acdb7db6 viceme_0.2.1_darwin_arm64
3
+ 96dc6b38b609639e3111bdbfcc9bf0bcedf3fa6a0c66e3b2b2d769261a6acc84 viceme_0.2.1_linux_amd64
4
+ 7a9b0576ee36e9eab893f84669ca8cb6bd58d4bfc50a58054356c3863f710570 viceme_0.2.1_linux_arm64
5
+ b825703ce462985bf8810b8c386e39b0e50ca6b600f301cec7a6822309e101b2 viceme_0.2.1_windows_amd64.exe
6
+ cea8060b6f1f7f5a1c6bf44b787cd1ca5312c9b1f4e585b0f8bef325d6f748e0 viceme_0.2.1_windows_arm64.exe
package/docs/releasing.md CHANGED
@@ -6,31 +6,61 @@ files, create tags, write changelog entries, or run npm commands locally.
6
6
 
7
7
  ## Normal flow
8
8
 
9
- 1. A non-bot push to `dev` starts `Prepare Release PR`.
10
- 2. `npm/scripts/prepare-release.mjs` finds the newest reachable stable tag and
9
+ 1. Feature and fix PRs can merge into `dev` without starting release
10
+ preparation.
11
+ 2. A maintainer explicitly opens or marks ready a repository-owned `dev` to
12
+ `main` PR. That release intent starts `Prepare Release PR`.
13
+ 3. `npm/scripts/prepare-release.mjs` finds the newest reachable stable tag and
11
14
  reads all unreleased non-merge commits.
12
- 3. Conventional Commits select the next version:
15
+ 4. Conventional Commits select the next version:
13
16
  - a `BREAKING CHANGE` footer or `type!:` selects major;
14
17
  - `feat:` selects minor;
15
18
  - every other releasable change selects patch.
16
- 4. The workflow synchronizes `package.json`, `package-lock.json`, Go build
19
+ 5. The workflow synchronizes `package.json`, `package-lock.json`, Go build
17
20
  metadata, bundled Skill metadata, command manifest, release digests, and
18
21
  `CHANGELOG.md`.
19
- 5. It runs `make check` and `make npm-package-check`, commits the generated
20
- release files to `dev`, and creates or updates one `dev` to `main` PR titled
21
- `chore(release): vX.Y.Z`.
22
- 6. A maintainer reviews and merges that PR using the repository's preferred
23
- merge method.
24
- 7. `Release CLI and npm launcher` tags the exact reviewed PR head, reconnects
25
- the merged `main` commit into `dev` when necessary, reruns the quality gates,
26
- builds six platform binaries and six checksums, creates the GitHub Release,
27
- and publishes the npm launcher.
22
+ 6. It runs `make check` and `make npm-package-check`, creates a short-lived
23
+ installation token for the repository-scoped ViceMe Release GitHub App, and
24
+ commits only the generated files directly to protected `dev`.
25
+ 7. The existing `dev` to `main` PR synchronizes, runs its required quality
26
+ checks, and is updated to `chore(release): vX.Y.Z` with exact run and commit
27
+ evidence. No internal preparation PR is created.
28
+ 8. A maintainer reviews and merges that same Release PR.
29
+ 9. `Release CLI and npm launcher` tags the exact reviewed `dev` head, reruns
30
+ the quality gates, builds six platform binaries and six checksums, creates
31
+ the GitHub Release, bundles those exact checksums into the npm launcher,
32
+ publishes it, and then sends an
33
+ AI-generated release summary to the release notification group in Feishu.
28
34
 
29
35
  ## One-time repository setup
30
36
 
31
- GitHub Actions needs `Read and write permissions` and permission to create pull
32
- requests so the built-in `GITHUB_TOKEN` can update `dev` and maintain the
33
- Release PR. No maintainer PAT is required by these workflows.
37
+ Register a private organization-owned GitHub App named `ViceMe CLI Release Bot`.
38
+ Install it only on `ViceMe-AI/cli` with repository `Contents: read and write`;
39
+ leave every other optional permission disabled. Webhooks and user authorization
40
+ are not required. Configure:
41
+
42
+ - repository variable `RELEASE_APP_ID`: the numeric App ID;
43
+ - repository secret `RELEASE_APP_PRIVATE_KEY`: the complete generated PEM key.
44
+
45
+ Protect `dev` with an active branch ruleset that retains the normal pull request,
46
+ one approving review, four CLI quality checks, deletion protection, and force
47
+ push protection. Add `ViceMe CLI Release Bot` and the organization-admin role to
48
+ the bypass list with `Always allow`; the latter preserves the legacy rule's
49
+ existing `enforce_admins: false` behavior. Do not leave the legacy
50
+ branch-protection rule active beside the ruleset because it cannot recognize the
51
+ ruleset's App bypass.
52
+
53
+ The App installation token is scoped to the current repository and
54
+ `Contents: write`, expires after at most one hour, and is revoked automatically
55
+ when the job finishes. The workflow still stages an explicit allowlist of
56
+ generated files and validates the complete release before pushing. No
57
+ maintainer PAT or Deploy Key is used.
58
+
59
+ The general CLI quality workflow runs for pull requests, not branch pushes. A
60
+ Release App push synchronizes the already-open `dev` to `main` PR, producing one
61
+ set of required checks for the exact prepared commit without duplicate generic
62
+ push and pull-request runs. The synchronize event may run release preparation a
63
+ second time; that run is intentionally idempotent and produces no new commit.
34
64
 
35
65
  Configure npm trusted publishing for:
36
66
 
@@ -44,9 +74,31 @@ its first release, add a repository secret named `NPM_TOKEN` containing a
44
74
  granular automation token limited to `@viceme-ai/cli` publication. Remove that
45
75
  secret after the package exists and trusted publishing is confirmed.
46
76
 
47
- No release-specific environment variables are required. `GITHUB_TOKEN` is
48
- provided by Actions. `NPM_TOKEN` is optional and should only be retained when
49
- the npm account policy requires it.
77
+ The npm tarball contains `checksums.txt`, generated from the six immutable
78
+ GitHub Release checksum assets immediately before publication. The launcher
79
+ uses that bundled manifest as its trust root whether the matching binary is
80
+ transported by GitHub Release, a configured npm registry binary mirror, or the
81
+ public npmmirror binary mirror. Registering `viceme-cli` with cnpmcore enables
82
+ the public `/-/binary/viceme-cli/` mirror; it does not create another npm
83
+ package.
84
+
85
+ `GITHUB_TOKEN` is provided by Actions and is used only to maintain the Release
86
+ PR. `RELEASE_APP_ID` and `RELEASE_APP_PRIVATE_KEY` authenticate the narrowly
87
+ scoped Release App. `NPM_TOKEN` is optional and should only be retained when the
88
+ npm account policy requires it.
89
+
90
+ The release notification job uses the same repository secrets as Viceme Web,
91
+ API, and Engine:
92
+
93
+ - `FEISHU_RELEASE_WEBHOOK`: webhook for the release notification group;
94
+ - `AI_API_KEY`: API key used to generate the release summary;
95
+ - `AI_MODEL`: optional model override, defaulting to `deepseek-chat`;
96
+ - `AI_BASE_URL`: optional OpenAI-compatible endpoint override, defaulting to
97
+ `https://api.deepseek.com/v1`.
98
+
99
+ The notification runs only after the GitHub Release and npm publication have
100
+ both succeeded, so a failed or incomplete release is not announced as
101
+ successful.
50
102
 
51
103
  ## Recovery
52
104
 
@@ -1,10 +1,15 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import { access, chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
4
  import { constants } from "node:fs";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
7
 
7
- const RELEASE_BASE_URL = "https://github.com/ViceMe-AI/cli/releases/download";
8
+ const GITHUB_RELEASE_BASE_URL = "https://github.com/ViceMe-AI/cli/releases/download";
9
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org";
10
+ const NPMMIRROR_BINARY_BASE_URL =
11
+ "https://registry.npmmirror.com/-/binary/viceme-cli";
12
+ const CHECKSUMS_URL = new URL("../../checksums.txt", import.meta.url);
8
13
  const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
9
14
  const GENERATIONS_DIRECTORY = "generations";
10
15
  const GENERATION_PREFIX = "generation-";
@@ -40,14 +45,40 @@ export function releaseAssetName(version, platform, architecture) {
40
45
  return `viceme_${version}_${target.operatingSystem}_${target.architecture}${target.extension}`;
41
46
  }
42
47
 
48
+ export function binaryDownloadURLs({
49
+ packageVersion,
50
+ asset,
51
+ environment = process.env,
52
+ sourceBaseURLs,
53
+ allowInsecureURL = false,
54
+ }) {
55
+ const bases = sourceBaseURLs ?? defaultBinarySourceBaseURLs(environment);
56
+ const urls = [];
57
+ const seen = new Set();
58
+ for (const base of bases) {
59
+ const parsed = parseDownloadBaseURL(base, allowInsecureURL);
60
+ const normalizedBase = parsed.href.replace(/\/$/, "");
61
+ const url = `${normalizedBase}/v${packageVersion}/${asset}`;
62
+ if (!seen.has(url)) {
63
+ seen.add(url);
64
+ urls.push(url);
65
+ }
66
+ }
67
+ if (urls.length === 0) {
68
+ throw new Error("no valid binary download source is configured");
69
+ }
70
+ return urls;
71
+ }
72
+
43
73
  export async function ensureBinary({
44
74
  packageVersion,
45
75
  environment = process.env,
46
76
  platform = process.platform,
47
77
  architecture = process.arch,
48
- fetchImplementation = globalThis.fetch,
49
- releaseBaseURL = RELEASE_BASE_URL,
78
+ downloadImplementation = downloadWithCurl,
79
+ sourceBaseURLs,
50
80
  cacheDirectory,
81
+ checksumsDocument,
51
82
  allowInsecureURL = false,
52
83
  }) {
53
84
  if (environment.VICEME_BINARY_PATH) {
@@ -55,8 +86,8 @@ export async function ensureBinary({
55
86
  await access(overridden, constants.X_OK);
56
87
  return overridden;
57
88
  }
58
- if (typeof fetchImplementation !== "function") {
59
- throw new Error("Node.js fetch support is required");
89
+ if (typeof downloadImplementation !== "function") {
90
+ throw new Error("a binary download implementation is required");
60
91
  }
61
92
  const asset = releaseAssetName(packageVersion, platform, architecture);
62
93
  const root = cacheDirectory ?? defaultCacheDirectory(environment, platform);
@@ -67,18 +98,23 @@ export async function ensureBinary({
67
98
  return cached;
68
99
  }
69
100
  await mkdir(generationsDirectory, { recursive: true, mode: 0o700 });
70
- const versionURL = `${releaseBaseURL}/v${packageVersion}`;
71
- const checksumURL = `${versionURL}/${asset}.sha256`;
72
- const binaryURL = `${versionURL}/${asset}`;
73
- const [checksumDocument, binary] = await Promise.all([
74
- download(checksumURL, fetchImplementation, allowInsecureURL),
75
- download(binaryURL, fetchImplementation, allowInsecureURL),
76
- ]);
77
- const expectedChecksum = parseChecksum(checksumDocument.toString("utf8"));
78
- const actualChecksum = digest(binary);
79
- if (actualChecksum !== expectedChecksum) {
80
- throw new Error(`checksum mismatch for ${asset}`);
81
- }
101
+ const checksumSource =
102
+ checksumsDocument ?? (await readFile(CHECKSUMS_URL, "utf8"));
103
+ const expectedChecksum = checksumForAsset(checksumSource, asset);
104
+ const urls = binaryDownloadURLs({
105
+ packageVersion,
106
+ asset,
107
+ environment,
108
+ sourceBaseURLs,
109
+ allowInsecureURL,
110
+ });
111
+ const binary = await downloadVerifiedBinary({
112
+ asset,
113
+ expectedChecksum,
114
+ urls,
115
+ downloadImplementation,
116
+ allowInsecureURL,
117
+ });
82
118
 
83
119
  // If another cold start finished while this process downloaded, reuse its
84
120
  // complete immutable generation. Duplicate downloads are harmless; shared
@@ -122,6 +158,130 @@ export async function ensureBinary({
122
158
  return installedBinary;
123
159
  }
124
160
 
161
+ function defaultBinarySourceBaseURLs(environment) {
162
+ const sources = [GITHUB_RELEASE_BASE_URL];
163
+ const configuredRegistry =
164
+ environment.npm_config_registry ?? environment.NPM_CONFIG_REGISTRY;
165
+ const registryBinaryBase = customRegistryBinaryBaseURL(configuredRegistry);
166
+ if (registryBinaryBase) {
167
+ sources.push(registryBinaryBase);
168
+ }
169
+ sources.push(NPMMIRROR_BINARY_BASE_URL);
170
+ return sources;
171
+ }
172
+
173
+ function customRegistryBinaryBaseURL(value) {
174
+ if (!value) {
175
+ return undefined;
176
+ }
177
+ let registry;
178
+ try {
179
+ registry = new URL(value);
180
+ } catch {
181
+ return undefined;
182
+ }
183
+ if (
184
+ registry.protocol !== "https:" ||
185
+ registry.username ||
186
+ registry.password ||
187
+ registry.origin === new URL(NPM_REGISTRY_URL).origin
188
+ ) {
189
+ return undefined;
190
+ }
191
+ return `${registry.href.replace(/\/$/, "")}/-/binary/viceme-cli`;
192
+ }
193
+
194
+ function parseDownloadBaseURL(value, allowInsecureURL) {
195
+ let parsed;
196
+ try {
197
+ parsed = new URL(value);
198
+ } catch {
199
+ throw new Error(`invalid binary download source ${value}`);
200
+ }
201
+ if (parsed.username || parsed.password) {
202
+ throw new Error("binary download sources must not contain credentials");
203
+ }
204
+ if (!allowInsecureURL && parsed.protocol !== "https:") {
205
+ throw new Error(`refusing non-HTTPS binary download source ${value}`);
206
+ }
207
+ if (allowInsecureURL && !["http:", "https:"].includes(parsed.protocol)) {
208
+ throw new Error(`unsupported binary download protocol ${parsed.protocol}`);
209
+ }
210
+ return parsed;
211
+ }
212
+
213
+ async function downloadVerifiedBinary({
214
+ asset,
215
+ expectedChecksum,
216
+ urls,
217
+ downloadImplementation,
218
+ allowInsecureURL,
219
+ }) {
220
+ const failures = [];
221
+ for (const url of urls) {
222
+ try {
223
+ const downloaded = await downloadImplementation(url, { allowInsecureURL });
224
+ const binary = Buffer.isBuffer(downloaded) ? downloaded : Buffer.from(downloaded);
225
+ if (digest(binary) !== expectedChecksum) {
226
+ throw new Error(`checksum mismatch for ${asset}`);
227
+ }
228
+ return binary;
229
+ } catch (error) {
230
+ failures.push(`- ${url}: ${errorMessage(error)}`);
231
+ }
232
+ }
233
+ throw new Error(
234
+ `could not download a verified ${asset}; attempted:\n${failures.join("\n")}`,
235
+ );
236
+ }
237
+
238
+ async function downloadWithCurl(url, { allowInsecureURL = false } = {}) {
239
+ const parsed = parseDownloadBaseURL(url, allowInsecureURL);
240
+ const arguments_ = [
241
+ "--fail",
242
+ "--location",
243
+ "--silent",
244
+ "--show-error",
245
+ "--connect-timeout",
246
+ "10",
247
+ "--max-time",
248
+ "120",
249
+ "--max-redirs",
250
+ "5",
251
+ ];
252
+ if (!allowInsecureURL) {
253
+ arguments_.push("--proto", "=https", "--proto-redir", "=https");
254
+ }
255
+ arguments_.push(parsed.href);
256
+ return await new Promise((resolve, reject) => {
257
+ const child = spawn("curl", arguments_, {
258
+ stdio: ["ignore", "pipe", "pipe"],
259
+ windowsHide: true,
260
+ });
261
+ const stdout = [];
262
+ let stderr = "";
263
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
264
+ child.stderr.on("data", (chunk) => {
265
+ stderr += chunk;
266
+ });
267
+ child.on("error", (error) => {
268
+ if (error.code === "ENOENT") {
269
+ reject(new Error("curl is required to download the ViceMe CLI binary"));
270
+ return;
271
+ }
272
+ reject(error);
273
+ });
274
+ child.on("close", (code, signal) => {
275
+ if (code === 0) {
276
+ resolve(Buffer.concat(stdout));
277
+ return;
278
+ }
279
+ const reason = stderr.trim() || `curl exited ${code ?? `on signal ${signal}`}`;
280
+ reject(new Error(reason));
281
+ });
282
+ });
283
+ }
284
+
125
285
  async function findValidGeneration(generationsDirectory, asset) {
126
286
  let entries;
127
287
  try {
@@ -170,22 +330,27 @@ async function cachedBinaryIsValid(binaryPath, checksumPath) {
170
330
  }
171
331
  }
172
332
 
173
- async function download(url, fetchImplementation, allowInsecureURL) {
174
- const parsed = new URL(url);
175
- if (!allowInsecureURL && parsed.protocol !== "https:") {
176
- throw new Error(`refusing non-HTTPS release URL ${url}`);
177
- }
178
- const response = await fetchImplementation(url, {
179
- redirect: "follow",
180
- headers: { "user-agent": "@viceme-ai/cli npm launcher" },
181
- });
182
- if (!response.ok) {
183
- throw new Error(`download failed (${response.status}) for ${url}`);
333
+ function checksumForAsset(document, asset) {
334
+ let checksum;
335
+ for (const line of document.split(/\r?\n/)) {
336
+ if (line.trim() === "") {
337
+ continue;
338
+ }
339
+ const match = line.match(/^([a-fA-F0-9]{64})\s+([^\s]+)$/);
340
+ if (!match) {
341
+ throw new Error("bundled checksum document is invalid");
342
+ }
343
+ if (match[2] === asset) {
344
+ if (checksum) {
345
+ throw new Error(`bundled checksum document contains duplicate ${asset}`);
346
+ }
347
+ checksum = match[1].toLowerCase();
348
+ }
184
349
  }
185
- if (!allowInsecureURL && response.url && new URL(response.url).protocol !== "https:") {
186
- throw new Error(`release download redirected to a non-HTTPS URL`);
350
+ if (!checksum) {
351
+ throw new Error(`bundled checksum document does not contain ${asset}`);
187
352
  }
188
- return Buffer.from(await response.arrayBuffer());
353
+ return checksum;
189
354
  }
190
355
 
191
356
  function parseChecksum(document) {
@@ -199,3 +364,7 @@ function parseChecksum(document) {
199
364
  function digest(buffer) {
200
365
  return createHash("sha256").update(buffer).digest("hex");
201
366
  }
367
+
368
+ function errorMessage(error) {
369
+ return error instanceof Error ? error.message : String(error);
370
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viceme-ai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Install and run the Viceme CLI and Viceme Agent Skill",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,9 @@
9
9
  "files": [
10
10
  "npm/bin/",
11
11
  "npm/lib/",
12
+ "checksums.txt",
12
13
  "README.md",
14
+ "README.zh.md",
13
15
  "CHANGELOG.md",
14
16
  "docs/releasing.md"
15
17
  ],