binzo-cli 0.0.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.
Files changed (3) hide show
  1. package/README.md +206 -0
  2. package/install.cjs +82 -0
  3. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # binzo
2
+
3
+ Your tools, straight from the source. Install command-line tools from release downloads and keep them up to date.
4
+
5
+ Install with npm (Node.js 20 or newer; Bun is not required):
6
+
7
+ ```sh
8
+ npm install --global binzo
9
+ ```
10
+
11
+ npm selects the binary package for your OS, architecture, and Linux libc. A one-time install script places that executable at npm’s command path. Running `binzo` then starts the native executable directly, without a Node launcher or wrapper process.
12
+
13
+ The `binzo` package contains the install script, README, and package metadata; each platform package contains its executable, README, and package metadata. Application source code, tests, and development tools are excluded from the published payload.
14
+
15
+ ```sh
16
+ binzo add aaif-goose/goose
17
+ goose
18
+
19
+ binzo install aaif-goose/goose@v1.50.0
20
+ binzo update aaif-goose/goose
21
+ binzo update --all
22
+ binzo list
23
+ ```
24
+
25
+ `add` and `install` are aliases. A reference without a tag uses GitHub’s latest published stable release. `@TAG` selects an exact release tag, including prereleases. **Tags select the initial version; they do not pin updates.** Both update commands move tagged installs to latest. `binzo update owner/repo@TAG` explicitly switches an installed tool to a particular release.
26
+
27
+ ## What happens during installation?
28
+
29
+ 1. Binzo gets the release and its uploaded assets directly from GitHub. No registry configuration or `jq` is needed for GitHub.
30
+ 2. It matches asset **filenames** against the current OS and CPU architecture, including common aliases such as `aarch64`/`arm64` and `x86_64`/`amd64`. Known incompatible platforms are excluded. On musl Linux, known glibc-only downloads are excluded.
31
+ 3. It downloads the selected asset, checks its size and GitHub SHA-256 digest when supplied, and unpacks it into a temporary directory.
32
+ 4. It inspects files for executable headers or a Unix shebang, rather than treating every extensionless file as a binary. Support files remain with the executable; desktop `.app` bundles are not treated as CLI tools.
33
+ 5. It stores the installation under `~/.binzo/packages`, links the executable into `~/.binzo/bin`, and records the registry, repository, release tag, asset, executable, and command in `~/.binzo/installed.json`.
34
+
35
+ Updates prepare the replacement before switching the command. Tracking writes and link replacements use atomic renames, with a recovery journal for interrupted changes. The previous installation is retained until the replacement is committed. A lock prevents concurrent installs from overwriting each other’s tracking records.
36
+
37
+ If an earlier installation stopped unexpectedly, Binzo offers **Recover and continue**. If another installation is still running, choose **Wait and continue automatically**. Binzo checks the recorded process and handles lock cleanup and transaction recovery for you. For scripts, add `--recover` to recover an abandoned lock or `--wait` to wait for the active installation. Neither option terminates a running process.
38
+
39
+ `update --all` continues past individual download/selection failures, reports totals, and exits unsuccessfully if any tool failed. If transaction recovery is blocked, it stops before changing more tools.
40
+
41
+ ### Ambiguous downloads
42
+
43
+ Release naming is not standardized. Binzo recognizes common target triples and aliases, but asks you to choose when several variants match, when the target is unclear, or when an archive has multiple executables. Equivalent compression formats with the same filename stem are collapsed, preferring `.tar.gz`.
44
+
45
+ Your selected asset variant and executable are remembered for updates. If they cannot be identified unambiguously in a new release, Binzo asks again. It does not run downloaded code during discovery.
46
+
47
+ Downloads show a live progress bar with percentage and downloaded/total size in the terminal. When the total size is unavailable, Binzo shows the downloaded byte count instead. CI, redirected output, and JSON mode omit the animated display.
48
+
49
+ For scripts or CI, make ambiguous choices explicit:
50
+
51
+ ```sh
52
+ binzo add owner/repo \
53
+ --asset tool-aarch64-apple-darwin.tar.gz \
54
+ --binary release/bin/tool \
55
+ --name tool
56
+ ```
57
+
58
+ - `--asset`: exact uploaded filename, not a URL or glob.
59
+ - `--binary`: relative path inside the extracted archive, using `/` separators.
60
+ - `--name`: command name (defaults to the executable filename). Existing command names are preserved during updates. One executable is tracked per repository per registry.
61
+ - `--no-interactive`: disables menus. CI, redirected input/output, and JSON mode also disable menus. Errors include available choices and the flags to use.
62
+ - `--json`: newline-delimited JSON events on stdout; errors on stderr. No terminal styling or prompts.
63
+ - `--verbose`: adds executable and tracking paths. `--debug`: includes technical error details.
64
+
65
+ Binzo never replaces an unrelated file in its command directory. If another tool already owns a command name, select a different name. It also reports when a command elsewhere on PATH takes precedence.
66
+
67
+ ### Shell setup
68
+
69
+ Binzo configures the detected shell: zsh, bash, fish, or the Windows user PATH. Unix uses symlinks; Windows uses `.cmd` launchers without requiring administrator symlink permissions.
70
+
71
+ **An installer cannot change its parent terminal’s environment.** If `~/.binzo/bin` is not already on PATH, Binzo shows the exact command to enable it in the current terminal. New terminals use the saved shell configuration. Windows terminals may need to be fully restarted to inherit the changed user PATH.
72
+
73
+ Use `--no-modify-path` to receive instructions without editing shell configuration. Set `BINZO_HOME` to use a different storage location.
74
+
75
+ ### Supported downloads
76
+
77
+ - Standalone executable files
78
+ - `.tar`, `.tar.gz`, `.tgz`, `.zip`
79
+ - `.gz` containing a single executable
80
+ - `.tar.xz`, `.txz`, `.xz` when `xz` is available
81
+ - `.tar.bz2`, `.tbz2`, `.bz2` when `bzip2` is available
82
+
83
+ OS installer packages (`.dmg`, `.pkg`, `.deb`, `.rpm`, `.msi`, etc.), source archives, and metadata are excluded. ZIP symlinks are rejected with guidance to use another format. Native system libraries and runtime dependencies remain the project’s responsibility; consult its release instructions if an installed tool cannot start.
84
+
85
+ ### Private repositories and API limits
86
+
87
+ Set `GH_TOKEN` or `GITHUB_TOKEN` to a GitHub token with read access to the repository. Tokens are not saved in installed-tool tracking and are not forwarded to GitHub’s asset-storage redirects. Public repositories work without authentication, subject to GitHub’s anonymous API limit. Errors explain how to resolve authentication, missing releases, and rate limits.
88
+
89
+ ## Additional registries
90
+
91
+ Release resolution is separate from the shared installation pipeline. `src/providers/types.ts` defines the common release/asset contract. GitHub is a built-in provider; declarative registries use the existing `map_vars`/`lookup`/`jq` definition format through a separate adapter.
92
+
93
+ Add a definition URL or absolute local path in `~/.binzo/config.json` (or `$BINZO_HOME/config.json`):
94
+
95
+ ```json
96
+ {
97
+ "version": "1.0.0",
98
+ "registries": [{ "url": "/absolute/path/to/registry.yaml" }]
99
+ }
100
+ ```
101
+
102
+ JSON and YAML configs are supported, also under `~/.config/binzo`. Remote definitions must use HTTPS. An optional `auth` field authenticates the definition fetch. The shorthand `github:owner/repo:ref` reads `.binzo/registry.yaml` from that repository/ref.
103
+
104
+ Example definition:
105
+
106
+ ```yaml
107
+ sources:
108
+ - name: Example registry
109
+ map_vars:
110
+ name: "{word:org}/{word:repo}"
111
+ lookup:
112
+ versions:
113
+ url: https://registry.example.com/{org}/{repo}/versions
114
+ jq: "[.versions[].tag]"
115
+ assets:
116
+ url: https://registry.example.com/{org}/{repo}/versions/{version}/assets
117
+ jq: "[.assets[] | {name: .filename, url: .download_url, size: .size}]"
118
+ download:
119
+ headers:
120
+ Accept: application/octet-stream
121
+ ```
122
+
123
+ ```sh
124
+ binzo add organization/tool --registry 'Example registry'
125
+ binzo update organization/tool --registry 'Example registry'
126
+ binzo update --all # remembers each tool’s registry
127
+ ```
128
+
129
+ Definitions may contain a single registry object or a `sources` array. Registry names must be unique; `github` is reserved for the built-in provider. Lookup headers belong in each lookup’s `headers` object. Download headers are explicit and separate.
130
+
131
+ The versions expression must return one array of installable tags, **newest first**. Binzo does not assume tags follow semantic versioning. The assets expression returns one array of HTTPS URL strings, or objects with `url`, optional `name`, and optional `size`. `{name}` and `{version}` are built in; `{word:variable}` patterns capture name components for URL placeholders. Substituted values are URL-encoded.
132
+
133
+ Custom definitions require `jq` on PATH; it is launched directly with the expression as an argument, not through shell evaluation. They are an extension point for JSON APIs. Providers needing other authentication, response formats, or latest-version semantics can implement the same typed provider contract without changing matching, installation, or tracking.
134
+
135
+ ## Development
136
+
137
+ Requires Bun 1.3.14 or newer.
138
+
139
+ ```sh
140
+ bun install
141
+ bun run src/index.ts --help
142
+ bun run src/index.ts add aaif-goose/goose --no-modify-path
143
+
144
+ bun test
145
+ bun run typecheck
146
+ bun run lint
147
+ bun run fmt:check
148
+ bun run build
149
+ ./dist/binzo --help
150
+ ```
151
+
152
+ Unit tests cover platform matching, ambiguity, reference parsing, archive bytes/path handling, registry mappings, and installation transaction recovery. They do not download or execute third-party tools.
153
+
154
+ ## Publishing a release
155
+
156
+ Push a new tag to run `.github/workflows/release.yml`:
157
+
158
+ ```sh
159
+ git tag -a v0.0.1 -m "Release v0.0.1"
160
+ git push origin v0.0.1
161
+ ```
162
+
163
+ The tagged commit must include the workflow. Every newly pushed tag triggers it; ordinary branch pushes and updates to existing tags do not publish releases.
164
+
165
+ The workflow runs the project checks, builds and verifies eight platform binaries, then publishes a GitHub Release with generated release notes:
166
+
167
+ - macOS: x64 and ARM64 (`.tar.gz`)
168
+ - Linux: x64 and ARM64, each for glibc and musl (`.tar.gz`)
169
+ - Windows: x64 and ARM64 (`.zip`)
170
+ - `SHA256SUMS` for the release archives
171
+
172
+ The tag supplies the binary’s version, with a leading `v` removed from numeric versions. For example, `v0.0.1` produces `binzo --version` → `0.0.1`; no separate package-version edit is required. Tags such as `v0.1.0-beta.1` create prereleases.
173
+
174
+ Publishing uses the built-in `GITHUB_TOKEN`; no additional secret is needed. Failed runs can be rerun from GitHub Actions: an unfinished draft is resumed, and an already published release is left intact.
175
+
176
+ ### Publishing to npm manually
177
+
178
+ npm publishing is separate from GitHub Actions. The eight binary packages live in `npm/` as npm workspaces. Keep their versions and the root package’s exact `optionalDependencies` versions aligned with the root `version`.
179
+
180
+ Publish the binary packages first, then the main package:
181
+
182
+ ```sh
183
+ bun install
184
+ npm login
185
+ npm publish --workspaces
186
+ npm publish
187
+ ```
188
+
189
+ Each binary package’s `prepack` command builds its executable directly with Bun before packing or publishing. Publishing uses your local npm authentication and requires access to `binzo` and the eight unscoped `binzo-*` names. For prereleases, add `--tag next` to both publish commands.
190
+
191
+ To change a published binary, increment the version and rebuild; npm versions are immutable. If a publish is interrupted, publish the remaining workspaces individually with `npm publish --workspace <package-name>`.
192
+
193
+ For local packaging work, build just one platform:
194
+
195
+ ```sh
196
+ npm run build --workspace binzo-darwin-arm64
197
+ ```
198
+
199
+ Preview exactly what will be published:
200
+
201
+ ```sh
202
+ npm pack --dry-run
203
+ npm pack --workspace binzo-darwin-arm64 --dry-run
204
+ ```
205
+
206
+ `bun run build:npm` builds all platforms without publishing. npm installation requires optional dependencies and install scripts to be enabled. The install script only places the downloaded binary; it does not compile code or download additional files. If scripts were disabled, enable them and run `npm rebuild binzo` (`npm rebuild --global binzo` for a global installation).
package/install.cjs ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { chmodSync, copyFileSync, existsSync, mkdirSync, renameSync, rmSync } = require("node:fs");
4
+ const { dirname, join } = require("node:path");
5
+
6
+ function platformFor(os, cpu, libc) {
7
+ if (!["darwin", "linux", "win32"].includes(os) || !["x64", "arm64"].includes(cpu))
8
+ return undefined;
9
+
10
+ if (os === "linux") {
11
+ if (libc !== "glibc" && libc !== "musl") return undefined;
12
+
13
+ return `linux-${cpu}-${libc === "glibc" ? "gnu" : "musl"}`;
14
+ }
15
+
16
+ return `${os === "win32" ? "windows" : os}-${cpu}`;
17
+ }
18
+
19
+ function resolveBinary() {
20
+ const libc =
21
+ process.platform === "linux"
22
+ ? process.report.getReport().header.glibcVersionRuntime
23
+ ? "glibc"
24
+ : "musl"
25
+ : undefined;
26
+ const platform = platformFor(process.platform, process.arch, libc);
27
+
28
+ if (!platform) {
29
+ throw new Error(
30
+ `Binzo does not have an npm binary for ${process.platform}/${process.arch}.\nCheck https://github.com/darylcecile/binzo/releases for supported downloads.`,
31
+ );
32
+ }
33
+
34
+ const { version } = require("./package.json");
35
+ const name = `binzo-${platform}`;
36
+ const executable = process.platform === "win32" ? "binzo.exe" : "binzo";
37
+
38
+ try {
39
+ const installed = require(`${name}/package.json`);
40
+ if (installed.version !== version) {
41
+ throw new Error(`Expected ${version}, but found ${installed.version}.`);
42
+ }
43
+
44
+ return require.resolve(`${name}/bin/${executable}`);
45
+ } catch (cause) {
46
+ throw new Error(
47
+ `The Binzo binary package ${name}@${version} is missing or does not match this installation.\nReinstall with optional dependencies enabled:\n\n npm install --global binzo@${version} --include=optional\n\nFor a local project, omit --global. Avoid copying node_modules between different operating systems or CPU architectures.`,
48
+ { cause },
49
+ );
50
+ }
51
+ }
52
+
53
+ function install() {
54
+ // Source checkouts use bun run src/index.ts; their binary packages may not be built yet.
55
+ if (existsSync(join(__dirname, "src", "index.ts"))) return;
56
+
57
+ const source = resolveBinary();
58
+ // npm's bin path is static. Windows needs .exe; Unix executes the native file regardless of suffix.
59
+ const target = join(__dirname, "bin", "binzo.exe");
60
+ const temporary = `${target}.${process.pid}.tmp`;
61
+
62
+ mkdirSync(dirname(target), { recursive: true });
63
+
64
+ try {
65
+ copyFileSync(source, temporary);
66
+ chmodSync(temporary, 0o755);
67
+ renameSync(temporary, target);
68
+ } finally {
69
+ rmSync(temporary, { force: true });
70
+ }
71
+ }
72
+
73
+ module.exports = { platformFor };
74
+
75
+ if (require.main === module) {
76
+ try {
77
+ install();
78
+ } catch (error) {
79
+ console.error(`\nCould not install Binzo: ${error.message}\n`);
80
+ process.exitCode = 1;
81
+ }
82
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "binzo-cli",
3
+ "version": "0.0.1",
4
+ "description": "Your tools, straight from the source.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/darylcecile/binzo.git"
8
+ },
9
+ "bin": {
10
+ "binzo": "bin/binzo.exe"
11
+ },
12
+ "workspaces": [
13
+ "npm/*"
14
+ ],
15
+ "files": [
16
+ "install.cjs"
17
+ ],
18
+ "type": "module",
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "registry": "https://registry.npmjs.org"
22
+ },
23
+ "scripts": {
24
+ "test": "bun test",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "oxlint",
27
+ "lint:fix": "oxlint --fix",
28
+ "fmt": "oxfmt",
29
+ "fmt:check": "oxfmt --check",
30
+ "build": "bun build src/index.ts --compile --outfile dist/binzo",
31
+ "build:npm": "npm run build --workspaces",
32
+ "preinstall": "node install.cjs"
33
+ },
34
+ "devDependencies": {
35
+ "@clack/prompts": "^1.8.1",
36
+ "@commander-js/extra-typings": "^15.0.0",
37
+ "@types/bun": "latest",
38
+ "@types/yauzl": "^3.4.0",
39
+ "chalk": "^6.0.0",
40
+ "commander": "^15.0.0",
41
+ "oxfmt": "^0.67.0",
42
+ "oxlint": "^1.82.0",
43
+ "typescript": "^7.0.2",
44
+ "valibot": "^1.5.0",
45
+ "yauzl": "^3.4.0"
46
+ },
47
+ "optionalDependencies": {
48
+ "binzo-darwin-arm64": "0.0.1",
49
+ "binzo-darwin-x64": "0.0.1",
50
+ "binzo-linux-arm64-gnu": "0.0.1",
51
+ "binzo-linux-arm64-musl": "0.0.1",
52
+ "binzo-linux-x64-gnu": "0.0.1",
53
+ "binzo-linux-x64-musl": "0.0.1",
54
+ "binzo-windows-arm64": "0.0.1",
55
+ "binzo-windows-x64": "0.0.1"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
59
+ }
60
+ }