@viceme-ai/cli 0.2.0 → 0.3.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/CHANGELOG.md +16 -0
- package/README.md +25 -12
- package/README.zh.md +25 -12
- package/checksums.txt +6 -0
- package/docs/releasing.md +10 -1
- package/npm/lib/installer.mjs +200 -31
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-07-20
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add profile management (`fec286e`)
|
|
8
|
+
|
|
9
|
+
## [0.2.1] - 2026-07-20
|
|
10
|
+
|
|
11
|
+
### Fixes
|
|
12
|
+
|
|
13
|
+
- add verified binary mirror fallback (`efb0d83`)
|
|
14
|
+
|
|
15
|
+
### Other Changes
|
|
16
|
+
|
|
17
|
+
- add Feishu pull request notifications (`b6174b1`)
|
|
18
|
+
|
|
3
19
|
## [0.2.0] - 2026-07-19
|
|
4
20
|
|
|
5
21
|
### Features
|
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
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
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)
|
|
11
|
+
[Install](#installation--quick-start) · [AI Agent Skills](#agent-skills) · [Auth](#authentication) · [Regions & profiles](#regions--profiles) · [Commands](#command-overview) · [Output contract](#json-output-contract) · [Security](#security-and-risk-controls) · [Development](#development)
|
|
12
12
|
|
|
13
|
-
> **Rollout status:** the Core publication transport and stable-link path are implemented
|
|
13
|
+
> **Rollout status:** the Core publication transport and stable-link path are implemented. 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
14
|
|
|
15
15
|
## Why Viceme CLI?
|
|
16
16
|
|
|
@@ -50,7 +50,7 @@ npm install --global @viceme-ai/cli
|
|
|
50
50
|
viceme install
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Both methods persist the npm launcher, download the matching checksum-verified Go binary, and install the bundled Viceme Agent Skill. They default to the China service. For the international service:
|
|
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
54
|
|
|
55
55
|
```bash
|
|
56
56
|
npx --yes @viceme-ai/cli@latest install --region global
|
|
@@ -103,6 +103,7 @@ viceme auth login --device-code <device-code>
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
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
|
+
For a non-default profile, pass the same global `--profile <name>` when starting and continuing device login; the start result reports the effective `profile` and `region`.
|
|
106
107
|
|
|
107
108
|
**Step 4 — Verify**
|
|
108
109
|
|
|
@@ -122,18 +123,29 @@ viceme skill inspect https://github.com/acme/poster-skill
|
|
|
122
123
|
|
|
123
124
|
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
|
|
|
125
|
-
## Regions
|
|
126
|
+
## Regions & Profiles
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
Each profile selects one Viceme region:
|
|
128
129
|
|
|
129
130
|
| Region | Install command | API endpoint |
|
|
130
131
|
|---|---|---|
|
|
131
132
|
| China | `viceme install` | `https://api.viceme.cn` |
|
|
132
133
|
| International | `viceme install --region global` | `https://api.viceme.ai` |
|
|
133
134
|
|
|
134
|
-
The
|
|
135
|
+
The first install creates the `default` profile. Non-sensitive configuration is stored in `~/.viceme-cli/config.json`; access tokens remain exclusively in the operating-system keychain. Credentials are isolated by both profile and region.
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
```bash
|
|
138
|
+
viceme profile list
|
|
139
|
+
viceme profile add --name work --region global --use
|
|
140
|
+
viceme profile use default
|
|
141
|
+
viceme --profile work auth status
|
|
142
|
+
viceme profile rename work company
|
|
143
|
+
viceme profile remove company
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`profile use` changes the persistent active profile; the global `--profile` flag overrides only one command. AI Agents must not switch or remove profiles unless the user explicitly requests it.
|
|
147
|
+
|
|
148
|
+
`VICEME_CLI_CONFIG_DIR` can override the config root. Local API development still uses the process-only `VICEME_API_BASE_URL`; it is never persisted in a profile.
|
|
137
149
|
|
|
138
150
|
## Agent Skills
|
|
139
151
|
|
|
@@ -160,10 +172,10 @@ viceme skills doctor
|
|
|
160
172
|
|
|
161
173
|
| Command | Purpose |
|
|
162
174
|
|---|---|
|
|
163
|
-
| `viceme auth status` | Show whether the current
|
|
175
|
+
| `viceme auth status` | Show whether the current profile is authenticated |
|
|
164
176
|
| `viceme auth login --no-wait` | Start device authorization and return immediately |
|
|
165
177
|
| `viceme auth login --device-code <code>` | Complete a previously started authorization |
|
|
166
|
-
| `viceme auth logout` | Revoke and remove the current
|
|
178
|
+
| `viceme auth logout` | Revoke and remove the current profile credential |
|
|
167
179
|
|
|
168
180
|
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
181
|
|
|
@@ -204,8 +216,9 @@ viceme skill publish --file ./poster-skill-v2.zip \
|
|
|
204
216
|
|
|
205
217
|
| Command group | Purpose |
|
|
206
218
|
|---|---|
|
|
207
|
-
| `viceme install` | Install the persistent launcher, Agent Skill, and
|
|
219
|
+
| `viceme install` | Install the persistent launcher, Agent Skill, and default profile |
|
|
208
220
|
| `viceme auth` | Start, complete, inspect, or revoke device authentication |
|
|
221
|
+
| `viceme profile` | Add, list, switch, rename, or remove local profiles |
|
|
209
222
|
| `viceme skill inspect` | Freeze and inspect a source candidate without publishing |
|
|
210
223
|
| `viceme skill publish` | Create or update a stable Skill Agent publication |
|
|
211
224
|
| `viceme skill target` | Resolve existing logical Agent Targets and versions |
|
|
@@ -266,10 +279,10 @@ Determine command success from the process exit code or `ok == true`. A successf
|
|
|
266
279
|
- **No source execution** — the CLI and compiler do not execute third-party scripts, binaries, shell fragments, marketplace commands, or copied instructions.
|
|
267
280
|
- **Explicit public mutation** — publishing and cancellation require `--yes`; exit code `10` means the Agent must obtain confirmation, not silently retry.
|
|
268
281
|
- **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.
|
|
282
|
+
- **Credential isolation** — credentials stay in the OS keychain and are namespaced by profile and region.
|
|
270
283
|
- **Immutable inputs** — inspection binds publication to an immutable source snapshot rather than re-reading a floating URL later.
|
|
271
284
|
- **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 and verifies
|
|
285
|
+
- **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
286
|
|
|
274
287
|
## Diagnose and Update
|
|
275
288
|
|
package/README.zh.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
Viceme 官方命令行客户端与 Agent Skill,用于将外部 Skill 发布为稳定、可分享的 Viceme Agent。它面向 Codex、Claude Code 等 AI 编程工具:Agent Skill 负责理解用户意图,CLI 负责确定性的认证、上传、发布和状态协议。
|
|
10
10
|
|
|
11
|
-
[安装](#安装与快速开始) · [AI Agent Skills](#agent-skills) · [认证](#认证) · [
|
|
11
|
+
[安装](#安装与快速开始) · [AI Agent Skills](#agent-skills) · [认证](#认证) · [区域与-profile](#区域与-profile) · [命令](#命令概览) · [输出契约](#json-输出契约) · [安全](#安全与风险控制) · [开发](#开发)
|
|
12
12
|
|
|
13
|
-
> **开放状态:** Core
|
|
13
|
+
> **开放状态:** Core 发布传输和稳定链接链路已经实现。正式对外开放仍取决于精确 Candidate 预览、试运行和结果确认门的完成。当前 `--yes` 只确认发起发布请求,并不代表用户已经审阅最终 Candidate。
|
|
14
14
|
|
|
15
15
|
## 为什么选择 Viceme CLI?
|
|
16
16
|
|
|
@@ -50,7 +50,7 @@ npm install --global @viceme-ai/cli
|
|
|
50
50
|
viceme install
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
两种方式都会持久化 npm 启动器、下载匹配且通过校验和验证的 Go 二进制文件,并安装随包发布的 Viceme Agent Skill
|
|
53
|
+
两种方式都会持久化 npm 启动器、下载匹配且通过校验和验证的 Go 二进制文件,并安装随包发布的 Viceme Agent Skill。二进制下载依次尝试 GitHub Release、用户配置的非默认 npm registry 的 `/-/binary/viceme-cli/` 镜像,最后回退到公共 npmmirror binary 镜像。启动器使用系统 `curl`,因此会遵循标准代理环境变量。默认使用中国区服务。使用国际区服务:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
npx --yes @viceme-ai/cli@latest install --region global
|
|
@@ -103,6 +103,7 @@ viceme auth login --device-code <device-code>
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
如果授权仍处于等待状态,应在过期前继续使用同一个 device code。只有原流程已过期时才能重新发起设备登录。
|
|
106
|
+
使用非默认 Profile 时,启动和继续设备登录必须传入同一个全局 `--profile <name>`;启动结果会返回实际的 `profile` 与 `region`。
|
|
106
107
|
|
|
107
108
|
**第 4 步 — 验证**
|
|
108
109
|
|
|
@@ -122,18 +123,29 @@ viceme skill inspect https://github.com/acme/poster-skill
|
|
|
122
123
|
|
|
123
124
|
inspect 是只读操作。后续应按照随包发布的 `viceme` Skill 处理不同来源、Target 选择、用户确认、有界任务等待和结果返回。在上方所述的精确 Candidate 确认门完成前,公开发布流程仍保持关闭。
|
|
124
125
|
|
|
125
|
-
##
|
|
126
|
+
## 区域与 Profile
|
|
126
127
|
|
|
127
|
-
Viceme
|
|
128
|
+
每个 Profile 独立选择一个 Viceme 区域:
|
|
128
129
|
|
|
129
130
|
| 区域 | 安装命令 | API 地址 |
|
|
130
131
|
|---|---|---|
|
|
131
132
|
| 中国区 | `viceme install` | `https://api.viceme.cn` |
|
|
132
133
|
| 国际区 | `viceme install --region global` | `https://api.viceme.ai` |
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
首次安装会创建 `default` Profile。配置保存在 `~/.viceme-cli/config.json`,访问令牌仍只保存在操作系统密钥链中。不同 Profile 和区域的凭证彼此隔离。
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
```bash
|
|
138
|
+
viceme profile list
|
|
139
|
+
viceme profile add --name work --region global --use
|
|
140
|
+
viceme profile use default
|
|
141
|
+
viceme --profile work auth status
|
|
142
|
+
viceme profile rename work company
|
|
143
|
+
viceme profile remove company
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`profile use` 修改持久化的当前 Profile;全局 `--profile` 只覆盖本次命令。不要让 AI Agent 在用户没有明确要求时切换或删除 Profile。
|
|
147
|
+
|
|
148
|
+
可以用 `VICEME_CLI_CONFIG_DIR` 覆盖配置根目录。本地 API 联调仍使用进程环境变量 `VICEME_API_BASE_URL`,不会写入 Profile。
|
|
137
149
|
|
|
138
150
|
## Agent Skills
|
|
139
151
|
|
|
@@ -160,10 +172,10 @@ viceme skills doctor
|
|
|
160
172
|
|
|
161
173
|
| 命令 | 用途 |
|
|
162
174
|
|---|---|
|
|
163
|
-
| `viceme auth status` |
|
|
175
|
+
| `viceme auth status` | 查看当前 Profile 是否已认证 |
|
|
164
176
|
| `viceme auth login --no-wait` | 启动设备授权并立即返回 |
|
|
165
177
|
| `viceme auth login --device-code <code>` | 完成之前启动的设备授权 |
|
|
166
|
-
| `viceme auth logout` |
|
|
178
|
+
| `viceme auth logout` | 撤销并删除当前 Profile 的凭证 |
|
|
167
179
|
|
|
168
180
|
令牌只保存在操作系统密钥链中,不会回退到明文存储;登录成功的输出也不会包含访问令牌或刷新令牌。
|
|
169
181
|
|
|
@@ -204,8 +216,9 @@ viceme skill publish --file ./poster-skill-v2.zip \
|
|
|
204
216
|
|
|
205
217
|
| 命令组 | 用途 |
|
|
206
218
|
|---|---|
|
|
207
|
-
| `viceme install` | 安装持久化启动器、Agent Skill
|
|
219
|
+
| `viceme install` | 安装持久化启动器、Agent Skill 和默认 Profile |
|
|
208
220
|
| `viceme auth` | 启动、完成、检查或撤销设备认证 |
|
|
221
|
+
| `viceme profile` | 新增、列出、切换、重命名或删除本地 Profile |
|
|
209
222
|
| `viceme skill inspect` | 固化并检查来源候选,不执行发布 |
|
|
210
223
|
| `viceme skill publish` | 创建或更新具有稳定链接的 Skill Agent 发布 |
|
|
211
224
|
| `viceme skill target` | 解析现有逻辑 Agent Target 及其版本 |
|
|
@@ -266,10 +279,10 @@ CLI 执行错误写入 **stderr**,退出码非零:
|
|
|
266
279
|
- **不执行来源内容** — CLI 和编译器不会执行第三方脚本、二进制文件、shell 片段、市场命令或复制口令中的指令。
|
|
267
280
|
- **公开变更需要明确确认** — 发布和取消操作需要 `--yes`;退出码 `10` 表示 Agent 必须向用户取得确认,不能静默重试。
|
|
268
281
|
- **安全预览** — 用户需要检查计划请求时,可以对 inspect 或 publish 使用 `--dry-run`,不会产生网络请求或发布副作用。
|
|
269
|
-
- **凭证隔离** —
|
|
282
|
+
- **凭证隔离** — 凭证保存在操作系统密钥链中,并按 Profile 与区域隔离。
|
|
270
283
|
- **不可变输入** — inspect 会把发布绑定到不可变来源快照,而不是在之后重新读取浮动 URL。
|
|
271
284
|
- **有界等待** — `job wait` 有最大等待时间;超时后返回最新持久化状态,不会取消工作流。
|
|
272
|
-
- **可信分发** — npm
|
|
285
|
+
- **可信分发** — npm 启动器从 GitHub 或 binary 镜像下载与其准确包版本匹配的二进制文件,并在启用前使用 npm 包内置的校验清单验证 SHA-256。
|
|
273
286
|
|
|
274
287
|
## 诊断与更新
|
|
275
288
|
|
package/checksums.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
8be3c86b43e3121fde111bc195830bfb055d4a83f3aeb2516d6c232d0668831b viceme_0.3.0_darwin_amd64
|
|
2
|
+
bb4ee6635f2a42820296827a07ccc917b470e3941e2a002ebf78eec5ad02a4e9 viceme_0.3.0_darwin_arm64
|
|
3
|
+
ffcc623f6d14e9145b91a294c3903a121a54b23a0cd1b585f9a37cd16a50bbcf viceme_0.3.0_linux_amd64
|
|
4
|
+
e8635cee232fa604493548e81db75664a7a0315e829bef1d357341ee377e17d3 viceme_0.3.0_linux_arm64
|
|
5
|
+
9e97ccd4834bd458d8334bb0ffe7cc613b1b83fa47d24ccca89a98d34316fc2a viceme_0.3.0_windows_amd64.exe
|
|
6
|
+
4953ef1bd354ac5885103672df9bc125c7e4410a11da0352745b687e09049e97 viceme_0.3.0_windows_arm64.exe
|
package/docs/releasing.md
CHANGED
|
@@ -28,7 +28,8 @@ files, create tags, write changelog entries, or run npm commands locally.
|
|
|
28
28
|
8. A maintainer reviews and merges that same Release PR.
|
|
29
29
|
9. `Release CLI and npm launcher` tags the exact reviewed `dev` head, reruns
|
|
30
30
|
the quality gates, builds six platform binaries and six checksums, creates
|
|
31
|
-
the GitHub Release,
|
|
31
|
+
the GitHub Release, bundles those exact checksums into the npm launcher,
|
|
32
|
+
publishes it, and then sends an
|
|
32
33
|
AI-generated release summary to the release notification group in Feishu.
|
|
33
34
|
|
|
34
35
|
## One-time repository setup
|
|
@@ -73,6 +74,14 @@ its first release, add a repository secret named `NPM_TOKEN` containing a
|
|
|
73
74
|
granular automation token limited to `@viceme-ai/cli` publication. Remove that
|
|
74
75
|
secret after the package exists and trusted publishing is confirmed.
|
|
75
76
|
|
|
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
|
+
|
|
76
85
|
`GITHUB_TOKEN` is provided by Actions and is used only to maintain the Release
|
|
77
86
|
PR. `RELEASE_APP_ID` and `RELEASE_APP_PRIVATE_KEY` authenticate the narrowly
|
|
78
87
|
scoped Release App. `NPM_TOKEN` is optional and should only be retained when the
|
package/npm/lib/installer.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
49
|
-
|
|
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
|
|
59
|
-
throw new Error("
|
|
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
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 (!
|
|
186
|
-
throw new Error(`
|
|
350
|
+
if (!checksum) {
|
|
351
|
+
throw new Error(`bundled checksum document does not contain ${asset}`);
|
|
187
352
|
}
|
|
188
|
-
return
|
|
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.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Install and run the Viceme CLI and Viceme Agent Skill",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"npm/bin/",
|
|
11
11
|
"npm/lib/",
|
|
12
|
+
"checksums.txt",
|
|
12
13
|
"README.md",
|
|
13
14
|
"README.zh.md",
|
|
14
15
|
"CHANGELOG.md",
|