@clawscarf/cli 0.1.0-alpha.1 → 0.1.0-alpha.3

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/README.md CHANGED
@@ -1,3 +1,112 @@
1
- # ClawScarf operator
1
+ # ClawScarf 🧣
2
2
 
3
- Read the [operator instructions](release/operator.md).
3
+ ### OpenClaw, ready for your team.
4
+
5
+ Give your team a shared OpenClaw server—with individual logins, your choice of AI
6
+ models, connected accounts, and execution protected by NVIDIA OpenShell.
7
+ **Self-hosted. Open source. OpenClaw’s own interface.**
8
+
9
+ [Get started](#get-started) · [Installation guide](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md) · [Releases](https://github.com/clawscarf/clawscarf/releases) · [Contribute](https://github.com/clawscarf/clawscarf/blob/main/CONTRIBUTING.md)
10
+
11
+ ## Get started
12
+
13
+ ```sh
14
+ curl -fsSL https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/install.sh | sh
15
+ export PATH="$HOME/.local/bin:$PATH"
16
+ clawscarf configure
17
+ ```
18
+
19
+ **Choose Team server. Pick a model. Sign in. Start your server.**
20
+
21
+ The terminal setup walks you through it, downloads the runtime, and opens OpenClaw
22
+ in your browser. Your sign-in becomes the first administrator account. No checkout,
23
+ Docker configuration files or separate OpenClaw installation required.
24
+
25
+ You’ll need **Docker Engine 29+ with Compose** (or Docker Desktop) and an **API key
26
+ for your chosen model provider**. The CLI includes its own Node runtime.
27
+ Available for **macOS Apple Silicon and Linux ARM64/x86-64**; Windows runs through
28
+ **WSL2, currently experimental**. Add `~/.local/bin` to your shell's PATH to keep
29
+ the command available in new terminals. The installer requires no sudo.
30
+ [Full platform requirements →](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md)
31
+
32
+ <details>
33
+ <summary>Prefer npm?</summary>
34
+
35
+ ```sh
36
+ npm install -g @clawscarf/cli@next
37
+ clawscarf configure
38
+ ```
39
+
40
+ This alternative uses your own Node installation: Node 24.16+ within 24.x or 26.1+.
41
+ The `next` tag installs the current alpha.
42
+
43
+ </details>
44
+
45
+ ## Make it your team’s workspace
46
+
47
+ | What you want to do | Where to do it |
48
+ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | **Start working with an agent** | Open a chat in OpenClaw. Create and configure agents using its native settings. |
50
+ | **Bring in your teammates** | Open **People**, create an invitation link, and assign native OpenClaw roles. Administrators can remove access there too. |
51
+ | **Connect Outlook and other services** | Open **Connections**, link an account, and choose which agents can use it. Connections is included in Team server and can be turned off during setup. |
52
+ | **Use your preferred models** | Choose the provider, model and reasoning level in setup. Run `clawscarf configure` again to change them. |
53
+
54
+ The default recipe uses **GPT-6 Astra through OpenAI, with medium reasoning**.
55
+ Choose another provider or model in the menu; usage is billed by your provider.
56
+ Your provider API key stays outside OpenClaw, in the model gateway.
57
+
58
+ Setup opens **[http://127.0.0.1:18800](http://127.0.0.1:18800)** by default.
59
+ That address is local to your machine. To share the server with teammates, use a
60
+ reachable HTTPS address; the [installation guide](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md#configuration-options)
61
+ covers the public URL and company SSO settings.
62
+
63
+ OpenShell applies filesystem and network policy to the whole OpenClaw runtime,
64
+ including plugins, shell commands and local tools. Recipes bring login, models and
65
+ optional Connections together in one setup; you keep OpenClaw’s native agents,
66
+ conversations, skills and settings.
67
+
68
+ ## Everyday commands
69
+
70
+ ```sh
71
+ clawscarf status # Check the server
72
+ clawscarf stop # Stop it, keeping your data
73
+ clawscarf start # Start it again
74
+ clawscarf configure # Change its configuration
75
+ ```
76
+
77
+ The default installation directory is `~/clawscarf-team`. Use
78
+ `--directory ~/another-team` to create or manage a separate installation.
79
+ Chats, files and settings survive stop/start. Closing the terminal leaves the server
80
+ running; Docker must stay running while it is in use.
81
+
82
+ For automation, the same configuration choices are available as command-line flags
83
+ with `--non-interactive`, and commands support `--json`.
84
+ [CLI reference and examples →](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md#configure-without-prompts)
85
+
86
+ ## Your server, your team
87
+
88
+ An installation serves **one trusted team**: separate logins and roles, shared
89
+ execution files and browser accounts. Native roles control application permissions;
90
+ people allowed to run code share the Gateway’s authority. Use separate installations
91
+ for teams that must be isolated from one another.
92
+
93
+ Default login and optional Connections use **ClawScarf Cloud**. You can use your own
94
+ OIDC provider and disable Connections to run without those hosted services. Original
95
+ model-provider keys and Connections management credentials stay outside OpenClaw.
96
+
97
+ ClawScarf is in alpha. Browser automation is off by default while an upstream routing
98
+ issue remains; retained data still needs backups. See the [security boundaries](https://github.com/clawscarf/clawscarf/blob/main/deploy/openshell/README.md),
99
+ [browser support](https://github.com/clawscarf/clawscarf/blob/main/deploy/execution/browser-node/README.md) and [open work](https://github.com/clawscarf/clawscarf/blob/main/TODO.md)
100
+ for details.
101
+
102
+ ## Explore and contribute
103
+
104
+ - **Using ClawScarf:** [Installation](https://github.com/clawscarf/clawscarf/blob/main/deploy/deployment/installation.md) · [People](https://github.com/clawscarf/clawscarf/blob/main/plugins/access/README.md) · [Connections](https://github.com/clawscarf/clawscarf/blob/main/plugins/connections/README.md) · [Models](https://github.com/clawscarf/clawscarf/blob/main/deploy/models/README.md)
105
+ - **Customizing it:** [Recipes](https://github.com/clawscarf/clawscarf/blob/main/recipes/README.md) · [Packs](https://github.com/clawscarf/clawscarf/blob/main/packs/README.md) · [Runtime architecture](https://github.com/clawscarf/clawscarf/blob/main/runtime/README.md)
106
+ - **Helping build it:** [Contributing](https://github.com/clawscarf/clawscarf/blob/main/CONTRIBUTING.md) · [Development setup](https://github.com/clawscarf/clawscarf/blob/main/scripts/README.md) · [Report a bug](https://github.com/clawscarf/clawscarf/issues)
107
+
108
+ Built on [OpenClaw](https://github.com/openclaw/openclaw),
109
+ [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell) and
110
+ [LiteLLM](https://github.com/BerriAI/litellm).
111
+ [MIT licensed](https://github.com/clawscarf/clawscarf/blob/main/LICENSE), with [third-party notices](https://github.com/clawscarf/clawscarf/blob/main/THIRD_PARTY_NOTICES.md).
112
+ ClawScarf is an independent project.
@@ -28,6 +28,11 @@ are recorded in lockfiles; upstream source is not vendored here.
28
28
  The terminal installer uses MIT-licensed `@clack/prompts` 1.8.1. It is installed
29
29
  from the operator lockfile and retains its upstream license with the dependency;
30
30
  the installer does not vendor or modify the prompt library.
31
+ Standalone CLI archives include the unmodified Node.js 24.19.0 executable from
32
+ [official Node downloads](https://nodejs.org/dist/v24.19.0/), verified against the
33
+ SHA-256 pins in [release/components.json](release/components.json). Each archive
34
+ retains Node's complete upstream LICENSE, including its bundled third-party notices,
35
+ at `node/LICENSE`. JavaScript dependencies retain their licenses in `node_modules`.
31
36
  The Gateway uses unmodified upstream commit `7bc487d39dc9e059bb9b19ea08152883022f83fe`;
32
37
  the [image build instructions](deploy/images/README.md) own source packaging.
33
38
  OpenClaw's upstream [license](https://github.com/openclaw/openclaw/blob/7bc487d39dc9e059bb9b19ea08152883022f83fe/LICENSE)
@@ -32,4 +32,4 @@ frontend native_node
32
32
  default_backend native_gateway
33
33
 
34
34
  backend native_gateway
35
- server gateway "host.docker.internal:${CLAWSCARF_NODE_GATEWAY_PORT}" resolvers docker init-addr libc,none
35
+ server gateway "application:${CLAWSCARF_NODE_GATEWAY_PORT}" resolvers docker init-addr libc,none
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-alpha.1",
2
+ "version": "0.1.0-alpha.3",
3
3
  "engines": {
4
4
  "node": ">=24.16.0 <25 || >=26.1.0"
5
5
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "team-server",
3
3
  "version": "0.1.0",
4
- "runtime": "../../runtime/releases/0.1.0-alpha.1.json",
4
+ "runtime": "../../runtime/releases/0.1.0-alpha.3.json",
5
5
  "name": "Team server",
6
6
  "description": "A protected OpenClaw server for your team, with hosted login, managed models and optional Connections.",
7
7
  "maturity": "supported",
package/release/README.md CHANGED
@@ -14,9 +14,10 @@ Recipes and packs ship with the CLI; there is no separate registry or dependency
14
14
  The [model catalog](../deploy/models/catalog.json) is also a CLI asset. Recipe IDs select
15
15
  catalog entries; model protocols and limits have one definition.
16
16
 
17
- ## Current development use
17
+ ## Published and development use
18
18
 
19
19
  ```sh
20
+ npm install -g @clawscarf/cli@next
20
21
  clawscarf configure
21
22
  # Or use staging for hosted login and Connections:
22
23
  clawscarf configure --cloud-url https://cloud-staging.clawscarf.com
@@ -33,7 +34,13 @@ locally built Docker image IDs and checksum-pinned OpenShell tools under ignored
33
34
  published, clean-machine release**. Definitions belong in the tracked directories
34
35
  above. Compiled images, binaries and test artifacts are not source definitions.
35
36
 
36
- The current platform is macOS arm64 with Docker Desktop. Setup validates the host,
37
+ Published candidates include macOS arm64 and Linux arm64/x86-64 tools, with Linux
38
+ container images for both architectures. Windows uses the Linux CLI inside WSL2
39
+ (experimental);
40
+ Docker Desktop must expose its Linux engine to that distribution. Intel Mac is blocked
41
+ by the pinned upstream OpenShell release lacking a Darwin x86-64 executable.
42
+ See the [upstream support matrix](https://docs.nvidia.com/openshell/reference/support-matrix)
43
+ for host requirements. Setup validates the host,
37
44
  tools and ports; it pulls registry images by digest and downloads missing runtime tools
38
45
  when their definition supplies an HTTPS URL and SHA-256. It cannot recover a missing
39
46
  local development image ID. See the
@@ -45,7 +52,8 @@ Release candidates contain:
45
52
 
46
53
  - npm `@clawscarf/cli`: compiled CLI, recipes, pack files, model catalog and runtime
47
54
  definitions. No Docker images or large OpenShell executables in npm.
48
- - GitHub Releases: versioned runtime tool archives, checksums and required notices.
55
+ - GitHub Releases: standalone CLI archives with a private Node runtime and installed
56
+ dependencies, an installer, runtime tool archives, checksums and required notices.
49
57
  - GHCR: runtime/companion images referenced by immutable registry digest.
50
58
 
51
59
  Installing a newer CLI supplies newer recipes. Each recipe still selects an exact
@@ -66,15 +74,37 @@ silently replaced. Docker verifies image digests. No start command resolves late
66
74
  with an exact version, such as `0.1.0-alpha.1`. It:
67
75
 
68
76
  1. Runs checks and tests the compiled operator outside the checkout on macOS arm64.
69
- 2. Builds the pinned OpenClaw source and ClawScarf images on Linux arm64, checks
70
- runtime capabilities and the Chromium sandbox, and pushes candidate images to GHCR.
77
+ 2. Builds the pinned OpenClaw source and ClawScarf images on Linux arm64 and x86-64,
78
+ checks runtime capabilities and the Chromium sandbox, then starts a protected
79
+ installation with private LiteLLM networking and verifies stop/start retention.
80
+ It publishes image indexes containing both architectures to GHCR.
71
81
  3. Downloads checksum-pinned OpenShell tools, records the image digests, and assembles
72
- the runtime archive, individual executable assets, npm CLI, checksums and notices.
82
+ one runtime archive per host platform, individual executable assets, npm CLI,
83
+ checksums and notices.
73
84
  4. Installs the resulting npm archive into a temporary prefix and checks its CLI/catalog.
85
+ 5. Builds standalone CLI archives on macOS ARM64 and Linux ARM64/x86-64 using the
86
+ same npm payload and frozen production dependency lockfile. Downloads Node using
87
+ the checksums in [components.json](components.json), retains its license, and
88
+ tests the installer and packaged CLI with system Node deliberately unavailable.
89
+ Only after all three platform checks pass does it assemble `release-candidate`.
90
+
91
+ [Standalone packaging](../scripts/release/standalone.ts) adds a private Node executable
92
+ and a small launcher; it does not change the CLI implementation. The
93
+ [installer](install.sh) is versioned with each release and downloads only that
94
+ version's archive, verifies SHA-256, and installs under `~/.local` by default.
95
+ `--prefix` selects another absolute directory. It never installs a host service,
96
+ changes system Node, edits shell profiles or needs sudo. To upgrade the CLI, run
97
+ the newer release's installer; it switches the command and retains the old version.
98
+ Runtime upgrades and installation data are separate from CLI installation.
74
99
 
75
100
  The [image builder](../scripts/release/build-images.sh) and
76
101
  [candidate assembler](../scripts/release/candidate.ts) contain the build commands;
77
- Actions supplies runners and registry credentials. The source recipe pins the development
102
+ GitHub Actions orchestrates the build and supplies registry credentials. Image builds
103
+ and Linux installation checks use Depot ARM64/x86-64 runners; macOS checks and packaging
104
+ use GitHub-hosted runners. The Depot Managed Runners app connects the ClawScarf organization
105
+ to the RAW Labs Depot organization. The image job installs Docker 29.5.3 on its
106
+ disposable runner for the network features required by installations.
107
+ The source recipe pins the development
78
108
  runtime. Packaging resolves that same runtime to the candidate's immutable definition;
79
109
  it does not make recipes select latest. The first release uses the same CLI/runtime
80
110
  version. Recipe versions remain independent; bump them when defaults, pack selection
@@ -92,20 +122,40 @@ GitHub Release and publishes the already-built npm tarball. It never rebuilds im
92
122
  or packages. Prereleases use npm's `next` tag; stable versions use `latest`. Repeating
93
123
  a GitHub upload is allowed only when the existing checksums match exactly.
94
124
 
95
- Repository setup before first publication:
125
+ Repository setup:
96
126
 
97
127
  - GHCR image packages must be publicly readable; a private candidate digest is not
98
128
  usable by an unauthenticated installer. The build uses GitHub's scoped job token.
99
- - Configure the GitHub `release` environment and npm trusted publishing for
100
- `@clawscarf/cli`, repository `clawscarf/clawscarf`, workflow [publish-release.yml](../.github/workflows/publish-release.yml),
101
- environment `release`. npm ownership/initial package setup is separate from code.
102
- - Check transitive notices/source obligations before distributing the first images.
129
+ - The GitHub `release` environment restricts deployment to `main`. The npm
130
+ organization `@clawscarf` owns the public `@clawscarf/cli` package.
131
+ - npm trusted publishing is configured for repository `clawscarf/clawscarf`, workflow
132
+ [publish-release.yml](../.github/workflows/publish-release.yml), environment `release`.
133
+ The first tested tarball was published using the owner's authenticated npm CLI because
134
+ npm requires a package to exist before configuring trust. Subsequent publication
135
+ uses the workflow's short-lived identity rather than a stored npm token; alpha.2
136
+ was published through that workflow successfully.
137
+ - Transitive notices/source review remains open in [TODO.md](../TODO.md).
103
138
  Debian copyright files remain in the images; runtime assets include upstream notices
104
139
  and the image build exports its network-tool sources. Those files are not a claim of
105
140
  a completed license audit. Pinned Debian packages still depend on mirror retention.
106
141
 
107
- No npm/GitHub release has been published by adding these workflows. The first Actions
108
- run, clean-machine journey, registry access and publication remain in [TODO.md](../TODO.md).
142
+ [Candidate 35532613602](https://github.com/clawscarf/clawscarf/actions/runs/35532613602)
143
+ passed full checks, compiled archive installation and all eight image builds on
144
+ native ARM64 and x86-64 Depot runners. The same artifacts are available in the
145
+ [0.1.0-alpha.2 GitHub prerelease](https://github.com/clawscarf/clawscarf/releases/tag/v0.1.0-alpha.2)
146
+ and as `@clawscarf/cli@0.1.0-alpha.2`, tagged `next`.
147
+
148
+ Both Linux architectures passed protected runtime startup, native WebSocket forwarding,
149
+ authenticated TLS to the private model gateway, retained stop/start and deletion on
150
+ Docker 29.5.3. The final ARM64 images also passed real GPT-6 Astra inference and retained
151
+ restart on macOS Docker Desktop. Candidate downloads passed SHA-256 verification and
152
+ the CLI tarball installed independently of the repository. Disposable test containers,
153
+ volumes and networks were deleted afterward.
154
+
155
+ Staging hosted administrator login and the packaged interactive setup previously
156
+ passed on macOS with alpha.1. Hosted login and real provider inference on Linux,
157
+ actual Windows/WSL2 installation, production first-time signup and changed-version
158
+ upgrades remain unverified; see [TODO.md](../TODO.md).
109
159
 
110
160
  ## Assemble runtime artifacts
111
161
 
@@ -12,16 +12,40 @@
12
12
  "darwin-arm64": {
13
13
  "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-aarch64-apple-darwin.tar.gz",
14
14
  "sha256": "e582f2374053bebac8e6aaeb4a369931b7d4bb97bd55055e2c02e85502627e22"
15
+ },
16
+ "linux-arm64": {
17
+ "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-aarch64-unknown-linux-musl.tar.gz",
18
+ "sha256": "7a949c48d1e000cd280869eea1e203e24816b9cfefc575b68a8b72b939cb3f43"
19
+ },
20
+ "linux-x64": {
21
+ "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-x86_64-unknown-linux-musl.tar.gz",
22
+ "sha256": "4fb4476d80a1875a0b83547ec3aba999cf0a2e2d75f95f2f709b622e2103520e"
15
23
  }
16
24
  },
17
25
  "gateway": {
18
26
  "darwin-arm64": {
19
27
  "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-gateway-aarch64-apple-darwin.tar.gz",
20
28
  "sha256": "f192d3d737c125264e13ef73458541df2ca6a9eb2fa599736a7f2587d5d2ce8d"
29
+ },
30
+ "linux-arm64": {
31
+ "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz",
32
+ "sha256": "292c379193a339220234ffea585350901468bb8f4076e2076bc074e8ed18974b"
33
+ },
34
+ "linux-x64": {
35
+ "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.116/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz",
36
+ "sha256": "59c6da724eae7a00c28826f9191efbdf4fbaa5c768afdc8dea6a80a949ebcc89"
21
37
  }
22
38
  },
23
39
  "gatewayImage": "ghcr.io/nvidia/openshell/gateway:0.0.116@sha256:05cf77bbb022a739aed6f22daa0e7e164415f4ab273b5f84319e46d91eb8f645"
24
40
  },
41
+ "cliNode": {
42
+ "version": "24.19.0",
43
+ "archives": {
44
+ "darwin-arm64": "8294b7aa9b03997481c06babf1e8b270c859358f27da57a11509afe537ac381d",
45
+ "linux-arm64": "d28c8a5bf0a808f0ed434a1dce8c54ae98f0371c0bd86ac58abc613f73e6643f",
46
+ "linux-x64": "f625d97cd707df4ff96254916fbc5ff014f09c09effe5a1e0ca8f6d41a8789d4"
47
+ }
48
+ },
25
49
  "companionNode": {
26
50
  "version": "24.19.0",
27
51
  "image": "node@sha256:4196d66a565c6f195728d9952f161f4adfe2ad753052a08b7ec7f1c5a6bda42b"
@@ -9,10 +9,35 @@ registry images by digest. Local development image IDs cannot be downloaded. The
9
9
  package is named `@clawscarf/cli` and exposes the `clawscarf` command. Development archives
10
10
  remain private; the release candidate builder makes the package publishable. [Release contents](README.md) defines the publication model.
11
11
 
12
- ## Run the archive
12
+ ## Install from npm
13
+
14
+ ```sh
15
+ npm install -g @clawscarf/cli@next
16
+ clawscarf configure
17
+ ```
18
+
19
+ The `next` tag selects the current alpha. Use an exact version instead when needed.
20
+ npm installs the CLI dependencies; runtime images and tools are fetched during setup.
21
+ See [release contents](README.md) for supported platforms and verification.
22
+
23
+ ## Standalone download
24
+
25
+ Standalone CLI archives include Node and installed dependencies. Download the archive
26
+ for your platform and its `SHA256SUMS` from the same GitHub Release. The release's
27
+ [installer](install.sh) selects the correct archive, verifies it and installs
28
+ `clawscarf` under `~/.local/bin`. It accepts `--prefix /absolute/directory`.
29
+ No system Node, npm or pnpm is required. These archives are separate from both the
30
+ npm package and the runtime-tool archives.
31
+
32
+ The layout is `clawscarf/` containing the launcher, `node/bin/node`, Node's LICENSE,
33
+ and `package/` with the CLI, dependencies and catalogs. You can also verify and
34
+ extract the archive yourself, then run `./clawscarf/clawscarf configure`.
35
+ Moving the whole directory preserves the bundled runtime and dependencies.
36
+
37
+ ## Run the JavaScript archive
13
38
 
14
39
  Verify the archive against its accompanying `SHA256SUMS`, then extract it into a new
15
- directory. Use Node 24.16 or later in the Node 24 line, or Node 26.1 or later, and
40
+ directory. Unlike the standalone download, this requires Node 24.16 or later in the Node 24 line, or Node 26.1 or later, and
16
41
  pnpm 10.33.0. In the extracted `package` directory:
17
42
 
18
43
  ```sh
@@ -28,7 +53,8 @@ Dependencies are installed from the included frozen lockfile. Its root importer
28
53
  contains only the production dependencies referenced by the staged operator;
29
54
  publisher and browser dependencies are omitted. Locked transitive versions are retained.
30
55
  No TypeScript compiler, contributor source or build step is needed to run the commands.
31
- The current local assembly supports macOS arm64 with Docker Desktop only.
56
+ Platform support is determined by the selected runtime definition. Published candidates
57
+ include macOS arm64 and Linux arm64/x86-64 tools; Windows runs the Linux CLI inside WSL2.
32
58
 
33
59
  The installer reviews recipe settings before credentials, prepares the installation,
34
60
  and offers **Start now**. It starts Docker services and protected OpenShell containers, then exits; closing
@@ -0,0 +1,63 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "0.1.0-alpha.3",
4
+ "sourceRevision": "b5e0988a9dbbf28b40974cc1a8b38e1d9466e6f2",
5
+ "platforms": [
6
+ "darwin-arm64",
7
+ "linux-arm64",
8
+ "linux-x64"
9
+ ],
10
+ "images": {
11
+ "postgres": "postgres@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193",
12
+ "gateway": "ghcr.io/clawscarf/clawscarf/runtime@sha256:fa6cb6ee1116a17577369166e6d73391f0194b78a69743e4f8bc137dbffb1c5c",
13
+ "companion": "ghcr.io/clawscarf/clawscarf/companion@sha256:cc2a9f4caf2f9a2e97d198270b8d02cfc7fc082a97c5982eb40e223fe1d2f9a8",
14
+ "openshellClient": "ghcr.io/clawscarf/clawscarf/openshell-client@sha256:48167a723eac58247b5e07d8266495b7b71e51e72491a1ed63c45e2f1e5590d1",
15
+ "relay": "ghcr.io/clawscarf/clawscarf/browser-relay@sha256:a6958a6ee43bb6ebcbf18a0aae477a76ac1c8d8c4ed914d319a40dca40571ae7",
16
+ "models": "ghcr.io/berriai/litellm:v1.100.1@sha256:a3715fa7ad8387941ab697259bd2881d68931657247a41984f90fae6d11c62bf",
17
+ "browser": {
18
+ "chromium": "ghcr.io/clawscarf/clawscarf/browser@sha256:b765e4698c471f26ff50d7f18d14ecf2fb903b92bb22e0299c3758a6429c2121",
19
+ "node": "ghcr.io/clawscarf/clawscarf/browser-node@sha256:c55268a56d45f65c05f1dd7c5f6a45c2b5cdaefb552606148c42fff0967a7654",
20
+ "dns": "ghcr.io/clawscarf/clawscarf/browser-dns@sha256:c9d1a557c8bb897eccd2f83693ba3bef71f8fd681e5cd4ce189942460e67e449",
21
+ "egress": "ghcr.io/clawscarf/clawscarf/browser-egress@sha256:b1b69e03629211ceaf84f7fd6c3bea7fbe659f81b4a4ab00fbc30588810cd970"
22
+ }
23
+ },
24
+ "tools": {
25
+ "openshell": {
26
+ "version": "0.0.116",
27
+ "cli": {
28
+ "darwin-arm64": {
29
+ "file": "tools/darwin-arm64/openshell",
30
+ "sha256": "0baeeffe0e4c184b08fde910ac9e8815fb0b8cfb808e49c0e634d7db65fb03bb",
31
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-darwin-arm64"
32
+ },
33
+ "linux-arm64": {
34
+ "file": "tools/linux-arm64/openshell",
35
+ "sha256": "84df9f059b16b66c83748606cc87d9edcda84b57effea19543f5c368aba7eb18",
36
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-linux-arm64"
37
+ },
38
+ "linux-x64": {
39
+ "file": "tools/linux-x64/openshell",
40
+ "sha256": "24e85062073d512d1951c76cd3890b7bce1ab01cc36a08f88b3b43c067a402da",
41
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-linux-x64"
42
+ }
43
+ },
44
+ "gateway": {
45
+ "darwin-arm64": {
46
+ "file": "tools/darwin-arm64/openshell-gateway",
47
+ "sha256": "298706259ef18350f334c222e981e91583558511d50bce22727c689d261203e6",
48
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-gateway-darwin-arm64"
49
+ },
50
+ "linux-arm64": {
51
+ "file": "tools/linux-arm64/openshell-gateway",
52
+ "sha256": "0e660428e655e7481f34ba8a4b351554400baf791863d32555c98dddb3f09ae2",
53
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-gateway-linux-arm64"
54
+ },
55
+ "linux-x64": {
56
+ "file": "tools/linux-x64/openshell-gateway",
57
+ "sha256": "780fc7ad871e2163f3fb866a2d0254ee682fc0d83e6261a84b350c8dcdd9f47b",
58
+ "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.3/openshell-gateway-linux-x64"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
@@ -11,6 +11,7 @@ const settingsSchema = z.strictObject({
11
11
  port: z.number().int().min(1024).max(65535),
12
12
  gateway: z.string().min(1),
13
13
  cli: z.string().min(1),
14
+ hostGatewayIp: z.ipv4().optional(),
14
15
  });
15
16
  const releaseInput = JSON.parse(await readFile(new URL("../release/components.json", import.meta.url), "utf8"));
16
17
  const release = z
@@ -75,6 +76,7 @@ gateway_id = ${JSON.stringify(settings.name)}
75
76
  ttl_secs = 0
76
77
  [openshell.drivers.docker]
77
78
  network_name = ${JSON.stringify(settings.name)}
79
+ ${settings.hostGatewayIp ? `host_gateway_ip = ${JSON.stringify(settings.hostGatewayIp)}` : ""}
78
80
  sandbox_namespace = ${JSON.stringify(settings.name)}
79
81
  supervisor_image = ${JSON.stringify(release.supervisorImage)}
80
82
  image_pull_policy = "IfNotPresent"
@@ -91,6 +93,7 @@ command
91
93
  .requiredOption("--directory <path>", "New private controller directory")
92
94
  .requiredOption("--gateway <path>", "Verified openshell-gateway executable")
93
95
  .requiredOption("--cli <path>", "Verified openshell executable")
96
+ .option("--host-gateway-ip <address>", "Controller address on the runtime bridge")
94
97
  .option("--name <name>", "Isolated controller name", "clawscarf")
95
98
  .option("--port <port>", "Loopback controller port", "17671")
96
99
  .action(async (options) => {
@@ -101,6 +104,7 @@ command
101
104
  port: Number(options.port),
102
105
  gateway: resolve(options.gateway),
103
106
  cli: resolve(options.cli),
107
+ hostGatewayIp: options.hostGatewayIp,
104
108
  });
105
109
  await verifyExecutable(settings.gateway);
106
110
  await verifyExecutable(settings.cli);
@@ -115,6 +119,8 @@ command
115
119
  "localhost",
116
120
  "--server-san",
117
121
  "host.openshell.internal",
122
+ "--server-san",
123
+ "controller.clawscarf.internal",
118
124
  ], { timeout: 30000 });
119
125
  const mtls = join(directory, "config/openshell/gateways", settings.name, "mtls");
120
126
  await mkdir(mtls, { recursive: true, mode: 0o700 });
@@ -41,7 +41,6 @@ export function browserNodeServices(state, directory, browserAddress, prepared)
41
41
  image: state.input.relayImage,
42
42
  user: String(process.getuid?.() ?? 1000),
43
43
  networks: { machine: { ipv4_address: addresses.ingress }, default: {} },
44
- extra_hosts: ["host.docker.internal:host-gateway"],
45
44
  environment: {
46
45
  CLAWSCARF_NODE_INGRESS_ADDRESS: addresses.ingress,
47
46
  CLAWSCARF_NODE_GATEWAY_PORT: String(state.input.ports.native),
@@ -64,7 +64,7 @@ export async function ensureCertificates(directory) {
64
64
  "-subj",
65
65
  "/CN=ClawScarf local management",
66
66
  "-addext",
67
- "subjectAltName=DNS:localhost,DNS:host.docker.internal,IP:127.0.0.1",
67
+ "subjectAltName=DNS:localhost,DNS:host.docker.internal,DNS:models.clawscarf.internal,IP:127.0.0.1",
68
68
  "-addext",
69
69
  "basicConstraints=critical,CA:TRUE",
70
70
  ]);
@@ -6,7 +6,7 @@ import { resourceNames } from "./state.js";
6
6
  import { run } from "./process.js";
7
7
  import { modelGatewayServices } from "./model-gateway-compose.js";
8
8
  import { postgresImage } from "./images.js";
9
- export function composeConfiguration(state, directory, browserAddress, browserMachine) {
9
+ export function composeConfiguration(state, directory, controllerAddress, browserAddress, browserMachine, controllerSocketGroup = 0) {
10
10
  const { input, ownerId } = state;
11
11
  const names = resourceNames(state);
12
12
  const privateDirectory = join(directory, "private");
@@ -23,7 +23,7 @@ export function composeConfiguration(state, directory, browserAddress, browserMa
23
23
  const configuration = {
24
24
  name: names.project,
25
25
  services: {
26
- ...controllerServices(directory, state),
26
+ ...controllerServices(directory, state, controllerAddress, controllerSocketGroup),
27
27
  ...modelGatewayServices(directory, state),
28
28
  ...(input.browser && browserAddress && browserMachine
29
29
  ? browserNodeServices(state, directory, browserAddress, browserMachine)
@@ -153,9 +153,7 @@ export function composeConfiguration(state, directory, browserAddress, browserMa
153
153
  },
154
154
  },
155
155
  networks: {
156
- ...(input.relayImage
157
- ? { runtime: { external: true, name: names.sandbox } }
158
- : {}),
156
+ runtime: { external: true, name: names.sandbox },
159
157
  default: { external: true, name: `${names.project}_default` },
160
158
  ...(input.browser
161
159
  ? {
@@ -187,8 +187,8 @@ export const localInput = z
187
187
  export function parseLocalInput(value) {
188
188
  return localInput.parse(value);
189
189
  }
190
- export function managementOrigin(input) {
191
- return `https://host.docker.internal:${String(input.ports.management)}`;
190
+ export function managementOrigin() {
191
+ return "https://localhost:18801";
192
192
  }
193
193
  function mountedPrivateFile(directory, path, name) {
194
194
  absolutePath.parse(path);
@@ -224,10 +224,10 @@ export function generateLocalConfiguration(options) {
224
224
  port: 18801,
225
225
  },
226
226
  runtime: {
227
- origin: `http://host.docker.internal:${String(input.ports.native)}`,
228
- managementOrigin: managementOrigin(input),
227
+ origin: `http://application:${String(input.ports.native)}`,
228
+ managementOrigin: managementOrigin(),
229
229
  widgetOrigin,
230
- widgetUpstream: `http://host.docker.internal:${String(input.ports.nativeWidgets)}`,
230
+ widgetUpstream: `http://widgets:${String(input.ports.nativeWidgets)}`,
231
231
  },
232
232
  ...(input.team.certificateFile
233
233
  ? {
@@ -2,13 +2,14 @@ import { join } from "node:path";
2
2
  import { openshellGatewayImage } from "./images.js";
3
3
  import { resourceNames } from "./state.js";
4
4
  /** OpenShell alone receives Docker authority. Forwarders receive only client credentials. */
5
- export function controllerServices(directory, state) {
5
+ export function controllerServices(directory, state, address, socketGroup) {
6
6
  const controller = join(directory, "controller");
7
7
  const { input } = state;
8
8
  const name = resourceNames(state).sandbox;
9
9
  const port = input.ports.controller;
10
10
  const forward = (target, port) => ({
11
11
  image: input.openshellClientImage,
12
+ user: `${String(process.getuid?.() ?? 1000)}:${String(process.getgid?.() ?? 1000)}`,
12
13
  init: true,
13
14
  read_only: true,
14
15
  cap_drop: ["ALL"],
@@ -21,23 +22,31 @@ export function controllerServices(directory, state) {
21
22
  XDG_DATA_HOME: "/tmp/data",
22
23
  },
23
24
  volumes: [`${join(controller, "config")}:/controller/config:ro`],
24
- extra_hosts: ["host.openshell.internal:host-gateway"],
25
25
  command: [
26
26
  "forward",
27
- "start",
27
+ "service",
28
+ "--target-port",
29
+ String(port),
30
+ "--local",
28
31
  `0.0.0.0:${String(port)}`,
29
32
  target,
30
33
  "--gateway",
31
34
  name,
32
35
  "--gateway-endpoint",
33
- `https://host.openshell.internal:${String(input.ports.controller)}`,
36
+ `https://controller.clawscarf.internal:${String(input.ports.controller)}`,
34
37
  ],
35
38
  ports: [`127.0.0.1:${String(port)}:${String(port)}`],
36
39
  });
37
40
  return {
38
41
  controller: {
39
42
  image: openshellGatewayImage,
40
- user: "0:0",
43
+ user: `${String(process.getuid?.() ?? 1000)}:${String(process.getgid?.() ?? 1000)}`,
44
+ // Desktop exposes a root-group socket; Linux Engine uses the host socket's group.
45
+ group_add: [...new Set(["0", String(socketGroup)])],
46
+ networks: {
47
+ default: { aliases: ["controller.clawscarf.internal"] },
48
+ runtime: { ipv4_address: address },
49
+ },
41
50
  command: ["--config", join(controller, "gateway.toml")],
42
51
  environment: {
43
52
  XDG_CONFIG_HOME: join(controller, "config"),
@@ -51,7 +60,6 @@ export function controllerServices(directory, state) {
51
60
  "/var/run/docker.sock:/var/run/docker.sock",
52
61
  ],
53
62
  ports: [`127.0.0.1:${String(port)}:${String(port)}`],
54
- extra_hosts: ["host.openshell.internal:host-gateway"],
55
63
  },
56
64
  application: forward(name, input.ports.native),
57
65
  widgets: forward(name, input.ports.nativeWidgets),
@@ -28,6 +28,12 @@ export function modelGatewayServices(directory, state) {
28
28
  },
29
29
  models: {
30
30
  image: input.image,
31
+ user: `${String(process.getuid?.() ?? 1000)}:${String(process.getgid?.() ?? 1000)}`,
32
+ environment: { HOME: "/tmp" },
33
+ networks: {
34
+ default: {},
35
+ runtime: { aliases: ["models.clawscarf.internal"] },
36
+ },
31
37
  init: true,
32
38
  read_only: true,
33
39
  cap_drop: ["ALL"],
@@ -43,6 +43,14 @@ export async function prepareModelGateway(directory, state, options = {}) {
43
43
  return;
44
44
  const loaded = await loadGatewayConfiguration(input.configurationFile, input.upstreamEnvironmentFile);
45
45
  const root = join(directory, "private/models");
46
+ let baseUrl = "https://models.clawscarf.internal:4000/v1";
47
+ if (options.replaceConfiguration) {
48
+ const current = configurationSchema.parse(JSON.parse(await readFile(join(root, "native.json"), "utf8")));
49
+ if (current.mode !== "litellm")
50
+ throw new LocalSetupError("invalid_model_setup", "Expected an existing model gateway.");
51
+ // Model choices do not change the installation's retained network endpoint.
52
+ baseUrl = current.baseUrl;
53
+ }
46
54
  await mkdir(root, { recursive: true, mode: 0o700 });
47
55
  for (const [name, prefix] of [
48
56
  ["master-key", "sk-"],
@@ -74,7 +82,7 @@ export async function prepareModelGateway(directory, state, options = {}) {
74
82
  await publish(join(root, "models.json"), JSON.stringify(liteLlmConfiguration(loaded.configuration)));
75
83
  await publish(join(root, "native.json"), JSON.stringify({
76
84
  ...loaded.configuration,
77
- baseUrl: `https://host.docker.internal:${String(input.port)}/v1`,
85
+ baseUrl,
78
86
  }));
79
87
  }
80
88
  export async function prepareModelCredential(directory, state) {
@@ -264,6 +264,10 @@ export async function ensureLocalNetworks(directory, state, command = run) {
264
264
  "create",
265
265
  "--driver",
266
266
  "bridge",
267
+ // Docker chooses a free subnet; declaring its size permits static endpoints.
268
+ ...(intent.purpose !== "companion"
269
+ ? ["--subnet", "0.0.0.0/24"]
270
+ : []),
267
271
  ...(intent.purpose === "runtime" ? ["--attachable"] : []),
268
272
  ...(["browser", "machine"].includes(intent.purpose)
269
273
  ? [
@@ -352,3 +356,20 @@ export async function observedBrowserMachineAddresses(directory, state, command
352
356
  changed();
353
357
  return { node: observed.isolated.address, ingress, dns };
354
358
  }
359
+ /** Stable address on the owned runtime bridge for the containerized OpenShell controller. */
360
+ export async function observedControllerAddress(directory, state) {
361
+ await verifyLocalNetworks(directory, state);
362
+ const configs = z
363
+ .array(z.object({ Subnet: z.string(), Gateway: z.string() }))
364
+ .parse(JSON.parse(await run("docker", [
365
+ "network",
366
+ "inspect",
367
+ resourceNames(state).sandbox,
368
+ "--format",
369
+ "{{json .IPAM.Config}}",
370
+ ])));
371
+ const config = configs.find((item) => isIP(item.Gateway) === 4);
372
+ if (!config)
373
+ changed();
374
+ return reservedAddress(config).address;
375
+ }
@@ -8,10 +8,10 @@ import { prepareRelay } from "./relay.js";
8
8
  import { prepareRuntimePolicy } from "./policy.js";
9
9
  import { prepareBrowser, initializeBrowserVolume } from "./browser.js";
10
10
  import { readTeamMaterials, prepareTeamFiles } from "./team.js";
11
- import { readFile, lstat } from "node:fs/promises";
11
+ import { readFile, lstat, stat } from "node:fs/promises";
12
12
  import { join, resolve } from "node:path";
13
13
  import { prepareInitialModels, withInitialModels, } from "./models.js";
14
- import { ensureLocalNetworks } from "./networks.js";
14
+ import { ensureLocalNetworks, observedControllerAddress } from "./networks.js";
15
15
  import { nodeEntrypoint } from "./entrypoint.js";
16
16
  import { ensureCertificates } from "./certificates.js";
17
17
  import { z } from "zod";
@@ -29,8 +29,9 @@ export async function prepareLocal(directoryInput, inputValue, capabilities) {
29
29
  const input = parseLocalInput(inputValue);
30
30
  const connections = await loadInitialConnections(input.connections);
31
31
  const teamMaterials = await readTeamMaterials(input.team);
32
- if (process.platform !== "darwin" || process.arch !== "arm64")
33
- throw new LocalSetupError("platform_unqualified", "This local assembly currently requires macOS arm64 with Docker Desktop; other platforms need qualification.");
32
+ if (!["darwin", "linux"].includes(process.platform) ||
33
+ !["arm64", "x64"].includes(process.arch))
34
+ throw new LocalSetupError("platform_unqualified", "Use macOS or Linux (including WSL2) with Linux Docker containers.");
34
35
  for (const image of [
35
36
  input.runtimeImage,
36
37
  input.openshellClientImage,
@@ -71,6 +72,7 @@ export async function prepareLocal(directoryInput, inputValue, capabilities) {
71
72
  const connectionsEndpoint = await prepareInitialConnections(directory, connections);
72
73
  await prepareModelGateway(directory, state);
73
74
  await ensureLocalNetworks(directory, state);
75
+ const controllerAddress = await observedControllerAddress(directory, state);
74
76
  const browser = await prepareBrowser(directory, state);
75
77
  const browserMachine = browser
76
78
  ? await prepareBrowserNode(directory, state, browser.token)
@@ -86,7 +88,9 @@ export async function prepareLocal(directoryInput, inputValue, capabilities) {
86
88
  await ensureOwnedVolume(names.browserVolume, state.ownerId);
87
89
  await initializeBrowserVolume(state, browser.token);
88
90
  }
89
- await ensurePrivateFile(join(directory, "compose.json"), JSON.stringify(composeConfiguration(state, directory, browser?.address, browserMachine), null, 2));
91
+ await ensurePrivateFile(join(directory, "compose.json"), JSON.stringify(composeConfiguration(state, directory, controllerAddress, browser?.address, browserMachine, process.platform === "linux"
92
+ ? (await stat("/var/run/docker.sock")).gid
93
+ : 0), null, 2));
90
94
  await prepareModelCredential(directory, state);
91
95
  const models = await prepareInitialModels(directory, input.models);
92
96
  await prepareRuntimePolicy(directory, models, connectionsEndpoint);
@@ -151,6 +155,8 @@ export async function prepareLocal(directoryInput, inputValue, capabilities) {
151
155
  input.openshellGateway,
152
156
  "--cli",
153
157
  input.openshellCli,
158
+ "--host-gateway-ip",
159
+ controllerAddress,
154
160
  "--name",
155
161
  names.sandbox,
156
162
  "--port",
@@ -66,7 +66,11 @@ export const terminalPrompts = {
66
66
  note: clack.note,
67
67
  async openBrowser(url) {
68
68
  try {
69
- await promisify(execFile)(process.platform === "darwin" ? "open" : "xdg-open", [url], { timeout: 5000 });
69
+ await promisify(execFile)(process.platform === "darwin"
70
+ ? "open"
71
+ : process.env.WSL_DISTRO_NAME
72
+ ? "explorer.exe"
73
+ : "xdg-open", [url], { timeout: 5000 });
70
74
  }
71
75
  catch {
72
76
  process.stderr.write("Could not open a browser. Open the link above manually.\n");
@@ -12,8 +12,9 @@ import { acquireRuntimeTools } from "./runtime.js";
12
12
  import { InstallationError } from "./errors.js";
13
13
  /** No configuration, credentials or cloud account is needed for these checks. */
14
14
  export async function checkHost(command = run) {
15
- if (process.platform !== "darwin" || process.arch !== "arm64")
16
- throw new InstallationError("unsupported_platform", "This release requires macOS on Apple Silicon with Docker Desktop.");
15
+ if (!["darwin", "linux"].includes(process.platform) ||
16
+ !["arm64", "x64"].includes(process.arch))
17
+ throw new InstallationError("unsupported_platform", "Use macOS or Linux (including WSL2), with an ARM64 or x86-64 processor and Linux Docker containers. Run the Windows CLI inside WSL2.");
17
18
  let os;
18
19
  try {
19
20
  os = await command("docker", ["info", "--format", "{{.OSType}}"]);
@@ -23,8 +24,8 @@ export async function checkHost(command = run) {
23
24
  error.commandFailure?.reason === "spawn" &&
24
25
  error.commandFailure.systemCode === "ENOENT";
25
26
  throw new InstallationError("unavailable", missing
26
- ? "Docker is not installed or is not on PATH. Install Docker Desktop, start it, then run configure again."
27
- : "Cannot reach Docker. Start Docker Desktop and wait until it is running, then try again.");
27
+ ? "Docker is not installed or is not on PATH. Install Docker Engine with Compose or Docker Desktop, then run configure again."
28
+ : "Cannot reach Docker. Start Docker and check that your user can access its socket, then try again.");
28
29
  }
29
30
  if (os.trim() !== "linux")
30
31
  throw new InstallationError("unsupported_platform", "Docker must run Linux containers.");
@@ -32,8 +33,12 @@ export async function checkHost(command = run) {
32
33
  await command("docker", ["compose", "version"]);
33
34
  }
34
35
  catch {
35
- throw new InstallationError("unavailable", "Docker Compose is unavailable. Install or update Docker Desktop, then try again.");
36
+ throw new InstallationError("unavailable", "Docker Compose is unavailable. Install the Docker Compose plugin or update Docker Desktop, then try again.");
36
37
  }
38
+ const version = (await command("docker", ["version", "--format", "{{.Server.Version}}"])).trim();
39
+ const major = Number(/^([0-9]+)\./.exec(version)?.[1]);
40
+ if (!Number.isInteger(major) || major < 29)
41
+ throw new InstallationError("unsupported_platform", "Docker Engine 29 or newer is required for automatic subnet allocation with fixed service addresses. Update Docker Engine or Docker Desktop, then try again.");
37
42
  }
38
43
  /** Stream only Docker's layer progress, never registry error payloads or credentials. */
39
44
  export function pullImage(image, report, signal) {
@@ -3,7 +3,7 @@ import { hostedOidc, hostedConnections } from "../cloud/registration.js";
3
3
  import { dirname, resolve, join } from "node:path";
4
4
  import { loadGatewayConfiguration } from "../deployment/model-gateway.js";
5
5
  import { createServer } from "node:net";
6
- import { releaseSchema } from "../release/definition.js";
6
+ import { releaseSchema, releaseTools } from "../release/definition.js";
7
7
  import { parseLocalInput, } from "../deployment/configuration.js";
8
8
  import { loadInitialModels } from "../deployment/models.js";
9
9
  import { loadInitialConnections, readInitialConnectionToken, } from "../deployment/connections.js";
@@ -49,10 +49,11 @@ export async function resolveInstallation(configFile, internalPorts) {
49
49
  if (!release.platforms.some((platform) => platform === `${process.platform}-${process.arch}`))
50
50
  throw new InstallationError("unsupported_platform", "This release does not support this host platform.");
51
51
  const toolBase = dirname(releasePath);
52
- const cli = resolve(toolBase, release.tools.openshell.cli.file);
53
- const gateway = resolve(toolBase, release.tools.openshell.gateway.file);
54
- await verifyReleaseTool(cli, release.tools.openshell.cli.sha256);
55
- await verifyReleaseTool(gateway, release.tools.openshell.gateway.sha256);
52
+ const tools = releaseTools(release);
53
+ const cli = resolve(toolBase, tools.cli.file);
54
+ const gateway = resolve(toolBase, tools.gateway.file);
55
+ await verifyReleaseTool(cli, tools.cli.sha256);
56
+ await verifyReleaseTool(gateway, tools.gateway.sha256);
56
57
  const [controller, management, native, nativeWidgets, database, browser, models,] = internalPorts;
57
58
  const inputs = {};
58
59
  async function inputFile(value, secret) {
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { chmod, copyFile, mkdir, open, rename, rm, writeFile, } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
- import { releaseSchema } from "../release/definition.js";
4
+ import { releaseSchema, releaseTools } from "../release/definition.js";
5
5
  import { readJson, verifyReleaseTool } from "./files.js";
6
6
  import { InstallationError } from "./errors.js";
7
7
  /** Installation-owned paths survive npm cache cleanup and later CLI updates. */
@@ -10,7 +10,7 @@ export async function retainRuntime(source, directory) {
10
10
  const target = join(directory, "runtime");
11
11
  await mkdir(join(target, "tools"), { recursive: true, mode: 0o700 });
12
12
  for (const name of ["cli", "gateway"]) {
13
- const tool = release.tools.openshell[name];
13
+ const tool = releaseTools(release)[name];
14
14
  const original = resolve(dirname(source), tool.file);
15
15
  tool.file = `tools/openshell${name === "gateway" ? "-gateway" : ""}`;
16
16
  try {
@@ -34,7 +34,7 @@ export async function acquireRuntimeTools(releaseFile, options) {
34
34
  const release = releaseSchema.parse(await readJson(releaseFile));
35
35
  for (const name of ["cli", "gateway"]) {
36
36
  options.signal?.throwIfAborted();
37
- const tool = release.tools.openshell[name];
37
+ const tool = releaseTools(release)[name];
38
38
  const file = resolve(dirname(releaseFile), tool.file);
39
39
  try {
40
40
  await verifyReleaseTool(file, tool.sha256);
@@ -15,12 +15,18 @@ const file = z.strictObject({
15
15
  }, "Tool downloads require HTTPS without credentials.")
16
16
  .optional(),
17
17
  });
18
+ export const hostPlatformSchema = z.enum([
19
+ "darwin-arm64",
20
+ "linux-arm64",
21
+ "linux-x64",
22
+ ]);
23
+ const tool = z.union([file, z.partialRecord(hostPlatformSchema, file)]);
18
24
  /** Pinned runtime metadata, independent of recipe defaults and deployment state. */
19
25
  export const releaseSchema = z.strictObject({
20
26
  schemaVersion: z.literal(1),
21
27
  version: z.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
22
28
  sourceRevision: z.string().regex(/^[a-f0-9]{40}$/),
23
- platforms: z.array(z.literal("darwin-arm64")).min(1).max(1),
29
+ platforms: z.array(hostPlatformSchema).min(1),
24
30
  images: z
25
31
  .strictObject({
26
32
  postgres: z.literal(postgresImage),
@@ -44,8 +50,29 @@ export const releaseSchema = z.strictObject({
44
50
  tools: z.strictObject({
45
51
  openshell: z.strictObject({
46
52
  version: z.string().min(1),
47
- cli: file,
48
- gateway: file,
53
+ cli: tool,
54
+ gateway: tool,
49
55
  }),
50
56
  }),
51
57
  });
58
+ /** Single-platform development bundles and multi-platform published bundles share resolution. */
59
+ export function releaseTools(release, platform = `${process.platform}-${process.arch}`) {
60
+ const host = hostPlatformSchema.parse(platform);
61
+ if (!release.platforms.includes(host))
62
+ throw new Error(`Runtime ${release.version} does not include ${host}.`);
63
+ const select = (value) => {
64
+ if ("file" in value) {
65
+ if (release.platforms.length !== 1)
66
+ throw new Error("Multi-platform runtimes require a tool for each platform.");
67
+ return value;
68
+ }
69
+ const selected = value[host];
70
+ if (!selected)
71
+ throw new Error(`Missing runtime tool for ${host}.`);
72
+ return selected;
73
+ };
74
+ return {
75
+ cli: select(release.tools.openshell.cli),
76
+ gateway: select(release.tools.openshell.gateway),
77
+ };
78
+ }
@@ -1,37 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "version": "0.1.0-alpha.1",
4
- "sourceRevision": "a53e95251321e61d8ffd6d06a6984f651c0b0db4",
5
- "platforms": [
6
- "darwin-arm64"
7
- ],
8
- "images": {
9
- "postgres": "postgres@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193",
10
- "gateway": "ghcr.io/clawscarf/clawscarf/runtime@sha256:eaa25c8e487970176b95de657df8bf4faff605f9f265ca760197800d9a1e5fcf",
11
- "companion": "ghcr.io/clawscarf/clawscarf/companion@sha256:97cfeb78ab56ba625b30aafe2249633d9b69f7f3c75de7116453385010e6281f",
12
- "openshellClient": "ghcr.io/clawscarf/clawscarf/openshell-client@sha256:669f37f654926fc7714783cc8b1b76bae9666895c2a4da959580037d281ebf56",
13
- "relay": "ghcr.io/clawscarf/clawscarf/browser-relay@sha256:5ee13e9496e3b76e076e91c59a861a10ad7a31a13b145384fba314d7f0b686b5",
14
- "models": "ghcr.io/berriai/litellm:v1.100.1@sha256:a3715fa7ad8387941ab697259bd2881d68931657247a41984f90fae6d11c62bf",
15
- "browser": {
16
- "chromium": "ghcr.io/clawscarf/clawscarf/browser@sha256:28ca9e4007d3b7c1bb9cb8f4bbaf275d1bc36ee2744079326b151b1f9eec78fd",
17
- "node": "ghcr.io/clawscarf/clawscarf/browser-node@sha256:2837d8b75df69fb39c7f7ca8fa68249d9de6e20d4e028213792de5f8f9ef1bd7",
18
- "dns": "ghcr.io/clawscarf/clawscarf/browser-dns@sha256:9f14cb85df522014c2bfc068e2b5788568f26b6e8d948cebe1250aff7630847b",
19
- "egress": "ghcr.io/clawscarf/clawscarf/browser-egress@sha256:bd819dffa4e1f60fbeed3b459a26b3b274300a4e5836bb90cbd080f6681d594c"
20
- }
21
- },
22
- "tools": {
23
- "openshell": {
24
- "version": "0.0.116",
25
- "cli": {
26
- "file": "tools/openshell",
27
- "sha256": "0baeeffe0e4c184b08fde910ac9e8815fb0b8cfb808e49c0e634d7db65fb03bb",
28
- "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.1/openshell-darwin-arm64"
29
- },
30
- "gateway": {
31
- "file": "tools/openshell-gateway",
32
- "sha256": "298706259ef18350f334c222e981e91583558511d50bce22727c689d261203e6",
33
- "url": "https://github.com/clawscarf/clawscarf/releases/download/v0.1.0-alpha.1/openshell-gateway-darwin-arm64"
34
- }
35
- }
36
- }
37
- }