mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 08:01:03 +00:00
feat: gigacode (#92)
This commit is contained in:
parent
0a73d1d8e8
commit
a02393436c
40 changed files with 2736 additions and 1327 deletions
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
|
|
@ -153,6 +153,7 @@ jobs:
|
||||||
|
|
||||||
COMMIT_SHA_SHORT="${GITHUB_SHA::7}"
|
COMMIT_SHA_SHORT="${GITHUB_SHA::7}"
|
||||||
BINARY_PATH="dist/sandbox-agent-${{ matrix.target }}${{ matrix.binary_ext }}"
|
BINARY_PATH="dist/sandbox-agent-${{ matrix.target }}${{ matrix.binary_ext }}"
|
||||||
|
GIGACODE_PATH="dist/gigacode-${{ matrix.target }}${{ matrix.binary_ext }}"
|
||||||
|
|
||||||
# Must specify --checksum-algorithm for compatibility with R2
|
# Must specify --checksum-algorithm for compatibility with R2
|
||||||
aws s3 cp \
|
aws s3 cp \
|
||||||
|
|
@ -162,6 +163,13 @@ jobs:
|
||||||
--endpoint-url https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com \
|
--endpoint-url https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com \
|
||||||
--checksum-algorithm CRC32
|
--checksum-algorithm CRC32
|
||||||
|
|
||||||
|
aws s3 cp \
|
||||||
|
"${GIGACODE_PATH}" \
|
||||||
|
"s3://rivet-releases/sandbox-agent/${COMMIT_SHA_SHORT}/binaries/gigacode-${{ matrix.target }}${{ matrix.binary_ext }}" \
|
||||||
|
--region auto \
|
||||||
|
--endpoint-url https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com \
|
||||||
|
--checksum-algorithm CRC32
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
name: "Build & Push Docker Images"
|
name: "Build & Push Docker Images"
|
||||||
needs: [setup]
|
needs: [setup]
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ The OpenCode compatibility suite lives at `server/packages/sandbox-agent/tests/o
|
||||||
SANDBOX_AGENT_SKIP_INSPECTOR=1 pnpm --filter @sandbox-agent/opencode-compat-tests test
|
SANDBOX_AGENT_SKIP_INSPECTOR=1 pnpm --filter @sandbox-agent/opencode-compat-tests test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
- The product name is "GigaCode" (capital G, capital C). The CLI binary/package is `gigacode` (lowercase).
|
||||||
|
|
||||||
## Git Commits
|
## Git Commits
|
||||||
|
|
||||||
- Do not include any co-authors in commit messages (no `Co-Authored-By` lines)
|
- Do not include any co-authors in commit messages (no `Co-Authored-By` lines)
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -189,6 +189,17 @@ npx sandbox-agent --help
|
||||||
bunx sandbox-agent --help
|
bunx sandbox-agent --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Quick OpenCode attach (runs `sandbox-agent opencode` by default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx gigacode --token "$SANDBOX_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g gigacode
|
||||||
|
gigacode --token "$SANDBOX_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
[CLI documentation](https://sandboxagent.dev/docs/cli)
|
[CLI documentation](https://sandboxagent.dev/docs/cli)
|
||||||
|
|
||||||
### Inspector
|
### Inspector
|
||||||
|
|
|
||||||
|
|
@ -17,30 +17,35 @@ case $TARGET in
|
||||||
DOCKERFILE="linux-x86_64.Dockerfile"
|
DOCKERFILE="linux-x86_64.Dockerfile"
|
||||||
TARGET_STAGE="x86_64-builder"
|
TARGET_STAGE="x86_64-builder"
|
||||||
BINARY="sandbox-agent-$TARGET"
|
BINARY="sandbox-agent-$TARGET"
|
||||||
|
GIGACODE="gigacode-$TARGET"
|
||||||
;;
|
;;
|
||||||
aarch64-unknown-linux-musl)
|
aarch64-unknown-linux-musl)
|
||||||
echo "Building for Linux aarch64 musl"
|
echo "Building for Linux aarch64 musl"
|
||||||
DOCKERFILE="linux-aarch64.Dockerfile"
|
DOCKERFILE="linux-aarch64.Dockerfile"
|
||||||
TARGET_STAGE="aarch64-builder"
|
TARGET_STAGE="aarch64-builder"
|
||||||
BINARY="sandbox-agent-$TARGET"
|
BINARY="sandbox-agent-$TARGET"
|
||||||
|
GIGACODE="gigacode-$TARGET"
|
||||||
;;
|
;;
|
||||||
x86_64-pc-windows-gnu)
|
x86_64-pc-windows-gnu)
|
||||||
echo "Building for Windows x86_64"
|
echo "Building for Windows x86_64"
|
||||||
DOCKERFILE="windows.Dockerfile"
|
DOCKERFILE="windows.Dockerfile"
|
||||||
TARGET_STAGE=""
|
TARGET_STAGE=""
|
||||||
BINARY="sandbox-agent-$TARGET.exe"
|
BINARY="sandbox-agent-$TARGET.exe"
|
||||||
|
GIGACODE="gigacode-$TARGET.exe"
|
||||||
;;
|
;;
|
||||||
x86_64-apple-darwin)
|
x86_64-apple-darwin)
|
||||||
echo "Building for macOS x86_64"
|
echo "Building for macOS x86_64"
|
||||||
DOCKERFILE="macos-x86_64.Dockerfile"
|
DOCKERFILE="macos-x86_64.Dockerfile"
|
||||||
TARGET_STAGE="x86_64-builder"
|
TARGET_STAGE="x86_64-builder"
|
||||||
BINARY="sandbox-agent-$TARGET"
|
BINARY="sandbox-agent-$TARGET"
|
||||||
|
GIGACODE="gigacode-$TARGET"
|
||||||
;;
|
;;
|
||||||
aarch64-apple-darwin)
|
aarch64-apple-darwin)
|
||||||
echo "Building for macOS aarch64"
|
echo "Building for macOS aarch64"
|
||||||
DOCKERFILE="macos-aarch64.Dockerfile"
|
DOCKERFILE="macos-aarch64.Dockerfile"
|
||||||
TARGET_STAGE="aarch64-builder"
|
TARGET_STAGE="aarch64-builder"
|
||||||
BINARY="sandbox-agent-$TARGET"
|
BINARY="sandbox-agent-$TARGET"
|
||||||
|
GIGACODE="gigacode-$TARGET"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unsupported target: $TARGET"
|
echo "Unsupported target: $TARGET"
|
||||||
|
|
@ -59,10 +64,13 @@ CONTAINER_ID=$(docker create "sandbox-agent-builder-$TARGET")
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
|
|
||||||
docker cp "$CONTAINER_ID:/artifacts/$BINARY" "dist/"
|
docker cp "$CONTAINER_ID:/artifacts/$BINARY" "dist/"
|
||||||
|
docker cp "$CONTAINER_ID:/artifacts/$GIGACODE" "dist/"
|
||||||
docker rm "$CONTAINER_ID"
|
docker rm "$CONTAINER_ID"
|
||||||
|
|
||||||
if [[ "$BINARY" != *.exe ]]; then
|
if [[ "$BINARY" != *.exe ]]; then
|
||||||
chmod +x "dist/$BINARY"
|
chmod +x "dist/$BINARY"
|
||||||
|
chmod +x "dist/$GIGACODE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Binary saved to: dist/$BINARY"
|
echo "Binary saved to: dist/$BINARY"
|
||||||
|
echo "Binary saved to: dist/$GIGACODE"
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,10 @@ COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/pac
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build -p sandbox-agent --release --target aarch64-unknown-linux-musl && \
|
cargo build -p sandbox-agent -p gigacode --release --target aarch64-unknown-linux-musl && \
|
||||||
mkdir -p /artifacts && \
|
mkdir -p /artifacts && \
|
||||||
cp target/aarch64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-aarch64-unknown-linux-musl
|
cp target/aarch64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-aarch64-unknown-linux-musl && \
|
||||||
|
cp target/aarch64-unknown-linux-musl/release/gigacode /artifacts/gigacode-aarch64-unknown-linux-musl
|
||||||
|
|
||||||
# Default command to show help
|
# Default command to show help
|
||||||
CMD ["ls", "-la", "/artifacts"]
|
CMD ["ls", "-la", "/artifacts"]
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,10 @@ COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/pac
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build -p sandbox-agent --release --target x86_64-unknown-linux-musl && \
|
cargo build -p sandbox-agent -p gigacode --release --target x86_64-unknown-linux-musl && \
|
||||||
mkdir -p /artifacts && \
|
mkdir -p /artifacts && \
|
||||||
cp target/x86_64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-x86_64-unknown-linux-musl
|
cp target/x86_64-unknown-linux-musl/release/sandbox-agent /artifacts/sandbox-agent-x86_64-unknown-linux-musl && \
|
||||||
|
cp target/x86_64-unknown-linux-musl/release/gigacode /artifacts/gigacode-x86_64-unknown-linux-musl
|
||||||
|
|
||||||
# Default command to show help
|
# Default command to show help
|
||||||
CMD ["ls", "-la", "/artifacts"]
|
CMD ["ls", "-la", "/artifacts"]
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,10 @@ COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/pac
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build -p sandbox-agent --release --target aarch64-apple-darwin && \
|
cargo build -p sandbox-agent -p gigacode --release --target aarch64-apple-darwin && \
|
||||||
mkdir -p /artifacts && \
|
mkdir -p /artifacts && \
|
||||||
cp target/aarch64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-aarch64-apple-darwin
|
cp target/aarch64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-aarch64-apple-darwin && \
|
||||||
|
cp target/aarch64-apple-darwin/release/gigacode /artifacts/gigacode-aarch64-apple-darwin
|
||||||
|
|
||||||
# Default command to show help
|
# Default command to show help
|
||||||
CMD ["ls", "-la", "/artifacts"]
|
CMD ["ls", "-la", "/artifacts"]
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,10 @@ COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/pac
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build -p sandbox-agent --release --target x86_64-apple-darwin && \
|
cargo build -p sandbox-agent -p gigacode --release --target x86_64-apple-darwin && \
|
||||||
mkdir -p /artifacts && \
|
mkdir -p /artifacts && \
|
||||||
cp target/x86_64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-x86_64-apple-darwin
|
cp target/x86_64-apple-darwin/release/sandbox-agent /artifacts/sandbox-agent-x86_64-apple-darwin && \
|
||||||
|
cp target/x86_64-apple-darwin/release/gigacode /artifacts/gigacode-x86_64-apple-darwin
|
||||||
|
|
||||||
# Default command to show help
|
# Default command to show help
|
||||||
CMD ["ls", "-la", "/artifacts"]
|
CMD ["ls", "-la", "/artifacts"]
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,10 @@ COPY --from=inspector-build /app/frontend/packages/inspector/dist ./frontend/pac
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
--mount=type=cache,target=/usr/local/cargo/git \
|
||||||
--mount=type=cache,target=/build/target \
|
--mount=type=cache,target=/build/target \
|
||||||
cargo build -p sandbox-agent --release --target x86_64-pc-windows-gnu && \
|
cargo build -p sandbox-agent -p gigacode --release --target x86_64-pc-windows-gnu && \
|
||||||
mkdir -p /artifacts && \
|
mkdir -p /artifacts && \
|
||||||
cp target/x86_64-pc-windows-gnu/release/sandbox-agent.exe /artifacts/sandbox-agent-x86_64-pc-windows-gnu.exe
|
cp target/x86_64-pc-windows-gnu/release/sandbox-agent.exe /artifacts/sandbox-agent-x86_64-pc-windows-gnu.exe && \
|
||||||
|
cp target/x86_64-pc-windows-gnu/release/gigacode.exe /artifacts/gigacode-x86_64-pc-windows-gnu.exe
|
||||||
|
|
||||||
# Default command to show help
|
# Default command to show help
|
||||||
CMD ["ls", "-la", "/artifacts"]
|
CMD ["ls", "-la", "/artifacts"]
|
||||||
|
|
|
||||||
51
docs/cli.mdx
51
docs/cli.mdx
|
|
@ -58,7 +58,7 @@ sandbox-agent install-agent claude --reinstall
|
||||||
|
|
||||||
## OpenCode (Experimental)
|
## OpenCode (Experimental)
|
||||||
|
|
||||||
Start a sandbox-agent server and attach an OpenCode session (uses `opencode attach`):
|
Start (or reuse) a sandbox-agent daemon and attach an OpenCode session (uses `opencode attach`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sandbox-agent opencode [OPTIONS]
|
sandbox-agent opencode [OPTIONS]
|
||||||
|
|
@ -77,7 +77,54 @@ sandbox-agent opencode [OPTIONS]
|
||||||
sandbox-agent opencode --token "$TOKEN"
|
sandbox-agent opencode --token "$TOKEN"
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires the `opencode` binary to be installed (or set `OPENCODE_BIN` / `--opencode-bin`).
|
The daemon logs to a per-host log file under the sandbox-agent data directory (for example, `~/.local/share/sandbox-agent/daemon/daemon-127-0-0-1-2468.log`).
|
||||||
|
|
||||||
|
Requires the `opencode` binary to be installed (or set `OPENCODE_BIN` / `--opencode-bin`). If it is not found on `PATH`, sandbox-agent installs it automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Daemon
|
||||||
|
|
||||||
|
Manage the background daemon. See the [Daemon](/daemon) docs for details on lifecycle and auto-upgrade.
|
||||||
|
|
||||||
|
### Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon start [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host to bind to |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port to bind to |
|
||||||
|
| `-t, --token <TOKEN>` | - | Authentication token |
|
||||||
|
| `-n, --no-token` | - | Disable authentication |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon start --no-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon stop [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host of the daemon |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port of the daemon |
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon status [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host of the daemon |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port of the daemon |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
96
docs/daemon.mdx
Normal file
96
docs/daemon.mdx
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
---
|
||||||
|
title: "Daemon"
|
||||||
|
description: "Background daemon lifecycle, auto-upgrade, and management."
|
||||||
|
icon: "microchip"
|
||||||
|
---
|
||||||
|
|
||||||
|
The sandbox-agent daemon is a background server process that stays running between sessions. Commands like `sandbox-agent opencode` and `gigacode` automatically start it when needed and restart it when the binary is updated.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
1. When you run `sandbox-agent opencode`, `sandbox-agent daemon start`, or `gigacode`, the CLI checks if a daemon is already healthy at the configured host and port.
|
||||||
|
2. If no daemon is running, one is spawned in the background with stdout/stderr redirected to a log file.
|
||||||
|
3. The CLI writes a PID file and a build ID file to track the running process and its version.
|
||||||
|
4. On subsequent invocations, if the daemon is still running but was built from a different commit, the CLI automatically stops the old daemon and starts a new one.
|
||||||
|
|
||||||
|
## Auto-upgrade
|
||||||
|
|
||||||
|
Each build of sandbox-agent embeds a unique **build ID** (the git short hash, or a version-timestamp fallback). When a daemon is started, this build ID is written to a version file alongside the PID file.
|
||||||
|
|
||||||
|
On every invocation of `ensure_running` (called by `opencode`, `gigacode`, and `daemon start`), the CLI compares the stored build ID against the current binary's build ID. If they differ, the running daemon is stopped and replaced automatically:
|
||||||
|
|
||||||
|
```
|
||||||
|
daemon outdated (build a1b2c3d -> f4e5d6c), restarting...
|
||||||
|
```
|
||||||
|
|
||||||
|
This means installing a new version of sandbox-agent and running any daemon-aware command is enough to upgrade — no manual restart needed.
|
||||||
|
|
||||||
|
## Managing the daemon
|
||||||
|
|
||||||
|
### Start
|
||||||
|
|
||||||
|
Start a daemon in the background. If one is already running and healthy, this is a no-op.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon start [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host to bind to |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port to bind to |
|
||||||
|
| `-t, --token <TOKEN>` | - | Authentication token |
|
||||||
|
| `-n, --no-token` | - | Disable authentication |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon start --no-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop
|
||||||
|
|
||||||
|
Stop a running daemon. Sends SIGTERM and waits up to 5 seconds for a graceful shutdown before falling back to SIGKILL.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon stop [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host of the daemon |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port of the daemon |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon stop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
Show whether the daemon is running, its PID, build ID, and log path.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon status [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `-H, --host <HOST>` | `127.0.0.1` | Host of the daemon |
|
||||||
|
| `-p, --port <PORT>` | `2468` | Port of the daemon |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sandbox-agent daemon status
|
||||||
|
# Daemon running (PID 12345, build a1b2c3d, logs: ~/.local/share/sandbox-agent/daemon/daemon-127-0-0-1-2468.log)
|
||||||
|
```
|
||||||
|
|
||||||
|
If the daemon was started with an older binary, the status includes an `[outdated, restart recommended]` notice.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
All daemon state files live under the sandbox-agent data directory (typically `~/.local/share/sandbox-agent/daemon/`):
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `daemon-{host}-{port}.pid` | PID of the running daemon process |
|
||||||
|
| `daemon-{host}-{port}.version` | Build ID of the running daemon |
|
||||||
|
| `daemon-{host}-{port}.log` | Daemon stdout/stderr log output |
|
||||||
|
|
||||||
|
Multiple daemons can run on different host/port combinations without conflicting.
|
||||||
|
|
@ -41,7 +41,12 @@
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"group": "Getting started",
|
"group": "Getting started",
|
||||||
"pages": ["quickstart", "building-chat-ui", "manage-sessions", "opencode-compatibility"]
|
"pages": [
|
||||||
|
"quickstart",
|
||||||
|
"building-chat-ui",
|
||||||
|
"manage-sessions",
|
||||||
|
"opencode-compatibility"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Deploy",
|
"group": "Deploy",
|
||||||
|
|
@ -65,14 +70,14 @@
|
||||||
"cli",
|
"cli",
|
||||||
"inspector",
|
"inspector",
|
||||||
"session-transcript-schema",
|
"session-transcript-schema",
|
||||||
"cors",
|
"gigacode",
|
||||||
{
|
{
|
||||||
"group": "AI",
|
"group": "AI",
|
||||||
"pages": ["ai/skill", "ai/llms-txt"]
|
"pages": ["ai/skill", "ai/llms-txt"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Advanced",
|
"group": "Advanced",
|
||||||
"pages": ["telemetry"]
|
"pages": ["daemon", "cors", "telemetry"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
6
docs/gigacode.mdx
Normal file
6
docs/gigacode.mdx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: GigaCode
|
||||||
|
url: "https://github.com/rivet-dev/sandbox-agent/tree/main/server/packages/gigacode"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Apache-2.0"
|
"name": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"version": "0.1.6-rc.1"
|
"version": "0.1.6"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,9 @@ export function GetStarted() {
|
||||||
<p className="text-lg text-zinc-400">
|
<p className="text-lg text-zinc-400">
|
||||||
Choose the installation method that works best for your use case.
|
Choose the installation method that works best for your use case.
|
||||||
</p>
|
</p>
|
||||||
|
<p className="mt-4 text-sm text-zinc-500">
|
||||||
|
Quick OpenCode attach: <span className="font-mono text-white">npx gigacode</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
|
|
|
||||||
17
justfile
17
justfile
|
|
@ -51,3 +51,20 @@ fmt:
|
||||||
[group('dev')]
|
[group('dev')]
|
||||||
dev-docs:
|
dev-docs:
|
||||||
cd docs && pnpm dlx mintlify dev
|
cd docs && pnpm dlx mintlify dev
|
||||||
|
|
||||||
|
install:
|
||||||
|
pnpm install
|
||||||
|
pnpm build --filter @sandbox-agent/inspector...
|
||||||
|
cargo install --path server/packages/sandbox-agent --debug
|
||||||
|
cargo install --path server/packages/gigacode --debug
|
||||||
|
|
||||||
|
install-fast:
|
||||||
|
SANDBOX_AGENT_SKIP_INSPECTOR=1 cargo install --path server/packages/sandbox-agent --debug
|
||||||
|
SANDBOX_AGENT_SKIP_INSPECTOR=1 cargo install --path server/packages/gigacode --debug
|
||||||
|
|
||||||
|
install-release:
|
||||||
|
pnpm install
|
||||||
|
pnpm build --filter @sandbox-agent/inspector...
|
||||||
|
cargo install --path server/packages/sandbox-agent
|
||||||
|
cargo install --path server/packages/gigacode
|
||||||
|
|
||||||
|
|
|
||||||
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
|
|
@ -382,6 +382,42 @@ importers:
|
||||||
|
|
||||||
sdks/cli/platforms/win32-x64: {}
|
sdks/cli/platforms/win32-x64: {}
|
||||||
|
|
||||||
|
sdks/gigacode:
|
||||||
|
dependencies:
|
||||||
|
'@sandbox-agent/cli-shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../cli-shared
|
||||||
|
optionalDependencies:
|
||||||
|
gigacode-darwin-arm64:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:platforms/darwin-arm64
|
||||||
|
gigacode-darwin-x64:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:platforms/darwin-x64
|
||||||
|
gigacode-linux-arm64:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:platforms/linux-arm64
|
||||||
|
gigacode-linux-x64:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:platforms/linux-x64
|
||||||
|
gigacode-win32-x64:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:platforms/win32-x64
|
||||||
|
devDependencies:
|
||||||
|
vitest:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
|
sdks/gigacode/platforms/darwin-arm64: {}
|
||||||
|
|
||||||
|
sdks/gigacode/platforms/darwin-x64: {}
|
||||||
|
|
||||||
|
sdks/gigacode/platforms/linux-arm64: {}
|
||||||
|
|
||||||
|
sdks/gigacode/platforms/linux-x64: {}
|
||||||
|
|
||||||
|
sdks/gigacode/platforms/win32-x64: {}
|
||||||
|
|
||||||
sdks/typescript:
|
sdks/typescript:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sandbox-agent/cli-shared':
|
'@sandbox-agent/cli-shared':
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ packages:
|
||||||
- "sdks/*"
|
- "sdks/*"
|
||||||
- "sdks/cli"
|
- "sdks/cli"
|
||||||
- "sdks/cli/platforms/*"
|
- "sdks/cli/platforms/*"
|
||||||
|
- "sdks/gigacode"
|
||||||
|
- "sdks/gigacode/platforms/*"
|
||||||
- "resources/agent-schemas"
|
- "resources/agent-schemas"
|
||||||
- "resources/vercel-ai-sdk-schemas"
|
- "resources/vercel-ai-sdk-schemas"
|
||||||
- "scripts/release"
|
- "scripts/release"
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ export async function promoteArtifacts(opts: ReleaseOpts) {
|
||||||
if (opts.latest) {
|
if (opts.latest) {
|
||||||
await uploadInstallScripts(opts, "latest");
|
await uploadInstallScripts(opts, "latest");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload gigacode install scripts
|
||||||
|
await uploadGigacodeInstallScripts(opts, opts.version);
|
||||||
|
if (opts.latest) {
|
||||||
|
await uploadGigacodeInstallScripts(opts, "latest");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,6 +61,23 @@ async function uploadInstallScripts(opts: ReleaseOpts, version: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function uploadGigacodeInstallScripts(opts: ReleaseOpts, version: string) {
|
||||||
|
const installScriptPaths = [
|
||||||
|
path.resolve(opts.root, "scripts/release/static/gigacode-install.sh"),
|
||||||
|
path.resolve(opts.root, "scripts/release/static/gigacode-install.ps1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const scriptPath of installScriptPaths) {
|
||||||
|
let scriptContent = await fs.readFile(scriptPath, "utf-8");
|
||||||
|
scriptContent = scriptContent.replace(/__VERSION__/g, version);
|
||||||
|
|
||||||
|
const uploadKey = `${PREFIX}/${version}/${scriptPath.split("/").pop() ?? ""}`;
|
||||||
|
|
||||||
|
console.log(`Uploading gigacode install script: ${uploadKey}`);
|
||||||
|
await uploadContentToReleases(scriptContent, uploadKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function copyPath(sourcePrefix: string, targetPrefix: string) {
|
async function copyPath(sourcePrefix: string, targetPrefix: string) {
|
||||||
console.log(`Copying ${sourcePrefix} -> ${targetPrefix}`);
|
console.log(`Copying ${sourcePrefix} -> ${targetPrefix}`);
|
||||||
await deleteReleasesPath(targetPrefix);
|
await deleteReleasesPath(targetPrefix);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const CRATES = [
|
||||||
"universal-agent-schema",
|
"universal-agent-schema",
|
||||||
"agent-management",
|
"agent-management",
|
||||||
"sandbox-agent",
|
"sandbox-agent",
|
||||||
|
"gigacode",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// NPM CLI packages
|
// NPM CLI packages
|
||||||
|
|
@ -22,15 +23,69 @@ const CLI_PACKAGES = [
|
||||||
"@sandbox-agent/cli-win32-x64",
|
"@sandbox-agent/cli-win32-x64",
|
||||||
"@sandbox-agent/cli-darwin-x64",
|
"@sandbox-agent/cli-darwin-x64",
|
||||||
"@sandbox-agent/cli-darwin-arm64",
|
"@sandbox-agent/cli-darwin-arm64",
|
||||||
|
"gigacode",
|
||||||
|
"gigacode-linux-x64",
|
||||||
|
"gigacode-linux-arm64",
|
||||||
|
"gigacode-win32-x64",
|
||||||
|
"gigacode-darwin-x64",
|
||||||
|
"gigacode-darwin-arm64",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Mapping from npm package name to Rust target and binary extension
|
// Mapping from npm package name to Rust target and binary extension
|
||||||
const CLI_PLATFORM_MAP: Record<string, { target: string; binaryExt: string }> = {
|
const CLI_PLATFORM_MAP: Record<
|
||||||
"@sandbox-agent/cli-linux-x64": { target: "x86_64-unknown-linux-musl", binaryExt: "" },
|
string,
|
||||||
"@sandbox-agent/cli-linux-arm64": { target: "aarch64-unknown-linux-musl", binaryExt: "" },
|
{ target: string; binaryExt: string; binaryName: string }
|
||||||
"@sandbox-agent/cli-win32-x64": { target: "x86_64-pc-windows-gnu", binaryExt: ".exe" },
|
> = {
|
||||||
"@sandbox-agent/cli-darwin-x64": { target: "x86_64-apple-darwin", binaryExt: "" },
|
"@sandbox-agent/cli-linux-x64": {
|
||||||
"@sandbox-agent/cli-darwin-arm64": { target: "aarch64-apple-darwin", binaryExt: "" },
|
target: "x86_64-unknown-linux-musl",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "sandbox-agent",
|
||||||
|
},
|
||||||
|
"@sandbox-agent/cli-linux-arm64": {
|
||||||
|
target: "aarch64-unknown-linux-musl",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "sandbox-agent",
|
||||||
|
},
|
||||||
|
"@sandbox-agent/cli-win32-x64": {
|
||||||
|
target: "x86_64-pc-windows-gnu",
|
||||||
|
binaryExt: ".exe",
|
||||||
|
binaryName: "sandbox-agent",
|
||||||
|
},
|
||||||
|
"@sandbox-agent/cli-darwin-x64": {
|
||||||
|
target: "x86_64-apple-darwin",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "sandbox-agent",
|
||||||
|
},
|
||||||
|
"@sandbox-agent/cli-darwin-arm64": {
|
||||||
|
target: "aarch64-apple-darwin",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "sandbox-agent",
|
||||||
|
},
|
||||||
|
"gigacode-linux-x64": {
|
||||||
|
target: "x86_64-unknown-linux-musl",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "gigacode",
|
||||||
|
},
|
||||||
|
"gigacode-linux-arm64": {
|
||||||
|
target: "aarch64-unknown-linux-musl",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "gigacode",
|
||||||
|
},
|
||||||
|
"gigacode-win32-x64": {
|
||||||
|
target: "x86_64-pc-windows-gnu",
|
||||||
|
binaryExt: ".exe",
|
||||||
|
binaryName: "gigacode",
|
||||||
|
},
|
||||||
|
"gigacode-darwin-x64": {
|
||||||
|
target: "x86_64-apple-darwin",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "gigacode",
|
||||||
|
},
|
||||||
|
"gigacode-darwin-arm64": {
|
||||||
|
target: "aarch64-apple-darwin",
|
||||||
|
binaryExt: "",
|
||||||
|
binaryName: "gigacode",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function npmVersionExists(
|
async function npmVersionExists(
|
||||||
|
|
@ -246,18 +301,27 @@ export async function publishNpmCli(opts: ReleaseOpts) {
|
||||||
let packagePath: string;
|
let packagePath: string;
|
||||||
if (packageName === "@sandbox-agent/cli") {
|
if (packageName === "@sandbox-agent/cli") {
|
||||||
packagePath = join(opts.root, "sdks/cli");
|
packagePath = join(opts.root, "sdks/cli");
|
||||||
} else {
|
} else if (packageName === "gigacode") {
|
||||||
|
packagePath = join(opts.root, "sdks/gigacode");
|
||||||
|
} else if (packageName.startsWith("@sandbox-agent/cli-")) {
|
||||||
// Platform-specific packages: @sandbox-agent/cli-linux-x64 -> sdks/cli/platforms/linux-x64
|
// Platform-specific packages: @sandbox-agent/cli-linux-x64 -> sdks/cli/platforms/linux-x64
|
||||||
const platform = packageName.replace("@sandbox-agent/cli-", "");
|
const platform = packageName.replace("@sandbox-agent/cli-", "");
|
||||||
packagePath = join(opts.root, "sdks/cli/platforms", platform);
|
packagePath = join(opts.root, "sdks/cli/platforms", platform);
|
||||||
|
} else if (packageName.startsWith("gigacode-")) {
|
||||||
|
// Platform-specific packages: gigacode-linux-x64 -> sdks/gigacode/platforms/linux-x64
|
||||||
|
const platform = packageName.replace("gigacode-", "");
|
||||||
|
packagePath = join(opts.root, "sdks/gigacode/platforms", platform);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown CLI package: ${packageName}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Download binary from R2 for platform-specific packages
|
// Download binary from R2 for platform-specific packages
|
||||||
const platformInfo = CLI_PLATFORM_MAP[packageName];
|
const platformInfo = CLI_PLATFORM_MAP[packageName];
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
const binDir = join(packagePath, "bin");
|
const binDir = join(packagePath, "bin");
|
||||||
const binaryName = `sandbox-agent${platformInfo.binaryExt}`;
|
const binaryName = `${platformInfo.binaryName}${platformInfo.binaryExt}`;
|
||||||
const localBinaryPath = join(binDir, binaryName);
|
const localBinaryPath = join(binDir, binaryName);
|
||||||
const remoteBinaryPath = `${PREFIX}/${sourceCommit}/binaries/sandbox-agent-${platformInfo.target}${platformInfo.binaryExt}`;
|
const remoteBinaryPath = `${PREFIX}/${sourceCommit}/binaries/${platformInfo.binaryName}-${platformInfo.target}${platformInfo.binaryExt}`;
|
||||||
|
|
||||||
console.log(`==> Downloading binary for ${packageName}`);
|
console.log(`==> Downloading binary for ${packageName}`);
|
||||||
console.log(` From: ${remoteBinaryPath}`);
|
console.log(` From: ${remoteBinaryPath}`);
|
||||||
|
|
@ -274,7 +338,6 @@ export async function publishNpmCli(opts: ReleaseOpts) {
|
||||||
await fs.chmod(localBinaryPath, 0o755);
|
await fs.chmod(localBinaryPath, 0o755);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Publish
|
// Publish
|
||||||
console.log(`==> Publishing to NPM: ${packageName}@${opts.version}`);
|
console.log(`==> Publishing to NPM: ${packageName}@${opts.version}`);
|
||||||
|
|
|
||||||
51
scripts/release/static/gigacode-install.ps1
Normal file
51
scripts/release/static/gigacode-install.ps1
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
# Create bin directory for gigacode
|
||||||
|
$BinDir = $env:BIN_DIR
|
||||||
|
$GigacodeInstall = if ($BinDir) {
|
||||||
|
$BinDir
|
||||||
|
} else {
|
||||||
|
"${Home}\.gigacode\bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path $GigacodeInstall)) {
|
||||||
|
New-Item $GigacodeInstall -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$GigacodeExe = "$GigacodeInstall\gigacode.exe"
|
||||||
|
$Version = '__VERSION__'
|
||||||
|
$FileName = 'gigacode-x86_64-pc-windows-gnu.exe'
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
Write-Host "> Installing gigacode ${Version}"
|
||||||
|
|
||||||
|
# Download binary
|
||||||
|
$DownloadUrl = "https://releases.rivet.dev/sandbox-agent/${Version}/binaries/${FileName}"
|
||||||
|
Write-Host
|
||||||
|
Write-Host "> Downloading ${DownloadUrl}"
|
||||||
|
Invoke-WebRequest $DownloadUrl -OutFile $GigacodeExe -UseBasicParsing
|
||||||
|
|
||||||
|
# Install to PATH
|
||||||
|
Write-Host
|
||||||
|
Write-Host "> Installing gigacode"
|
||||||
|
$User = [System.EnvironmentVariableTarget]::User
|
||||||
|
$Path = [System.Environment]::GetEnvironmentVariable('Path', $User)
|
||||||
|
if (!(";${Path};".ToLower() -like "*;${GigacodeInstall};*".ToLower())) {
|
||||||
|
[System.Environment]::SetEnvironmentVariable('Path', "${Path};${GigacodeInstall}", $User)
|
||||||
|
$Env:Path += ";${GigacodeInstall}"
|
||||||
|
Write-Host "Please restart your PowerShell session or run the following command to refresh the environment variables:"
|
||||||
|
Write-Host "[System.Environment]::SetEnvironmentVariable('Path', '${Path};${GigacodeInstall}', [System.EnvironmentVariableTarget]::Process)"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
Write-Host "> Checking installation"
|
||||||
|
gigacode.exe --version
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
Write-Host "gigacode was installed successfully to ${GigacodeExe}."
|
||||||
|
Write-Host "Run 'gigacode --help' to get started."
|
||||||
|
Write-Host
|
||||||
103
scripts/release/static/gigacode-install.sh
Normal file
103
scripts/release/static/gigacode-install.sh
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# shellcheck enable=add-default-case
|
||||||
|
# shellcheck enable=avoid-nullary-conditions
|
||||||
|
# shellcheck enable=check-unassigned-uppercase
|
||||||
|
# shellcheck enable=deprecate-which
|
||||||
|
# shellcheck enable=quote-safe-variables
|
||||||
|
# shellcheck enable=require-variable-braces
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
rm -rf /tmp/gigacode_install
|
||||||
|
mkdir /tmp/gigacode_install
|
||||||
|
cd /tmp/gigacode_install
|
||||||
|
|
||||||
|
GIGACODE_VERSION="${GIGACODE_VERSION:-__VERSION__}"
|
||||||
|
UNAME="$(uname -s)"
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
|
||||||
|
# Find asset suffix
|
||||||
|
if [ "$(printf '%s' "$UNAME" | cut -c 1-6)" = "Darwin" ]; then
|
||||||
|
echo
|
||||||
|
echo "> Detected macOS"
|
||||||
|
|
||||||
|
if [ "$ARCH" = "x86_64" ]; then
|
||||||
|
FILE_NAME="gigacode-x86_64-apple-darwin"
|
||||||
|
elif [ "$ARCH" = "arm64" ]; then
|
||||||
|
FILE_NAME="gigacode-aarch64-apple-darwin"
|
||||||
|
else
|
||||||
|
echo "Unknown arch $ARCH" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif [ "$(printf '%s' "$UNAME" | cut -c 1-5)" = "Linux" ]; then
|
||||||
|
echo
|
||||||
|
echo "> Detected Linux ($(getconf LONG_BIT) bit)"
|
||||||
|
|
||||||
|
FILE_NAME="gigacode-x86_64-unknown-linux-musl"
|
||||||
|
else
|
||||||
|
echo "Unable to determine platform" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine install location
|
||||||
|
set +u
|
||||||
|
if [ -z "$BIN_DIR" ]; then
|
||||||
|
BIN_DIR="/usr/local/bin"
|
||||||
|
fi
|
||||||
|
set -u
|
||||||
|
INSTALL_PATH="$BIN_DIR/gigacode"
|
||||||
|
|
||||||
|
if [ ! -d "$BIN_DIR" ]; then
|
||||||
|
# Find the base parent directory. We're using mkdir -p, which recursively creates directories, so we can't rely on `dirname`.
|
||||||
|
CHECK_DIR="$BIN_DIR"
|
||||||
|
while [ ! -d "$CHECK_DIR" ] && [ "$CHECK_DIR" != "/" ]; do
|
||||||
|
CHECK_DIR=$(dirname "$CHECK_DIR")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if the directory is writable
|
||||||
|
if [ ! -w "$CHECK_DIR" ]; then
|
||||||
|
echo
|
||||||
|
echo "> Creating directory $BIN_DIR (requires sudo)"
|
||||||
|
sudo mkdir -p "$BIN_DIR"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "> Creating directory $BIN_DIR"
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download binary
|
||||||
|
URL="https://releases.rivet.dev/sandbox-agent/${GIGACODE_VERSION}/binaries/${FILE_NAME}"
|
||||||
|
echo
|
||||||
|
echo "> Downloading $URL"
|
||||||
|
curl -fsSL "$URL" -o gigacode
|
||||||
|
chmod +x gigacode
|
||||||
|
|
||||||
|
# Move binary
|
||||||
|
if [ ! -w "$BIN_DIR" ]; then
|
||||||
|
echo
|
||||||
|
echo "> Installing gigacode to $INSTALL_PATH (requires sudo)"
|
||||||
|
sudo mv ./gigacode "$INSTALL_PATH"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "> Installing gigacode to $INSTALL_PATH"
|
||||||
|
mv ./gigacode "$INSTALL_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if path may be incorrect
|
||||||
|
case ":$PATH:" in
|
||||||
|
*:$BIN_DIR:*) ;;
|
||||||
|
*)
|
||||||
|
echo "WARNING: $BIN_DIR is not in \$PATH"
|
||||||
|
echo "For instructions on how to add it to your PATH, visit:"
|
||||||
|
echo "https://opensource.com/article/17/6/set-path-linux"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "> Checking installation"
|
||||||
|
"$BIN_DIR/gigacode" --version
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "gigacode was installed successfully."
|
||||||
|
echo "Run 'gigacode --help' to get started."
|
||||||
|
|
@ -32,11 +32,21 @@ export async function updateVersion(opts: ReleaseOpts) {
|
||||||
find: /"version": ".*"/,
|
find: /"version": ".*"/,
|
||||||
replace: `"version": "${opts.version}"`,
|
replace: `"version": "${opts.version}"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "sdks/gigacode/package.json",
|
||||||
|
find: /"version": ".*"/,
|
||||||
|
replace: `"version": "${opts.version}"`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "sdks/cli/platforms/*/package.json",
|
path: "sdks/cli/platforms/*/package.json",
|
||||||
find: /"version": ".*"/,
|
find: /"version": ".*"/,
|
||||||
replace: `"version": "${opts.version}"`,
|
replace: `"version": "${opts.version}"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "sdks/gigacode/platforms/*/package.json",
|
||||||
|
find: /"version": ".*"/,
|
||||||
|
replace: `"version": "${opts.version}"`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Update internal crate versions in workspace dependencies
|
// Update internal crate versions in workspace dependencies
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export type NonExecutableBinaryMessageOptions = {
|
||||||
trustPackages: string;
|
trustPackages: string;
|
||||||
bunInstallBlocks: InstallCommandBlock[];
|
bunInstallBlocks: InstallCommandBlock[];
|
||||||
genericInstallCommands?: string[];
|
genericInstallCommands?: string[];
|
||||||
|
binaryName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FsSubset = {
|
export type FsSubset = {
|
||||||
|
|
@ -63,10 +64,16 @@ export function assertExecutable(binPath: string, fs: FsSubset): boolean {
|
||||||
export function formatNonExecutableBinaryMessage(
|
export function formatNonExecutableBinaryMessage(
|
||||||
options: NonExecutableBinaryMessageOptions,
|
options: NonExecutableBinaryMessageOptions,
|
||||||
): string {
|
): string {
|
||||||
const { binPath, trustPackages, bunInstallBlocks, genericInstallCommands } =
|
const {
|
||||||
options;
|
binPath,
|
||||||
|
trustPackages,
|
||||||
|
bunInstallBlocks,
|
||||||
|
genericInstallCommands,
|
||||||
|
binaryName,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const lines = [`sandbox-agent binary is not executable: ${binPath}`];
|
const label = binaryName ?? "sandbox-agent";
|
||||||
|
const lines = [`${label} binary is not executable: ${binPath}`];
|
||||||
|
|
||||||
if (isBunRuntime()) {
|
if (isBunRuntime()) {
|
||||||
lines.push(
|
lines.push(
|
||||||
|
|
|
||||||
66
sdks/gigacode/bin/gigacode
Normal file
66
sdks/gigacode/bin/gigacode
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const { execFileSync } = require("child_process");
|
||||||
|
const {
|
||||||
|
assertExecutable,
|
||||||
|
formatNonExecutableBinaryMessage,
|
||||||
|
} = require("@sandbox-agent/cli-shared");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const TRUST_PACKAGES =
|
||||||
|
"gigacode-linux-x64 gigacode-linux-arm64 gigacode-darwin-arm64 gigacode-darwin-x64 gigacode-win32-x64";
|
||||||
|
|
||||||
|
function formatHint(binPath) {
|
||||||
|
return formatNonExecutableBinaryMessage({
|
||||||
|
binPath,
|
||||||
|
binaryName: "gigacode",
|
||||||
|
trustPackages: TRUST_PACKAGES,
|
||||||
|
bunInstallBlocks: [
|
||||||
|
{
|
||||||
|
label: "Project install",
|
||||||
|
commands: [
|
||||||
|
`bun pm trust ${TRUST_PACKAGES}`,
|
||||||
|
"bun add gigacode",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Global install",
|
||||||
|
commands: [
|
||||||
|
`bun pm -g trust ${TRUST_PACKAGES}`,
|
||||||
|
"bun add -g gigacode",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLATFORMS = {
|
||||||
|
"darwin-arm64": "gigacode-darwin-arm64",
|
||||||
|
"darwin-x64": "gigacode-darwin-x64",
|
||||||
|
"linux-x64": "gigacode-linux-x64",
|
||||||
|
"linux-arm64": "gigacode-linux-arm64",
|
||||||
|
"win32-x64": "gigacode-win32-x64",
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = `${process.platform}-${process.arch}`;
|
||||||
|
const pkg = PLATFORMS[key];
|
||||||
|
if (!pkg) {
|
||||||
|
console.error(`Unsupported platform: ${key}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pkgPath = require.resolve(`${pkg}/package.json`);
|
||||||
|
const bin = process.platform === "win32" ? "gigacode.exe" : "gigacode";
|
||||||
|
const binPath = path.join(path.dirname(pkgPath), "bin", bin);
|
||||||
|
|
||||||
|
if (!assertExecutable(binPath, fs)) {
|
||||||
|
console.error(formatHint(binPath));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status !== undefined) process.exit(e.status);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
32
sdks/gigacode/package.json
Normal file
32
sdks/gigacode/package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "Gigacode CLI (sandbox-agent with OpenCode attach by default)",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"gigacode": "bin/gigacode"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest run"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@sandbox-agent/cli-shared": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^3.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"gigacode-linux-x64": "workspace:*",
|
||||||
|
"gigacode-linux-arm64": "workspace:*",
|
||||||
|
"gigacode-darwin-arm64": "workspace:*",
|
||||||
|
"gigacode-darwin-x64": "workspace:*",
|
||||||
|
"gigacode-win32-x64": "workspace:*"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
sdks/gigacode/platforms/darwin-arm64/package.json
Normal file
22
sdks/gigacode/platforms/darwin-arm64/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode-darwin-arm64",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "gigacode CLI binary for macOS arm64",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "chmod +x bin/gigacode || true"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
sdks/gigacode/platforms/darwin-x64/package.json
Normal file
22
sdks/gigacode/platforms/darwin-x64/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode-darwin-x64",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "gigacode CLI binary for macOS x64",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "chmod +x bin/gigacode || true"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
sdks/gigacode/platforms/linux-arm64/package.json
Normal file
22
sdks/gigacode/platforms/linux-arm64/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode-linux-arm64",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "gigacode CLI binary for Linux arm64",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "chmod +x bin/gigacode || true"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
sdks/gigacode/platforms/linux-x64/package.json
Normal file
22
sdks/gigacode/platforms/linux-x64/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode-linux-x64",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "gigacode CLI binary for Linux x64",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "chmod +x bin/gigacode || true"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
19
sdks/gigacode/platforms/win32-x64/package.json
Normal file
19
sdks/gigacode/platforms/win32-x64/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "gigacode-win32-x64",
|
||||||
|
"version": "0.1.6",
|
||||||
|
"description": "gigacode CLI binary for Windows x64",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
server/packages/gigacode/Cargo.toml
Normal file
17
server/packages/gigacode/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "gigacode"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
description = "Sandbox Agent CLI with OpenCode attach by default"
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "gigacode"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap.workspace = true
|
||||||
|
sandbox-agent.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
80
server/packages/gigacode/README.md
Normal file
80
server/packages/gigacode/README.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# GigaCode
|
||||||
|
|
||||||
|
Use [OpenCode](https://opencode.ai)'s UI with any coding agent.
|
||||||
|
|
||||||
|
Supports Claude Code, Codex, and Amp.
|
||||||
|
|
||||||
|
This is __not__ a fork. It's powered by [Sandbox Agent SDK](https://sandboxagent.dev)'s wizardry.
|
||||||
|
|
||||||
|
> **Experimental**: This project is under active development. Please report bugs on [GitHub Issues](https://github.com/rivet-dev/sandbox-agent/issues) or join our [Discord](https://rivet.dev/discord).
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ GigaCode ────────────────────────────────────────────────────────┐
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ OpenCode TUI │───▶│ Sandbox Agent │───▶│ Claude Code / │ │
|
||||||
|
│ │ │ │ │ │ Codex / Amp │ │
|
||||||
|
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||||
|
└───────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- [Sandbox Agent SDK](https://sandboxagent.dev) provides a universal HTTP API for controlling Claude Code, Codex, and Amp
|
||||||
|
- Sandbox Agent SDK exposes an [OpenCode-compatible endpoint](https://sandboxagent.dev/opencode-compatibility) so OpenCode can talk to any agent
|
||||||
|
- OpenCode connects to Sandbox Agent SDK via [`attach`](https://opencode.ai/docs/cli/#attach)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
**macOS / Linux / WSL (Recommended)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/gigacode-install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**npm i -g**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g gigacode
|
||||||
|
gigacode --help
|
||||||
|
```
|
||||||
|
|
||||||
|
**bun add -g**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add -g gigacode
|
||||||
|
# Allow Bun to run postinstall scripts for native binaries.
|
||||||
|
bun pm -g trust gigacode-linux-x64 gigacode-linux-arm64 gigacode-darwin-arm64 gigacode-darwin-x64 gigacode-win32-x64
|
||||||
|
gigacode --help
|
||||||
|
```
|
||||||
|
|
||||||
|
**npx**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx gigacode --help
|
||||||
|
```
|
||||||
|
|
||||||
|
**bunx**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bunx gigacode --help
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Windows is unsupported. Please use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
**TUI**
|
||||||
|
|
||||||
|
Launch the OpenCode TUI with any coding agent:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gigacode
|
||||||
|
```
|
||||||
|
|
||||||
|
**Web UI**
|
||||||
|
|
||||||
|
Use the [OpenCode Web UI](https://sandboxagent.dev/opencode-compatibility) to control any coding agent from the browser.
|
||||||
|
|
||||||
|
**OpenCode SDK**
|
||||||
|
|
||||||
|
Use the [`@opencode-ai/sdk`](https://sandboxagent.dev/opencode-compatibility) to programmatically control any coding agent.
|
||||||
28
server/packages/gigacode/src/main.rs
Normal file
28
server/packages/gigacode/src/main.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use sandbox_agent::cli::{
|
||||||
|
CliConfig, CliError, Command, GigacodeCli, OpencodeArgs, init_logging, run_command,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Err(err) = run() {
|
||||||
|
tracing::error!(error = %err, "gigacode failed");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<(), CliError> {
|
||||||
|
let cli = GigacodeCli::parse();
|
||||||
|
let config = CliConfig {
|
||||||
|
token: cli.token,
|
||||||
|
no_token: cli.no_token,
|
||||||
|
gigacode: true,
|
||||||
|
};
|
||||||
|
let command = cli
|
||||||
|
.command
|
||||||
|
.unwrap_or_else(|| Command::Opencode(OpencodeArgs::default()));
|
||||||
|
if let Err(err) = init_logging(&command) {
|
||||||
|
eprintln!("failed to init logging: {err}");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
run_command(&command, &config)
|
||||||
|
}
|
||||||
|
|
@ -19,9 +19,22 @@ fn main() {
|
||||||
println!("cargo:rerun-if-env-changed=SANDBOX_AGENT_VERSION");
|
println!("cargo:rerun-if-env-changed=SANDBOX_AGENT_VERSION");
|
||||||
println!("cargo:rerun-if-changed={}", dist_dir.display());
|
println!("cargo:rerun-if-changed={}", dist_dir.display());
|
||||||
|
|
||||||
|
// Rebuild when the git HEAD changes so BUILD_ID stays current.
|
||||||
|
let git_head = manifest_dir.join(".git/HEAD");
|
||||||
|
if git_head.exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}", git_head.display());
|
||||||
|
} else {
|
||||||
|
// In a workspace the .git dir lives at the repo root.
|
||||||
|
let root_git_head = root_dir.join(".git/HEAD");
|
||||||
|
if root_git_head.exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}", root_git_head.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate version constant from environment variable or fallback to Cargo.toml version
|
// Generate version constant from environment variable or fallback to Cargo.toml version
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
|
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
|
||||||
generate_version(&out_dir);
|
generate_version(&out_dir);
|
||||||
|
generate_build_id(&out_dir);
|
||||||
|
|
||||||
let skip = env::var("SANDBOX_AGENT_SKIP_INSPECTOR").is_ok();
|
let skip = env::var("SANDBOX_AGENT_SKIP_INSPECTOR").is_ok();
|
||||||
let out_file = out_dir.join("inspector_assets.rs");
|
let out_file = out_dir.join("inspector_assets.rs");
|
||||||
|
|
@ -81,3 +94,33 @@ fn generate_version(out_dir: &Path) {
|
||||||
|
|
||||||
fs::write(&out_file, contents).expect("write version.rs");
|
fs::write(&out_file, contents).expect("write version.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_build_id(out_dir: &Path) {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let build_id = Command::new("git")
|
||||||
|
.args(["rev-parse", "--short", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.filter(|o| o.status.success())
|
||||||
|
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// Fallback: use the package version + compile-time timestamp
|
||||||
|
let version = env::var("CARGO_PKG_VERSION").unwrap_or_default();
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
format!("{version}-{timestamp}")
|
||||||
|
});
|
||||||
|
|
||||||
|
let out_file = out_dir.join("build_id.rs");
|
||||||
|
let contents = format!(
|
||||||
|
"/// Unique identifier for this build (git short hash or version-timestamp fallback).\n\
|
||||||
|
pub const BUILD_ID: &str = \"{}\";\n",
|
||||||
|
build_id
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::write(&out_file, contents).expect("write build_id.rs");
|
||||||
|
}
|
||||||
|
|
|
||||||
1320
server/packages/sandbox-agent/src/cli.rs
Normal file
1320
server/packages/sandbox-agent/src/cli.rs
Normal file
File diff suppressed because it is too large
Load diff
487
server/packages/sandbox-agent/src/daemon.rs
Normal file
487
server/packages/sandbox-agent/src/daemon.rs
Normal file
|
|
@ -0,0 +1,487 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Child, Command as ProcessCommand, Stdio};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use reqwest::blocking::Client as HttpClient;
|
||||||
|
|
||||||
|
use crate::cli::{CliConfig, CliError};
|
||||||
|
|
||||||
|
mod build_id {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/build_id.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use build_id::BUILD_ID;
|
||||||
|
|
||||||
|
const DAEMON_HEALTH_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Paths
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn daemon_state_dir() -> PathBuf {
|
||||||
|
dirs::data_dir()
|
||||||
|
.map(|dir| dir.join("sandbox-agent").join("daemon"))
|
||||||
|
.unwrap_or_else(|| PathBuf::from(".").join(".sandbox-agent").join("daemon"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sanitize_host(host: &str) -> String {
|
||||||
|
host.chars()
|
||||||
|
.map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daemon_pid_path(host: &str, port: u16) -> PathBuf {
|
||||||
|
let name = format!("daemon-{}-{}.pid", sanitize_host(host), port);
|
||||||
|
daemon_state_dir().join(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daemon_log_path(host: &str, port: u16) -> PathBuf {
|
||||||
|
let name = format!("daemon-{}-{}.log", sanitize_host(host), port);
|
||||||
|
daemon_state_dir().join(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn daemon_version_path(host: &str, port: u16) -> PathBuf {
|
||||||
|
let name = format!("daemon-{}-{}.version", sanitize_host(host), port);
|
||||||
|
daemon_state_dir().join(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// PID helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn read_pid(path: &Path) -> Option<u32> {
|
||||||
|
let text = fs::read_to_string(path).ok()?;
|
||||||
|
text.trim().parse::<u32>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_pid(path: &Path, pid: u32) -> Result<(), CliError> {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
fs::write(path, pid.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_pid(path: &Path) -> Result<(), CliError> {
|
||||||
|
if path.exists() {
|
||||||
|
fs::remove_file(path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Version helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn read_daemon_version(host: &str, port: u16) -> Option<String> {
|
||||||
|
let path = daemon_version_path(host, port);
|
||||||
|
let text = fs::read_to_string(path).ok()?;
|
||||||
|
Some(text.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_daemon_version(host: &str, port: u16) -> Result<(), CliError> {
|
||||||
|
let path = daemon_version_path(host, port);
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
fs::write(&path, BUILD_ID)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_version_file(host: &str, port: u16) -> Result<(), CliError> {
|
||||||
|
let path = daemon_version_path(host, port);
|
||||||
|
if path.exists() {
|
||||||
|
fs::remove_file(path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_version_current(host: &str, port: u16) -> bool {
|
||||||
|
match read_daemon_version(host, port) {
|
||||||
|
Some(v) => v == BUILD_ID,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Process helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn is_process_running(pid: u32) -> bool {
|
||||||
|
let result = unsafe { libc::kill(pid as i32, 0) };
|
||||||
|
if result == 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match std::io::Error::last_os_error().raw_os_error() {
|
||||||
|
Some(code) if code == libc::EPERM => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn is_process_running(pid: u32) -> bool {
|
||||||
|
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||||
|
use windows::Win32::System::Threading::{
|
||||||
|
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||||
|
if handle.is_invalid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut exit_code = 0u32;
|
||||||
|
let ok = GetExitCodeProcess(handle, &mut exit_code).as_bool();
|
||||||
|
let _ = CloseHandle(handle);
|
||||||
|
ok && exit_code == 259
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Health checks
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn check_health(base_url: &str, token: Option<&str>) -> Result<bool, CliError> {
|
||||||
|
let client = HttpClient::builder().build()?;
|
||||||
|
let url = format!("{base_url}/v1/health");
|
||||||
|
let mut request = client.get(url);
|
||||||
|
if let Some(token) = token {
|
||||||
|
request = request.bearer_auth(token);
|
||||||
|
}
|
||||||
|
match request.send() {
|
||||||
|
Ok(response) if response.status().is_success() => Ok(true),
|
||||||
|
Ok(_) => Ok(false),
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_health(
|
||||||
|
mut server_child: Option<&mut Child>,
|
||||||
|
base_url: &str,
|
||||||
|
token: Option<&str>,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Result<(), CliError> {
|
||||||
|
let client = HttpClient::builder().build()?;
|
||||||
|
let deadline = Instant::now() + timeout;
|
||||||
|
|
||||||
|
while Instant::now() < deadline {
|
||||||
|
if let Some(child) = server_child.as_mut() {
|
||||||
|
if let Some(status) = child.try_wait()? {
|
||||||
|
return Err(CliError::Server(format!(
|
||||||
|
"sandbox-agent exited before becoming healthy ({status})"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = format!("{base_url}/v1/health");
|
||||||
|
let mut request = client.get(&url);
|
||||||
|
if let Some(token) = token {
|
||||||
|
request = request.bearer_auth(token);
|
||||||
|
}
|
||||||
|
match request.send() {
|
||||||
|
Ok(response) if response.status().is_success() => return Ok(()),
|
||||||
|
_ => {
|
||||||
|
std::thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(CliError::Server(
|
||||||
|
"timed out waiting for sandbox-agent health".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Spawn
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn spawn_sandbox_agent_daemon(
|
||||||
|
cli: &CliConfig,
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
token: Option<&str>,
|
||||||
|
log_path: &Path,
|
||||||
|
) -> Result<Child, CliError> {
|
||||||
|
if let Some(parent) = log_path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
let log_file = fs::File::create(log_path)?;
|
||||||
|
let log_file_err = log_file.try_clone()?;
|
||||||
|
|
||||||
|
let exe = std::env::current_exe()?;
|
||||||
|
let mut cmd = ProcessCommand::new(exe);
|
||||||
|
cmd.arg("server")
|
||||||
|
.arg("--host")
|
||||||
|
.arg(host)
|
||||||
|
.arg("--port")
|
||||||
|
.arg(port.to_string())
|
||||||
|
.env("SANDBOX_AGENT_LOG_STDOUT", "1")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::from(log_file))
|
||||||
|
.stderr(Stdio::from(log_file_err));
|
||||||
|
|
||||||
|
if cli.no_token {
|
||||||
|
cmd.arg("--no-token");
|
||||||
|
} else if let Some(token) = token {
|
||||||
|
cmd.arg("--token").arg(token);
|
||||||
|
} else {
|
||||||
|
return Err(CliError::MissingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.spawn().map_err(CliError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// DaemonStatus
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DaemonStatus {
|
||||||
|
Running {
|
||||||
|
pid: u32,
|
||||||
|
version: Option<String>,
|
||||||
|
version_current: bool,
|
||||||
|
log_path: PathBuf,
|
||||||
|
},
|
||||||
|
NotRunning,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DaemonStatus {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
DaemonStatus::Running {
|
||||||
|
pid,
|
||||||
|
version,
|
||||||
|
version_current,
|
||||||
|
log_path,
|
||||||
|
} => {
|
||||||
|
let version_str = version.as_deref().unwrap_or("unknown");
|
||||||
|
let outdated = if *version_current {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" [outdated, restart recommended]"
|
||||||
|
};
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Daemon running (PID {pid}, build {version_str}, logs: {}){}",
|
||||||
|
log_path.display(),
|
||||||
|
outdated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DaemonStatus::NotRunning => write!(f, "Daemon not running"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// High-level commands
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub fn status(host: &str, port: u16, token: Option<&str>) -> Result<DaemonStatus, CliError> {
|
||||||
|
let pid_path = daemon_pid_path(host, port);
|
||||||
|
let log_path = daemon_log_path(host, port);
|
||||||
|
|
||||||
|
if let Some(pid) = read_pid(&pid_path) {
|
||||||
|
if is_process_running(pid) {
|
||||||
|
let version = read_daemon_version(host, port);
|
||||||
|
let version_current = is_version_current(host, port);
|
||||||
|
return Ok(DaemonStatus::Running {
|
||||||
|
pid,
|
||||||
|
version,
|
||||||
|
version_current,
|
||||||
|
log_path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Stale PID file
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try a health check in case the daemon is running but we lost the PID file
|
||||||
|
let base_url = format!("http://{host}:{port}");
|
||||||
|
if check_health(&base_url, token)? {
|
||||||
|
return Ok(DaemonStatus::Running {
|
||||||
|
pid: 0,
|
||||||
|
version: read_daemon_version(host, port),
|
||||||
|
version_current: is_version_current(host, port),
|
||||||
|
log_path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DaemonStatus::NotRunning)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(
|
||||||
|
cli: &CliConfig,
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
token: Option<&str>,
|
||||||
|
) -> Result<(), CliError> {
|
||||||
|
let base_url = format!("http://{host}:{port}");
|
||||||
|
let pid_path = daemon_pid_path(host, port);
|
||||||
|
let log_path = daemon_log_path(host, port);
|
||||||
|
|
||||||
|
// Already healthy?
|
||||||
|
if check_health(&base_url, token)? {
|
||||||
|
eprintln!("daemon already running at {base_url}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stale PID?
|
||||||
|
if let Some(pid) = read_pid(&pid_path) {
|
||||||
|
if is_process_running(pid) {
|
||||||
|
eprintln!("daemon process {pid} exists; waiting for health");
|
||||||
|
return wait_for_health(None, &base_url, token, DAEMON_HEALTH_TIMEOUT);
|
||||||
|
}
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"starting daemon at {base_url} (logs: {})",
|
||||||
|
log_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut child = spawn_sandbox_agent_daemon(cli, host, port, token, &log_path)?;
|
||||||
|
let pid = child.id();
|
||||||
|
write_pid(&pid_path, pid)?;
|
||||||
|
write_daemon_version(host, port)?;
|
||||||
|
|
||||||
|
let result = wait_for_health(Some(&mut child), &base_url, token, DAEMON_HEALTH_TIMEOUT);
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("daemon started (PID {pid}, logs: {})", log_path.display());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn stop(host: &str, port: u16) -> Result<(), CliError> {
|
||||||
|
let pid_path = daemon_pid_path(host, port);
|
||||||
|
|
||||||
|
let pid = match read_pid(&pid_path) {
|
||||||
|
Some(pid) => pid,
|
||||||
|
None => {
|
||||||
|
eprintln!("daemon is not running (no PID file)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_process_running(pid) {
|
||||||
|
eprintln!("daemon is not running (stale PID file)");
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("stopping daemon (PID {pid})...");
|
||||||
|
|
||||||
|
// SIGTERM
|
||||||
|
unsafe {
|
||||||
|
libc::kill(pid as i32, libc::SIGTERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait up to 5 seconds for graceful exit
|
||||||
|
for _ in 0..50 {
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
if !is_process_running(pid) {
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
eprintln!("daemon stopped");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIGKILL
|
||||||
|
eprintln!("daemon did not stop gracefully, sending SIGKILL...");
|
||||||
|
unsafe {
|
||||||
|
libc::kill(pid as i32, libc::SIGKILL);
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
eprintln!("daemon killed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn stop(host: &str, port: u16) -> Result<(), CliError> {
|
||||||
|
let pid_path = daemon_pid_path(host, port);
|
||||||
|
|
||||||
|
let pid = match read_pid(&pid_path) {
|
||||||
|
Some(pid) => pid,
|
||||||
|
None => {
|
||||||
|
eprintln!("daemon is not running (no PID file)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_process_running(pid) {
|
||||||
|
eprintln!("daemon is not running (stale PID file)");
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("stopping daemon (PID {pid})...");
|
||||||
|
|
||||||
|
// Use taskkill on Windows
|
||||||
|
let _ = ProcessCommand::new("taskkill")
|
||||||
|
.args(["/PID", &pid.to_string(), "/F"])
|
||||||
|
.status();
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
eprintln!("daemon stopped");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_running(
|
||||||
|
cli: &CliConfig,
|
||||||
|
host: &str,
|
||||||
|
port: u16,
|
||||||
|
token: Option<&str>,
|
||||||
|
) -> Result<(), CliError> {
|
||||||
|
let base_url = format!("http://{host}:{port}");
|
||||||
|
let pid_path = daemon_pid_path(host, port);
|
||||||
|
|
||||||
|
// Check if daemon is already healthy
|
||||||
|
if check_health(&base_url, token)? {
|
||||||
|
// Check build version
|
||||||
|
if !is_version_current(host, port) {
|
||||||
|
let old = read_daemon_version(host, port).unwrap_or_else(|| "unknown".to_string());
|
||||||
|
eprintln!(
|
||||||
|
"daemon outdated (build {old} -> {BUILD_ID}), restarting..."
|
||||||
|
);
|
||||||
|
stop(host, port)?;
|
||||||
|
return start(cli, host, port, token);
|
||||||
|
}
|
||||||
|
let log_path = daemon_log_path(host, port);
|
||||||
|
if let Some(pid) = read_pid(&pid_path) {
|
||||||
|
eprintln!(
|
||||||
|
"daemon already running at {base_url} (PID {pid}, logs: {})",
|
||||||
|
log_path.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("daemon already running at {base_url}");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not healthy — check for stale PID
|
||||||
|
if let Some(pid) = read_pid(&pid_path) {
|
||||||
|
if is_process_running(pid) {
|
||||||
|
eprintln!("daemon process {pid} running; waiting for health");
|
||||||
|
return wait_for_health(None, &base_url, token, DAEMON_HEALTH_TIMEOUT);
|
||||||
|
}
|
||||||
|
let _ = remove_pid(&pid_path);
|
||||||
|
let _ = remove_version_file(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(cli, host, port, token)
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
mod agent_server_logs;
|
mod agent_server_logs;
|
||||||
pub mod credentials;
|
pub mod credentials;
|
||||||
|
pub mod daemon;
|
||||||
pub mod opencode_compat;
|
pub mod opencode_compat;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod server_logs;
|
pub mod server_logs;
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
pub mod cli;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
1
target
1
target
|
|
@ -1 +0,0 @@
|
||||||
/home/nathan/sandbox-agent/target
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue