@workerstack/system 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +319 -2
- package/package.json +48 -5
- package/src/args.ts +67 -0
- package/src/bin.ts +5 -0
- package/src/builtins/agents.ts +73 -0
- package/src/builtins/bootstrap.ts +39 -0
- package/src/builtins/browsers.ts +79 -0
- package/src/builtins/cloudflare.ts +32 -0
- package/src/builtins/database.ts +41 -0
- package/src/builtins/index.ts +41 -0
- package/src/builtins/languages.ts +218 -0
- package/src/builtins/recipes.ts +145 -0
- package/src/builtins/release.ts +133 -0
- package/src/builtins/resolve.ts +80 -0
- package/src/builtins/runtimes.ts +54 -0
- package/src/builtins/types.ts +38 -0
- package/src/commands/check.ts +101 -0
- package/src/commands/env.ts +99 -0
- package/src/commands/install.ts +134 -0
- package/src/commands/list.ts +29 -0
- package/src/commands/sync-pm.ts +53 -0
- package/src/config.ts +123 -0
- package/src/env.ts +103 -0
- package/src/errors.ts +4 -0
- package/src/exec.ts +64 -0
- package/src/graph.ts +33 -0
- package/src/index.ts +21 -0
- package/src/load.ts +43 -0
- package/src/main.ts +99 -0
- package/src/path.ts +172 -0
- package/src/platform.ts +73 -0
- package/src/probe.ts +44 -0
- package/src/runtime.ts +99 -0
- package/src/validate.ts +154 -0
- package/src/version.ts +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lovro Žagar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,320 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @workerstack/system
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Formerly [`@lovrozagar/crossdeps`](https://www.npmjs.com/package/@lovrozagar/crossdeps). Same code and history, new name.
|
|
4
|
+
|
|
5
|
+
Every OS dependency a workerstack repo needs, from one config file. Name a built-in tool and its version, `go: "1.27.2"`, and `system install` installs it on macOS, Linux (apt, dnf, pacman) and Windows, puts it first on PATH, and `system check` verifies the version. Anything outside the catalog keeps the full custom form with your own commands.
|
|
6
|
+
|
|
7
|
+
This README is the full usage contract. Agents: read it end to end before writing a config or running the CLI.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Requirements](#requirements)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Built-in catalog](#built-in-catalog)
|
|
14
|
+
- [Config](#config)
|
|
15
|
+
- [CLI](#cli)
|
|
16
|
+
- [Versions](#versions)
|
|
17
|
+
- [Shell environment](#shell-environment)
|
|
18
|
+
- [Library API](#library-api)
|
|
19
|
+
- [Gotchas](#gotchas)
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
| Need | Detail |
|
|
24
|
+
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| Bun | The CLI is TypeScript with `#!/usr/bin/env bun`. bun is the only supported package manager. |
|
|
26
|
+
| OS | macOS, Linux with apt, dnf or pacman, Windows (PowerShell). |
|
|
27
|
+
| CPU | Built-ins ship x64 and arm64 recipes. Custom deps run anywhere. |
|
|
28
|
+
| sudo | Only OS packages use it (`git`, `curl`, `chromium`, `ffmpeg`, `docker` on Linux, playwright's apt deps). Everything else installs per user. |
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun add -d @workerstack/system
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`system.config.ts`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineConfig } from "@workerstack/system"
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
deps: {
|
|
43
|
+
bun: "1.4.4",
|
|
44
|
+
go: "1.27.2",
|
|
45
|
+
uv: "0.13.1",
|
|
46
|
+
ruff: "0.17.1",
|
|
47
|
+
java: "21",
|
|
48
|
+
ffmpeg: { required: false, version: "latest" },
|
|
49
|
+
"stripe-cli": {
|
|
50
|
+
description: "Stripe CLI",
|
|
51
|
+
os: { macos: "brew install stripe/stripe-cli/stripe", windows: false },
|
|
52
|
+
required: false,
|
|
53
|
+
version: "1.53.0",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bunx system install # everything, prerequisites first
|
|
61
|
+
bunx system check # installed vs pinned, exit 1 when a required dep is missing
|
|
62
|
+
bunx system install go --upgrade
|
|
63
|
+
bunx system list # the built-in catalog
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Built-in catalog
|
|
67
|
+
|
|
68
|
+
A built-in takes only a version. The catalog supplies install commands for every OS and CPU, the version check, PATH entries and prerequisites. `system list` prints this table.
|
|
69
|
+
|
|
70
|
+
| Name | Group | Version | Where | What |
|
|
71
|
+
| -------------------- | ---------- | --------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `brew` | bootstrap | `latest` only | macOS | Homebrew, the macOS package manager. |
|
|
73
|
+
| `choco` | bootstrap | `latest` only | Windows | Chocolatey, the Windows package manager (needs an admin shell). |
|
|
74
|
+
| `bun` | runtimes | exact pin | all | Bun: runtime and the only package manager (its version also pins packageManager). |
|
|
75
|
+
| `deno` | runtimes | exact pin | all | Deno, for the generated TypeScript SDK checks. |
|
|
76
|
+
| `node` | runtimes | exact pin | all | Node.js runtime, for tools that need it (bun stays the package manager). |
|
|
77
|
+
| `dotnet` | languages | exact pin | all | .NET SDK, for the generated C# SDK (dotnet format included). |
|
|
78
|
+
| `go` | languages | exact pin | all | Go toolchain (gofmt included), for the generated Go SDK and CLI. |
|
|
79
|
+
| `google-java-format` | languages | exact pin | all | google-java-format, the generated Java SDK's formatter. Needs `java`. |
|
|
80
|
+
| `gradle` | languages | exact pin | all | Gradle, the generated Java SDK's build. Needs `java`. |
|
|
81
|
+
| `java` | languages | exact pin | all | Eclipse Temurin JDK: a feature major (21) or an exact release (21.0.13+9). |
|
|
82
|
+
| `mypy` | languages | exact pin | all | mypy, the generated Python SDK's type check (uv tool). Needs `uv`. |
|
|
83
|
+
| `python` | languages | exact pin | all | Python, managed by uv (python and python3 shims). Needs `uv`. |
|
|
84
|
+
| `ruff` | languages | exact pin | all | ruff, the generated Python SDK's formatter and linter (uv tool). Needs `uv`. |
|
|
85
|
+
| `rust` | languages | exact pin | all | Rust via rustup (rustfmt and clippy), for the generated Rust SDK. |
|
|
86
|
+
| `uv` | languages | exact pin | all | uv, the Python toolchain manager. |
|
|
87
|
+
| `curl` | release | `latest` only | all | HTTP client the install commands use (the OS package manager's current build). Needs `brew`, `choco`. |
|
|
88
|
+
| `git` | release | `latest` only | all | Version control (the OS package manager's current build). Needs `brew`, `choco`. |
|
|
89
|
+
| `gh` | release | exact pin | all | GitHub CLI, for releases, PRs and the API. |
|
|
90
|
+
| `goreleaser` | release | exact pin | all | Release automation for the generated Go CLI. |
|
|
91
|
+
| `cosign` | release | exact pin | all | Sigstore signing for release artifacts. |
|
|
92
|
+
| `jq` | release | exact pin | all | JSON processor. |
|
|
93
|
+
| `pwsh` | release | exact pin | all | PowerShell 7, for the generated Windows install scripts. |
|
|
94
|
+
| `shellcheck` | release | exact pin | all | Shell script linter, for the generated install scripts. |
|
|
95
|
+
| `cf` | cloudflare | exact pin | all | Cloudflare CLI: deploy, workers types, account operations (bun global, runs on node). Needs `bun`, `node`. |
|
|
96
|
+
| `cloudflared` | cloudflare | exact pin | all | Cloudflare Tunnel client, for remote bindings. |
|
|
97
|
+
| `atlas` | database | exact pin | all | Atlas schema migrations. |
|
|
98
|
+
| `sqld` | database | exact pin | apt, dnf, pacman, macOS | libSQL server, for local database tests (no Windows build). |
|
|
99
|
+
| `chromium` | browsers | `latest` only | apt, dnf, pacman | Chromium, the browser rail fallback (Linux only: brew and choco ship no usable build). |
|
|
100
|
+
| `docker` | browsers | `latest` only | all | Container engine with its daemon (Docker Engine on Linux, Docker Desktop elsewhere). Needs `brew`, `choco`. |
|
|
101
|
+
| `lightpanda` | browsers | `latest` only | apt, dnf, pacman, macOS | Lightpanda, the default headless browser rail (nightly; no Windows build). |
|
|
102
|
+
| `playwright` | browsers | exact pin | all | Playwright's Chromium for browser tests (version = your @playwright/test). Needs `bun`. |
|
|
103
|
+
| `claude` | agents | pin or `latest` | all | Claude Code CLI (native installer; self-updates unless DISABLE_AUTOUPDATER=1). |
|
|
104
|
+
| `codex` | agents | exact pin | all | OpenAI Codex CLI (bun global, runs on node). Needs `bun`, `node`. |
|
|
105
|
+
| `ffmpeg` | agents | `latest` only | all | ffmpeg, for /listen mic capture (the OS package manager's current build). Needs `brew`, `choco`. |
|
|
106
|
+
| `grok` | agents | pin or `latest` | all | Grok Build CLI by xAI (native installer). |
|
|
107
|
+
| `rtk` | agents | exact pin | all | rtk, shell output compression behind the agent Bash hook. |
|
|
108
|
+
|
|
109
|
+
Where things land:
|
|
110
|
+
|
|
111
|
+
- Single binaries: `$HOME/.local/bin`, or `%LOCALAPPDATA%\bin` on Windows (added to the user PATH).
|
|
112
|
+
- Toolchains that need their files together (go, node, java, gradle, dotnet, pwsh): `$HOME/.local/share/<tool>`, or `%LOCALAPPDATA%\Programs\<tool>`.
|
|
113
|
+
- Installers with their own home keep it: bun `~/.bun`, deno `~/.deno`, rust `~/.cargo`, grok `~/.grok`, claude and uv's Python and tools `~/.local/bin`.
|
|
114
|
+
- Global JS CLIs (`cf`, `codex`) install with `bun i -g` into `~/.bun/bin`; their launchers run on `node`.
|
|
115
|
+
|
|
116
|
+
Version policy, checked when the config loads:
|
|
117
|
+
|
|
118
|
+
- **exact pin**: `"latest"` is an error.
|
|
119
|
+
- **`latest` only**: the source cannot pin (OS package managers, nightly builds). Any other version is an error.
|
|
120
|
+
- **pin or `latest`**: either.
|
|
121
|
+
|
|
122
|
+
Prerequisites: a built-in that needs `brew` or `choco` gets it added automatically. Any other missing prerequisite is an error that names the fix:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Invalid system config:
|
|
126
|
+
- deps.ruff: needs uv; add uv: "<version>"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Config
|
|
130
|
+
|
|
131
|
+
### Discovery
|
|
132
|
+
|
|
133
|
+
`system.config.ts`, `system.config.js` or `system.config.mjs` in the working directory, or `--config <path>`. Export the config as `export default defineConfig(...)`, or as any named export holding `deps`.
|
|
134
|
+
|
|
135
|
+
| Field | Type | Default | Meaning |
|
|
136
|
+
| ----------------- | ------ | ---------------- | ---------------------------------------------------------------------------------------- |
|
|
137
|
+
| `deps` | object | required | One entry per dependency, by name |
|
|
138
|
+
| `packageJsonPath` | string | `"package.json"` | The package.json whose `packageManager` follows the bun pin, relative to the config file |
|
|
139
|
+
|
|
140
|
+
### Entry forms
|
|
141
|
+
|
|
142
|
+
A built-in name takes one of two forms:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
go: "1.27.2"
|
|
146
|
+
|
|
147
|
+
go: {
|
|
148
|
+
version: "1.27.2",
|
|
149
|
+
required: false, // default true
|
|
150
|
+
description: "Go for the CLI", // replaces the catalog's
|
|
151
|
+
os: { windows: false }, // merges per OS key over the catalog's commands
|
|
152
|
+
check: { command: "go version" }, // replaces
|
|
153
|
+
env: [{ path: "/opt/go/bin" }], // replaces
|
|
154
|
+
dependsOn: [], // replaces
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Any other name is a custom dep and needs the full form. `defineConfig` checks each key: a version string under a custom name is a type error.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
"stripe-cli": {
|
|
162
|
+
description: "Stripe CLI", // required
|
|
163
|
+
version: "1.53.0", // required; "latest" allowed
|
|
164
|
+
required: false, // required
|
|
165
|
+
os: { // required, at least one key
|
|
166
|
+
all: "curl … {{version}} …", // every OS not listed
|
|
167
|
+
macos: "brew install stripe/stripe-cli/stripe",
|
|
168
|
+
windows: false, // explicitly unavailable
|
|
169
|
+
},
|
|
170
|
+
check: { command: "stripe version", pattern: "(\\d+\\.\\d+\\.\\d+)" }, // default "{{name}} --version"
|
|
171
|
+
dependsOn: ["curl"],
|
|
172
|
+
env: [{ path: "$HOME/.stripe/bin" }],
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`os` keys: `all`, `linux-apt`, `linux-dnf`, `linux-pacman`, `macos`, `windows`. An OS key overrides `all`, `false` makes the dep unavailable there, and a missing key without `all` means unavailable.
|
|
177
|
+
|
|
178
|
+
Templates in `os` commands and `check.command`: `{{name}}`, `{{version}}`, `{{major}}` (the first dot segment) and `{{arch}}` (`arm64`, else `amd64`).
|
|
179
|
+
|
|
180
|
+
`check.pattern` is a regex source whose first capture is the version, for tools whose output the default rule misreads.
|
|
181
|
+
|
|
182
|
+
### `env`
|
|
183
|
+
|
|
184
|
+
| Entry | Writes |
|
|
185
|
+
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
186
|
+
| `{ path: "$HOME/tool/bin" }` | The directory first on PATH, so the pinned binary wins |
|
|
187
|
+
| `{ key: "GOPATH", value: "$HOME/go" }` | The variable |
|
|
188
|
+
| `{ key: "ANDROID_HOME", detect: ["$HOME/Android/Sdk", "/opt/android-sdk"], fallback: "$HOME/Android/Sdk" }` | The first `detect` path that exists, else `fallback`. Nothing when neither applies |
|
|
189
|
+
|
|
190
|
+
Values are written as-is; the shell expands `$HOME`. `detect` paths expand `$VAR`, `${VAR}` and `%VAR%` from the environment to test for existence.
|
|
191
|
+
|
|
192
|
+
### Validation
|
|
193
|
+
|
|
194
|
+
The config is validated before anything installs. Every problem is listed with its path:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Invalid system config:
|
|
198
|
+
- deps.go.verison: unknown key (allowed: check, dependsOn, description, env, os, required, version)
|
|
199
|
+
- deps.tool.os: must name at least one OS key or all
|
|
200
|
+
- deps.tool.env[0]: appendToPath is gone; use { path: "/opt/tool/bin" } (it goes first on PATH)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A `dependsOn` name missing from the config and a dependency cycle (`dependency cycle: a → b → a`) are errors too.
|
|
204
|
+
|
|
205
|
+
## CLI
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
system <command> [args] [flags]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
| Command | Does |
|
|
212
|
+
| ---------------- | ----------------------------------------------------------------------------------- |
|
|
213
|
+
| `install` | Install every dep for this OS, prerequisites first |
|
|
214
|
+
| `install <name>` | Install one dep |
|
|
215
|
+
| `check` | Report installed vs pinned for every dep, plus `packageManager` against the bun pin |
|
|
216
|
+
| `check <name>` | Report one dep |
|
|
217
|
+
| `env` | Write every dep's env block into the shell rc files |
|
|
218
|
+
| `sync-pm` | Set package.json `packageManager` to `bun@<bun pin>`, keeping its formatting |
|
|
219
|
+
| `list` | Print the built-in catalog |
|
|
220
|
+
|
|
221
|
+
| Flag | Meaning |
|
|
222
|
+
| ----------------- | ------------------------------------------------------------------------------------- |
|
|
223
|
+
| `--config <path>` | Config file instead of discovery |
|
|
224
|
+
| `--os <target>` | Use another OS's command set (or set `WORKERSTACK_SYSTEM_OS`). For dry-runs and tests |
|
|
225
|
+
| `--dry-run` | Print the install commands without running them |
|
|
226
|
+
| `--upgrade` | Reinstall a dep whose installed version does not match |
|
|
227
|
+
| `--here` | `check` with this process's PATH instead of a new interactive shell's |
|
|
228
|
+
|
|
229
|
+
`install` skips a dep already at the pinned version. A present but mismatched dep is skipped with a `run: system install <name> --upgrade` hint unless `--upgrade` is set. After a successful install, the dep's env block is written, and a warning appears if another binary still wins on PATH.
|
|
230
|
+
|
|
231
|
+
`check` reads PATH from a new interactive shell (`bash -ic`, `zsh -lic`, Windows PowerShell with its profile), so it sees what a fresh terminal sees, including blocks `install` just wrote. If that shell cannot start, it falls back to this process's PATH with one warning. A check command that runs longer than 30 seconds is killed and reported.
|
|
232
|
+
|
|
233
|
+
Exit codes:
|
|
234
|
+
|
|
235
|
+
| Command | 0 | 1 |
|
|
236
|
+
| --------- | ---------------------------------------------------- | ---------------------------------------------------- |
|
|
237
|
+
| `install` | every required dep installed, skipped or unavailable | a required dep failed |
|
|
238
|
+
| `check` | no required dep missing (mismatches only warn) | a required dep is missing |
|
|
239
|
+
| `env` | every block written | a dep's variables could not be resolved |
|
|
240
|
+
| `sync-pm` | updated, already correct, or skipped | package.json could not be read or written |
|
|
241
|
+
| any | | bad flag, unknown command, missing or invalid config |
|
|
242
|
+
|
|
243
|
+
## Versions
|
|
244
|
+
|
|
245
|
+
The installed version is the first `N.N…` in the check output, or `check.pattern`'s capture. It matches the pin when:
|
|
246
|
+
|
|
247
|
+
- the pin is `latest`;
|
|
248
|
+
- the pin's dot segments prefix the installed ones: `22` matches `22.20.0`, and `1.27.1` matches `1.27.1` but never `1.27.10`;
|
|
249
|
+
- a prerelease (`-beta.12`) is exact on both sides;
|
|
250
|
+
- build metadata (`+9`) on the installed side is ignored unless the pin has some, so `java: "21"` matches `21.0.13+9`.
|
|
251
|
+
|
|
252
|
+
## Shell environment
|
|
253
|
+
|
|
254
|
+
`env` and `install` write one managed block per dep:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# go environment (managed by @workerstack/system)
|
|
258
|
+
export PATH="$HOME/.local/share/go/bin:$PATH"
|
|
259
|
+
export PATH="$HOME/go/bin:$PATH"
|
|
260
|
+
# end go environment
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Shell | File |
|
|
264
|
+
| ---------- | --------------------------------------------------------------------------------------- |
|
|
265
|
+
| bash | `~/.bashrc` |
|
|
266
|
+
| zsh | `~/.zshrc` |
|
|
267
|
+
| fish | `$XDG_CONFIG_HOME/fish/conf.d/workerstack-system.fish` (default `~/.config/fish/...`) |
|
|
268
|
+
| PowerShell | both `Documents\PowerShell\` (pwsh 7) and `Documents\WindowsPowerShell\` (5.1) profiles |
|
|
269
|
+
|
|
270
|
+
Rewriting a block replaces it in place, and re-running is idempotent. A block written by `crossdeps` (`(managed by crossdeps)`) is replaced, never duplicated.
|
|
271
|
+
|
|
272
|
+
## Library API
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import {
|
|
276
|
+
BUILTINS,
|
|
277
|
+
BUILTIN_GROUPS,
|
|
278
|
+
BUILTIN_NAMES, // the catalog
|
|
279
|
+
resolveDeps, // entries → full SystemDepConfig for { arch, os }
|
|
280
|
+
validateConfig, // throws ConfigError listing every problem
|
|
281
|
+
defineConfig,
|
|
282
|
+
resolveOsCommand,
|
|
283
|
+
resolveCheckCommand,
|
|
284
|
+
interpolate,
|
|
285
|
+
sortByDependencies, // install order; throws on cycles and unknown names
|
|
286
|
+
versionsMatch,
|
|
287
|
+
parseVersion,
|
|
288
|
+
detectOs,
|
|
289
|
+
detectOsFromPlatform,
|
|
290
|
+
detectLinuxDistro,
|
|
291
|
+
parseOsTarget,
|
|
292
|
+
OS_TARGETS,
|
|
293
|
+
whichBinary,
|
|
294
|
+
commandExists,
|
|
295
|
+
ConfigError,
|
|
296
|
+
} from "@workerstack/system"
|
|
297
|
+
|
|
298
|
+
const deps = resolveDeps(config.deps, { arch: process.arch, os: detectOs() }, BUILTINS)
|
|
299
|
+
resolveOsCommand("go", deps.go!, "macos")
|
|
300
|
+
// tmp="$(mktemp -d)" && curl -fsSL "https://go.dev/dl/go1.27.2.darwin-arm64.tar.gz" ...
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Types: `SystemConfig`, `DepEntry`, `SystemDepConfig`, `OsCommands`, `OsTarget`, `EnvVar`, `BuiltinDep`, `BuiltinOverride`, `BuiltinName`, `RecipeContext`, `Arch`, `RawEntry`.
|
|
304
|
+
|
|
305
|
+
## Gotchas
|
|
306
|
+
|
|
307
|
+
- `choco` and the Windows `choco install` recipes need an admin shell.
|
|
308
|
+
- `docker` on Linux adds you to the `docker` group. Log out and back in before using it without sudo.
|
|
309
|
+
- `playwright` installs OS libraries only on apt (`--with-deps`); on dnf, pacman, macOS and Windows it installs the browser alone.
|
|
310
|
+
- `claude` and `grok` update themselves. Pin them only when you need a fixed version, and set `DISABLE_AUTOUPDATER=1` for claude.
|
|
311
|
+
- `chromium` is Linux-only, and on Ubuntu apt hands it to snap.
|
|
312
|
+
- A new terminal (or `source` of the rc file) picks up PATH changes; `system check` already reads a new shell's PATH.
|
|
313
|
+
|
|
314
|
+
## Changelog
|
|
315
|
+
|
|
316
|
+
[CHANGELOG.md](./CHANGELOG.md)
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@workerstack/system",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Every OS dependency workerstack needs, installed by one command. Define deps in system.config.ts, run system install.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"cross-platform",
|
|
8
|
+
"dependencies",
|
|
9
|
+
"installer",
|
|
10
|
+
"system"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/workerstack/system#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/workerstack/system/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "workerstack",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/workerstack/system.git"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"system": "./src/bin.ts"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "vitest run --coverage",
|
|
38
|
+
"test:unit": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"type:check": "tsc --noEmit -p tsconfig.src.json && tsc --noEmit",
|
|
41
|
+
"type:check:tests": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "catalog:",
|
|
45
|
+
"@vitest/coverage-v8": "catalog:",
|
|
46
|
+
"typescript": "catalog:",
|
|
47
|
+
"vitest": "catalog:"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/args.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing. Pure: returns the parsed invocation or an error message, never exits.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type OsTarget, OS_TARGETS } from "./config.ts"
|
|
6
|
+
|
|
7
|
+
export interface Invocation {
|
|
8
|
+
command: string | undefined
|
|
9
|
+
config: string | undefined
|
|
10
|
+
dryRun: boolean
|
|
11
|
+
here: boolean
|
|
12
|
+
os: OsTarget | undefined
|
|
13
|
+
target: string | undefined
|
|
14
|
+
upgrade: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ParseResult = { ok: true; value: Invocation } | { error: string; ok: false }
|
|
18
|
+
|
|
19
|
+
const VALUE_FLAGS = { "--config": "a path", "--os": "a target" } as const
|
|
20
|
+
const BOOL_FLAGS = ["--dry-run", "--here", "--upgrade"] as const
|
|
21
|
+
|
|
22
|
+
function isOsTarget(value: string): value is OsTarget {
|
|
23
|
+
return (OS_TARGETS as readonly string[]).includes(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseArgs(argv: readonly string[]): ParseResult {
|
|
27
|
+
const values: Partial<Record<keyof typeof VALUE_FLAGS, string>> = {}
|
|
28
|
+
const bools = new Set<string>()
|
|
29
|
+
const positional: string[] = []
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const arg = argv[i] as string
|
|
33
|
+
if (arg in VALUE_FLAGS) {
|
|
34
|
+
const flag = arg as keyof typeof VALUE_FLAGS
|
|
35
|
+
const value = argv[i + 1]
|
|
36
|
+
if (value === undefined || value.startsWith("--")) {
|
|
37
|
+
return { error: `${flag} requires ${VALUE_FLAGS[flag]} argument`, ok: false }
|
|
38
|
+
}
|
|
39
|
+
values[flag] = value
|
|
40
|
+
i++
|
|
41
|
+
} else if ((BOOL_FLAGS as readonly string[]).includes(arg)) {
|
|
42
|
+
bools.add(arg)
|
|
43
|
+
} else if (arg.startsWith("--")) {
|
|
44
|
+
return { error: `Unknown flag: ${arg}`, ok: false }
|
|
45
|
+
} else {
|
|
46
|
+
positional.push(arg)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const os = values["--os"]
|
|
51
|
+
if (os !== undefined && !isOsTarget(os)) {
|
|
52
|
+
return { error: `Unknown OS target: ${os}. Expected one of: ${OS_TARGETS.join(", ")}`, ok: false }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
value: {
|
|
58
|
+
command: positional[0],
|
|
59
|
+
config: values["--config"],
|
|
60
|
+
dryRun: bools.has("--dry-run"),
|
|
61
|
+
here: bools.has("--here"),
|
|
62
|
+
os,
|
|
63
|
+
target: positional[1],
|
|
64
|
+
upgrade: bools.has("--upgrade"),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tooling: claude, codex, grok, rtk, ffmpeg.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { archive, bunGlobal, LOCAL_BIN, localBinEnv, pick, ps, unix, winArchive } from "./recipes.ts"
|
|
6
|
+
import type { BuiltinDep } from "./types.ts"
|
|
7
|
+
|
|
8
|
+
/** The installer's version argument: none for latest, so it takes its default channel. */
|
|
9
|
+
function versionArg(version: string, flag = ""): string {
|
|
10
|
+
return version === "latest" ? "" : ` ${flag}${version}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const AGENTS = {
|
|
14
|
+
claude: {
|
|
15
|
+
description: "Claude Code CLI (native installer; self-updates unless DISABLE_AUTOUPDATER=1)",
|
|
16
|
+
env: (os) => [{ path: os === "windows" ? "$HOME\\.local\\bin" : LOCAL_BIN }],
|
|
17
|
+
os: ({ version }) => ({
|
|
18
|
+
...unix(
|
|
19
|
+
`curl -fsSL https://claude.ai/install.sh | bash -s${versionArg(version)}`,
|
|
20
|
+
`curl -fsSL https://claude.ai/install.sh | bash -s${versionArg(version)}`,
|
|
21
|
+
),
|
|
22
|
+
windows: ps(`& ([scriptblock]::Create((irm https://claude.ai/install.ps1)))${versionArg(version)}`),
|
|
23
|
+
}),
|
|
24
|
+
versions: "any",
|
|
25
|
+
},
|
|
26
|
+
codex: {
|
|
27
|
+
/* installed by bun; its launcher is a node script */
|
|
28
|
+
dependsOn: ["bun", "node"],
|
|
29
|
+
description: "OpenAI Codex CLI (bun global, runs on node)",
|
|
30
|
+
os: ({ version }) => bunGlobal("@openai/codex", version),
|
|
31
|
+
versions: "pinned",
|
|
32
|
+
},
|
|
33
|
+
ffmpeg: {
|
|
34
|
+
check: { command: "ffmpeg -version" },
|
|
35
|
+
dependsOn: ["brew", "choco"],
|
|
36
|
+
description: "ffmpeg, for /listen mic capture (the OS package manager's current build)",
|
|
37
|
+
os: () => ({
|
|
38
|
+
"linux-apt": "sudo apt-get install -y ffmpeg",
|
|
39
|
+
"linux-dnf": "sudo dnf install -y ffmpeg-free",
|
|
40
|
+
"linux-pacman": "sudo pacman -S --noconfirm ffmpeg",
|
|
41
|
+
macos: "brew install ffmpeg",
|
|
42
|
+
windows: "choco install ffmpeg -y",
|
|
43
|
+
}),
|
|
44
|
+
versions: "latest",
|
|
45
|
+
},
|
|
46
|
+
grok: {
|
|
47
|
+
description: "Grok Build CLI by xAI (native installer)",
|
|
48
|
+
env: (os) => [{ path: os === "windows" ? "$HOME\\.grok\\bin" : "$HOME/.grok/bin" }],
|
|
49
|
+
os: ({ version }) => ({
|
|
50
|
+
...unix(
|
|
51
|
+
`curl -fsSL https://x.ai/cli/install.sh | bash -s${versionArg(version)}`,
|
|
52
|
+
`curl -fsSL https://x.ai/cli/install.sh | bash -s${versionArg(version)}`,
|
|
53
|
+
),
|
|
54
|
+
windows: ps(`& ([scriptblock]::Create((irm https://x.ai/cli/install.ps1)))${versionArg(version, "-Version ")}`),
|
|
55
|
+
}),
|
|
56
|
+
versions: "any",
|
|
57
|
+
},
|
|
58
|
+
rtk: {
|
|
59
|
+
description: "rtk, shell output compression behind the agent Bash hook",
|
|
60
|
+
env: localBinEnv,
|
|
61
|
+
os: ({ arch, version }) => {
|
|
62
|
+
const base = `https://github.com/rtk-ai/rtk/releases/download/v${version}/rtk`
|
|
63
|
+
/* x64 Linux ships a static musl build; arm64 Linux only a glibc one */
|
|
64
|
+
const linux = pick(arch, { arm64: "aarch64-unknown-linux-gnu", x64: "x86_64-unknown-linux-musl" })
|
|
65
|
+
const mac = pick(arch, { arm64: "aarch64-apple-darwin", x64: "x86_64-apple-darwin" })
|
|
66
|
+
return {
|
|
67
|
+
...unix(archive(`${base}-${linux}.tar.gz`, "rtk", "rtk"), archive(`${base}-${mac}.tar.gz`, "rtk", "rtk")),
|
|
68
|
+
windows: winArchive(`${base}-x86_64-pc-windows-msvc.zip`, "rtk.exe", "rtk"),
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
versions: "pinned",
|
|
72
|
+
},
|
|
73
|
+
} satisfies Record<string, BuiltinDep>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-manager bootstraps other recipes depend on: Homebrew on macOS, Chocolatey on Windows.
|
|
3
|
+
* Both are `latest`-only and added automatically when a dep needs them.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ps } from "./recipes.ts"
|
|
7
|
+
import type { BuiltinDep } from "./types.ts"
|
|
8
|
+
|
|
9
|
+
export const BOOTSTRAP = {
|
|
10
|
+
brew: {
|
|
11
|
+
description: "Homebrew, the macOS package manager",
|
|
12
|
+
/* arm64 Macs install to /opt/homebrew, which is not on the default PATH */
|
|
13
|
+
env: (os) => (os === "macos" ? [{ path: "/opt/homebrew/bin" }] : []),
|
|
14
|
+
os: () => ({
|
|
15
|
+
"linux-apt": false,
|
|
16
|
+
"linux-dnf": false,
|
|
17
|
+
"linux-pacman": false,
|
|
18
|
+
macos:
|
|
19
|
+
'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
20
|
+
windows: false,
|
|
21
|
+
}),
|
|
22
|
+
versions: "latest",
|
|
23
|
+
},
|
|
24
|
+
choco: {
|
|
25
|
+
description: "Chocolatey, the Windows package manager (needs an admin shell)",
|
|
26
|
+
os: () => ({
|
|
27
|
+
"linux-apt": false,
|
|
28
|
+
"linux-dnf": false,
|
|
29
|
+
"linux-pacman": false,
|
|
30
|
+
macos: false,
|
|
31
|
+
windows: ps(
|
|
32
|
+
"Set-ExecutionPolicy Bypass -Scope Process -Force",
|
|
33
|
+
"[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072",
|
|
34
|
+
"iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))",
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
versions: "latest",
|
|
38
|
+
},
|
|
39
|
+
} satisfies Record<string, BuiltinDep>
|