@viceme-ai/cli 0.1.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 ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-07-18
4
+
5
+ ### Features
6
+
7
+ - automate CLI releases (`b1c27a5`)
8
+ - publish the Viceme CLI through npm (`80a45d3`)
9
+ - add skill agent publishing CLI (`00f173c`)
10
+
11
+ ### Fixes
12
+
13
+ - harden release and API transport (`c45db91`)
14
+ - satisfy release workflow shellcheck (`96e8c4a`)
15
+ - record publication admission confirmation (`ed27923`)
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Viceme CLI
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.
4
+
5
+ ## Install
6
+
7
+ Node.js 18.20 or newer is required for the npm launcher.
8
+
9
+ ```bash
10
+ npm install --global @viceme-ai/cli
11
+ viceme install
12
+ ```
13
+
14
+ For a one-shot bootstrap without a prior global install, use
15
+ `npx --yes @viceme-ai/cli@latest install`.
16
+
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.
18
+
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`.
20
+
21
+ ## Authenticate and publish
22
+
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.
24
+
25
+ ```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
30
+ ```
31
+
32
+ Credentials are stored only in the operating-system keychain. There is no plaintext token fallback.
33
+
34
+ ## Diagnose and update
35
+
36
+ ```bash
37
+ viceme skills doctor --json
38
+ viceme update --check --json
39
+ viceme update --json
40
+ ```
41
+
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.
43
+
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.
45
+
46
+ ## Development and quality gates
47
+
48
+ ```bash
49
+ make build
50
+ make test
51
+ make check
52
+ make npm-package-check
53
+ make quality-check
54
+ ```
55
+
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.
57
+
58
+ The checked-in quality artifacts are:
59
+
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.
63
+
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.
65
+
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.
67
+
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.
69
+
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.
@@ -0,0 +1,63 @@
1
+ # Automated CLI releases
2
+
3
+ Viceme CLI uses an automated Release PR as the only normal production release
4
+ gate. Maintainers merge feature and fix PRs into `dev`; they do not edit version
5
+ files, create tags, write changelog entries, or run npm commands locally.
6
+
7
+ ## Normal flow
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
11
+ reads all unreleased non-merge commits.
12
+ 3. Conventional Commits select the next version:
13
+ - a `BREAKING CHANGE` footer or `type!:` selects major;
14
+ - `feat:` selects minor;
15
+ - every other releasable change selects patch.
16
+ 4. The workflow synchronizes `package.json`, `package-lock.json`, Go build
17
+ metadata, bundled Skill metadata, command manifest, release digests, and
18
+ `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.
28
+
29
+ ## One-time repository setup
30
+
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.
34
+
35
+ Configure npm trusted publishing for:
36
+
37
+ - npm package: `@viceme-ai/cli`;
38
+ - GitHub organization/repository: `ViceMe-AI/cli`;
39
+ - workflow file: `.github/workflows/release.yml`.
40
+
41
+ Trusted publishing is the steady-state path and uses GitHub OIDC plus npm
42
+ provenance. If npm does not allow trusted publishing to create the package on
43
+ its first release, add a repository secret named `NPM_TOKEN` containing a
44
+ granular automation token limited to `@viceme-ai/cli` publication. Remove that
45
+ secret after the package exists and trusted publishing is confirmed.
46
+
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.
50
+
51
+ ## Recovery
52
+
53
+ The original merged Release PR workflow run is safe to rerun from GitHub
54
+ Actions. Existing tags must point to the same reviewed commit. Existing GitHub
55
+ Release assets are compared byte-for-byte and never overwritten. Existing npm
56
+ versions must have the same registry integrity as the locally packed artifact;
57
+ otherwise the workflow fails closed. A rerun of an older version cannot move
58
+ the npm `latest` tag behind a newer release.
59
+
60
+ There is deliberately no tag-push or manual-dispatch production trigger. A
61
+ production release can only originate from merging the repository-owned `dev`
62
+ Release PR into `main`; recovery reruns that same authorized event and exact
63
+ reviewed SHA.
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { readFile } from "node:fs/promises";
5
+ import process from "node:process";
6
+
7
+ import { ensureBinary } from "../lib/installer.mjs";
8
+
9
+ export async function main(args = process.argv.slice(2), environment = process.env) {
10
+ const packageDocument = JSON.parse(
11
+ await readFile(new URL("../../package.json", import.meta.url), "utf8"),
12
+ );
13
+ const binary = await ensureBinary({
14
+ packageVersion: packageDocument.version,
15
+ environment,
16
+ });
17
+ const child = spawnSync(binary, args, {
18
+ stdio: "inherit",
19
+ env: {
20
+ ...environment,
21
+ VICEME_INSTALL_METHOD:
22
+ environment.VICEME_INSTALL_METHOD === "npm"
23
+ ? "npm"
24
+ : environment.VICEME_BINARY_PATH
25
+ ? "development"
26
+ : "npm",
27
+ VICEME_NPM_PACKAGE_VERSION: packageDocument.version,
28
+ },
29
+ windowsHide: false,
30
+ });
31
+ if (child.error) {
32
+ throw child.error;
33
+ }
34
+ if (child.signal) {
35
+ return 128;
36
+ }
37
+ return child.status ?? 1;
38
+ }
39
+
40
+ try {
41
+ process.exitCode = await main();
42
+ } catch (error) {
43
+ process.stderr.write(`viceme launcher: ${error.message}\n`);
44
+ process.exitCode = 1;
45
+ }
@@ -0,0 +1,201 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { access, chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import { constants } from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+
7
+ const RELEASE_BASE_URL = "https://github.com/ViceMe-AI/cli/releases/download";
8
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
9
+ const GENERATIONS_DIRECTORY = "generations";
10
+ const GENERATION_PREFIX = "generation-";
11
+ const STAGING_PREFIX = ".staging-";
12
+
13
+ export function releaseTarget(platform = process.platform, architecture = process.arch) {
14
+ const operatingSystems = {
15
+ darwin: "darwin",
16
+ linux: "linux",
17
+ win32: "windows",
18
+ };
19
+ const architectures = {
20
+ arm64: "arm64",
21
+ x64: "amd64",
22
+ };
23
+ const operatingSystem = operatingSystems[platform];
24
+ const goArchitecture = architectures[architecture];
25
+ if (!operatingSystem || !goArchitecture) {
26
+ throw new Error(`unsupported platform ${platform}/${architecture}`);
27
+ }
28
+ return {
29
+ operatingSystem,
30
+ architecture: goArchitecture,
31
+ extension: platform === "win32" ? ".exe" : "",
32
+ };
33
+ }
34
+
35
+ export function releaseAssetName(version, platform, architecture) {
36
+ if (!VERSION_PATTERN.test(version)) {
37
+ throw new Error(`invalid package version ${version}`);
38
+ }
39
+ const target = releaseTarget(platform, architecture);
40
+ return `viceme_${version}_${target.operatingSystem}_${target.architecture}${target.extension}`;
41
+ }
42
+
43
+ export async function ensureBinary({
44
+ packageVersion,
45
+ environment = process.env,
46
+ platform = process.platform,
47
+ architecture = process.arch,
48
+ fetchImplementation = globalThis.fetch,
49
+ releaseBaseURL = RELEASE_BASE_URL,
50
+ cacheDirectory,
51
+ allowInsecureURL = false,
52
+ }) {
53
+ if (environment.VICEME_BINARY_PATH) {
54
+ const overridden = path.resolve(environment.VICEME_BINARY_PATH);
55
+ await access(overridden, constants.X_OK);
56
+ return overridden;
57
+ }
58
+ if (typeof fetchImplementation !== "function") {
59
+ throw new Error("Node.js fetch support is required");
60
+ }
61
+ const asset = releaseAssetName(packageVersion, platform, architecture);
62
+ const root = cacheDirectory ?? defaultCacheDirectory(environment, platform);
63
+ const destinationDirectory = path.join(root, "cli", packageVersion);
64
+ const generationsDirectory = path.join(destinationDirectory, GENERATIONS_DIRECTORY);
65
+ const cached = await findValidGeneration(generationsDirectory, asset);
66
+ if (cached) {
67
+ return cached;
68
+ }
69
+ 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
+ }
82
+
83
+ // If another cold start finished while this process downloaded, reuse its
84
+ // complete immutable generation. Duplicate downloads are harmless; shared
85
+ // partially-written binary/checksum pairs do not exist.
86
+ const publishedByContender = await findValidGeneration(generationsDirectory, asset);
87
+ if (publishedByContender) {
88
+ return publishedByContender;
89
+ }
90
+
91
+ const generationID = `${expectedChecksum.slice(0, 16)}-${randomUUID()}`;
92
+ const stagingDirectory = path.join(
93
+ destinationDirectory,
94
+ `${STAGING_PREFIX}${process.pid}-${generationID}`,
95
+ );
96
+ const generationDirectory = path.join(
97
+ generationsDirectory,
98
+ `${GENERATION_PREFIX}${generationID}`,
99
+ );
100
+ const stagedBinary = path.join(stagingDirectory, asset);
101
+ const stagedChecksum = `${stagedBinary}.sha256`;
102
+ try {
103
+ await mkdir(stagingDirectory, { mode: 0o700 });
104
+ await writeFile(stagedBinary, binary, { mode: 0o700 });
105
+ await chmod(stagedBinary, 0o700);
106
+ await writeFile(stagedChecksum, `${expectedChecksum} ${asset}\n`, { mode: 0o600 });
107
+ if (!(await cachedBinaryIsValid(stagedBinary, stagedChecksum))) {
108
+ throw new Error(`staged binary verification failed for ${asset}`);
109
+ }
110
+
111
+ // Staging and generations share a parent filesystem. Renaming the complete
112
+ // unique directory is the only publication step, and its destination is
113
+ // never replaced or removed by another installer process.
114
+ await rename(stagingDirectory, generationDirectory);
115
+ } finally {
116
+ await rm(stagingDirectory, { recursive: true, force: true });
117
+ }
118
+ const installedBinary = path.join(generationDirectory, asset);
119
+ if (!(await cachedBinaryIsValid(installedBinary, `${installedBinary}.sha256`))) {
120
+ throw new Error(`published binary verification failed for ${asset}`);
121
+ }
122
+ return installedBinary;
123
+ }
124
+
125
+ async function findValidGeneration(generationsDirectory, asset) {
126
+ let entries;
127
+ try {
128
+ entries = await readdir(generationsDirectory, { withFileTypes: true });
129
+ } catch (error) {
130
+ if (error.code === "ENOENT") {
131
+ return undefined;
132
+ }
133
+ throw error;
134
+ }
135
+ for (const entry of entries
136
+ .filter((candidate) => candidate.isDirectory() && candidate.name.startsWith(GENERATION_PREFIX))
137
+ .sort((left, right) => left.name.localeCompare(right.name))) {
138
+ const binaryPath = path.join(generationsDirectory, entry.name, asset);
139
+ if (await cachedBinaryIsValid(binaryPath, `${binaryPath}.sha256`)) {
140
+ return binaryPath;
141
+ }
142
+ }
143
+ return undefined;
144
+ }
145
+
146
+ function defaultCacheDirectory(environment, platform) {
147
+ if (environment.VICEME_CACHE_DIR) {
148
+ return path.resolve(environment.VICEME_CACHE_DIR);
149
+ }
150
+ if (environment.XDG_CACHE_HOME) {
151
+ return path.join(environment.XDG_CACHE_HOME, "viceme");
152
+ }
153
+ if (platform === "win32" && environment.LOCALAPPDATA) {
154
+ return path.join(environment.LOCALAPPDATA, "Viceme", "Cache");
155
+ }
156
+ return path.join(os.homedir(), ".cache", "viceme");
157
+ }
158
+
159
+ async function cachedBinaryIsValid(binaryPath, checksumPath) {
160
+ try {
161
+ const [binary, checksumDocument] = await Promise.all([
162
+ readFile(binaryPath),
163
+ readFile(checksumPath, "utf8"),
164
+ access(binaryPath, constants.X_OK),
165
+ ]);
166
+ const expected = parseChecksum(checksumDocument);
167
+ return digest(binary) === expected;
168
+ } catch {
169
+ return false;
170
+ }
171
+ }
172
+
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}`);
184
+ }
185
+ if (!allowInsecureURL && response.url && new URL(response.url).protocol !== "https:") {
186
+ throw new Error(`release download redirected to a non-HTTPS URL`);
187
+ }
188
+ return Buffer.from(await response.arrayBuffer());
189
+ }
190
+
191
+ function parseChecksum(document) {
192
+ const match = document.trim().match(/^([a-fA-F0-9]{64})(?:\s|$)/);
193
+ if (!match) {
194
+ throw new Error("release checksum document is invalid");
195
+ }
196
+ return match[1].toLowerCase();
197
+ }
198
+
199
+ function digest(buffer) {
200
+ return createHash("sha256").update(buffer).digest("hex");
201
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@viceme-ai/cli",
3
+ "version": "0.1.0",
4
+ "description": "Install and run the Viceme CLI and Viceme Agent Skill",
5
+ "type": "module",
6
+ "bin": {
7
+ "viceme": "npm/bin/viceme.mjs"
8
+ },
9
+ "files": [
10
+ "npm/bin/",
11
+ "npm/lib/",
12
+ "README.md",
13
+ "CHANGELOG.md",
14
+ "docs/releasing.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "node --test npm/test/*.test.mjs",
18
+ "pack:check": "npm pack --dry-run",
19
+ "release:prepare": "node npm/scripts/prepare-release.mjs --fallback-ref origin/main"
20
+ },
21
+ "engines": {
22
+ "node": ">=18.20.0"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/ViceMe-AI/cli.git"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "provenance": true
31
+ },
32
+ "license": "Apache-2.0"
33
+ }