docs(public): align release facts with runtime
Derive website sandbox facts from explicit runtime capability markers instead of source filenames. Document actual platform wrappers, narrow model and audit claims, and make two-package/three-command installs exact. Verify release assets under their manifest filenames before renaming them, with an executable contract test for every Unix snippet. Refresh localized README receipts and keep source-candidate toolCount at 66. Signed-off-by: Hunter B <hmbown@gmail.com>
This commit is contained in:
+8
-6
@@ -1,7 +1,7 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**Un runtime. Todos los modelos. Tu máquina.**
|
||||
**Un runtime. Modelos alojados y locales compatibles. Tu máquina.**
|
||||
|
||||
Codewhale es un agente de código para tu terminal. Funciona con modelos
|
||||
alojados y locales compatibles; los modelos abiertos primero. Le das un proveedor, un modelo y una
|
||||
@@ -17,10 +17,12 @@ mitad de tarea con `/model`. Usa la TUI para el trabajo interactivo y
|
||||
vienen de la ruta real. Un precio desconocido se muestra como desconocido,
|
||||
nunca como $0.
|
||||
- **Seguro por construcción.** El modo Plan es de solo lectura. Las
|
||||
aprobaciones controlan cada llamada riesgosa. El sandbox del sistema
|
||||
operativo resiste — Seatbelt, Landlock, seccomp, bwrap. El
|
||||
`constitution.json` de un repo se compila en bloqueos de escritura que ni
|
||||
siquiera Full Access puede saltarse.
|
||||
aprobaciones controlan cada llamada riesgosa. Codewhale solo informa un
|
||||
sandbox de comandos del sistema operativo cuando realmente envuelve el
|
||||
comando: Seatbelt en macOS cuando está disponible y bubblewrap opcional en
|
||||
Linux cuando está instalado. Windows actualmente informa que no hay
|
||||
sandbox. El `constitution.json` de un repo se compila en bloqueos de
|
||||
escritura que ni siquiera Full Access puede saltarse.
|
||||
- **Trabajo que sobrevive.** Los fleets registran cada paso en un libro mayor
|
||||
de solo agregado; `fleet resume` retoma donde te detuviste. Cada turno deja
|
||||
un recibo que puedes inspeccionar.
|
||||
|
||||
+8
-5
@@ -1,7 +1,7 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**ひとつのランタイム。あらゆるモデル。あなたのマシン。**
|
||||
**ひとつのランタイム。対応するホスト型・ローカルモデル。あなたのマシン。**
|
||||
|
||||
Codewhale はターミナルで動くコーディングエージェントです。対応するホスト型・
|
||||
ローカルモデルで動作し、オープンモデルを最優先します。プロバイダ、モデル、タスクを渡すと、
|
||||
@@ -18,9 +18,12 @@ CI には `codewhale exec` を。Rust 製、MIT ライセンス、あなたの
|
||||
実際のルートに由来します。不明な価格は不明と表示され、$0 とは決して
|
||||
表示されません。
|
||||
- **構造として安全。** Plan モードは読み取り専用。リスクのある呼び出しは
|
||||
すべて承認でゲートされます。OS サンドボックスが守ります — Seatbelt、
|
||||
Landlock、seccomp、bwrap。リポジトリの `constitution.json` は書き込み
|
||||
ホールドへとコンパイルされ、Full Access でもスキップできません。
|
||||
すべて承認でゲートされます。Codewhale が OS コマンドサンドボックスを
|
||||
表示するのは、実際にコマンドをラップするときだけです。macOS では利用可能な
|
||||
Seatbelt、Linux ではインストール済みで明示的に有効化した bubblewrap を使い、
|
||||
Windows は現在サンドボックスなしと表示します。リポジトリの
|
||||
`constitution.json` は書き込みホールドへとコンパイルされ、Full Access でも
|
||||
スキップできません。
|
||||
- **消えない作業。** Fleet はすべてのステップを追記専用の台帳に記録し、
|
||||
`fleet resume` で止めたところから再開できます。どのターンも検証できる
|
||||
レシートを残します。
|
||||
|
||||
+7
-5
@@ -1,7 +1,7 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**하나의 런타임. 모든 모델. 당신의 컴퓨터.**
|
||||
**하나의 런타임. 지원되는 호스팅 및 로컬 모델. 당신의 컴퓨터.**
|
||||
|
||||
Codewhale은 터미널에서 쓰는 코딩 에이전트입니다. 지원되는 호스팅 및 로컬
|
||||
모델과 함께 동작하며, 오픈 모델을 우선합니다. 프로바이더, 모델, 작업을 지정하면 코드를 읽고,
|
||||
@@ -17,9 +17,11 @@ Codewhale은 터미널에서 쓰는 코딩 에이전트입니다. 지원되는
|
||||
알 수 없는 가격은 알 수 없음으로 표시됩니다 — 절대 $0으로 표시되지
|
||||
않습니다.
|
||||
- **구조적으로 안전.** Plan 모드는 읽기 전용입니다. 위험한 호출은 모두
|
||||
승인을 거칩니다. OS 샌드박스가 지켜냅니다 — Seatbelt, Landlock, seccomp,
|
||||
bwrap. 저장소의 `constitution.json`은 Full Access조차 건너뛸 수 없는 쓰기
|
||||
홀드로 컴파일됩니다.
|
||||
승인을 거칩니다. Codewhale은 명령이 실제로 래핑될 때만 OS 명령 샌드박스를
|
||||
표시합니다. macOS에서는 사용 가능한 Seatbelt를, Linux에서는 설치되어 있고
|
||||
명시적으로 활성화한 bubblewrap을 사용하며, Windows는 현재 샌드박스 없음으로
|
||||
표시합니다. 저장소의 `constitution.json`은 Full Access조차 건너뛸 수 없는
|
||||
쓰기 홀드로 컴파일됩니다.
|
||||
- **사라지지 않는 작업.** Fleet은 모든 단계를 추가 전용 원장에 기록하고,
|
||||
`fleet resume`은 멈춘 지점부터 이어갑니다. 매 턴마다 확인할 수 있는
|
||||
영수증이 남습니다.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Codewhale
|
||||
|
||||
**One runtime. Every model. Your machine.**
|
||||
**One runtime. Supported hosted and local models. Your machine.**
|
||||
|
||||
Codewhale is a coding agent for your terminal. It works with supported hosted
|
||||
and local models; open models first. Give it a provider, a model, and a task: it reads your
|
||||
@@ -15,9 +15,11 @@ runs on your machine.
|
||||
and one toolset. Context budgets and prices come from the real route. An
|
||||
unknown price shows as unknown, never as $0.
|
||||
- **Safe by construction.** Plan mode is read-only. Approvals gate every
|
||||
risky call. The OS sandbox holds — Seatbelt, Landlock, seccomp, bwrap.
|
||||
A repo's `constitution.json` compiles into write holds that even Full
|
||||
Access cannot skip.
|
||||
risky call. Codewhale reports an OS command sandbox only when it actually
|
||||
wraps the command: Seatbelt on macOS when available, and opt-in bubblewrap
|
||||
on Linux when installed. Windows currently reports none. A repo's
|
||||
`constitution.json` compiles into write holds that even Full Access cannot
|
||||
skip.
|
||||
- **Work that survives.** Fleets record every step in an append-only
|
||||
ledger; `fleet resume` picks up where you stopped. Every turn leaves a
|
||||
receipt you can inspect.
|
||||
|
||||
+8
-5
@@ -1,7 +1,7 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**Um runtime. Todos os modelos. Sua máquina.**
|
||||
**Um runtime. Modelos hospedados e locais compatíveis. Sua máquina.**
|
||||
|
||||
O Codewhale é um agente de código para o seu terminal. Funciona com modelos
|
||||
hospedados e locais compatíveis; modelos abertos em primeiro lugar. Você informa um provedor, um
|
||||
@@ -17,9 +17,12 @@ sua máquina.
|
||||
um único conjunto de ferramentas. Orçamentos de contexto e preços vêm da
|
||||
rota real. Um preço desconhecido aparece como desconhecido, nunca como $0.
|
||||
- **Seguro por construção.** O modo Plan é somente leitura. Aprovações
|
||||
controlam cada chamada arriscada. O sandbox do sistema operacional segura —
|
||||
Seatbelt, Landlock, seccomp, bwrap. O `constitution.json` de um repositório
|
||||
é compilado em bloqueios de escrita que nem o Full Access consegue pular.
|
||||
controlam cada chamada arriscada. O Codewhale só informa um sandbox de
|
||||
comandos do sistema operacional quando ele realmente envolve o comando:
|
||||
Seatbelt no macOS quando disponível e bubblewrap opcional no Linux quando
|
||||
instalado. O Windows atualmente informa que não há sandbox. O
|
||||
`constitution.json` de um repositório é compilado em bloqueios de escrita
|
||||
que nem o Full Access consegue pular.
|
||||
- **Trabalho que sobrevive.** Fleets registram cada passo em um livro-razão
|
||||
de apenas inclusão; `fleet resume` retoma de onde você parou. Cada turno
|
||||
deixa um recibo que você pode inspecionar.
|
||||
|
||||
+7
-5
@@ -1,7 +1,7 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**Một runtime. Mọi model. Máy của bạn.**
|
||||
**Một runtime. Các model hosted và local được hỗ trợ. Máy của bạn.**
|
||||
|
||||
Codewhale là một coding agent cho terminal của bạn. Hoạt động với các model
|
||||
hosted và local được hỗ trợ; ưu tiên model mở. Đưa cho nó một provider, một model và một nhiệm vụ: nó đọc
|
||||
@@ -16,9 +16,11 @@ bằng Rust, giấy phép MIT, chạy trên máy của bạn.
|
||||
runtime và một bộ công cụ. Ngân sách ngữ cảnh và giá lấy từ route thật; giá
|
||||
chưa rõ hiển thị là chưa rõ, không bao giờ là $0.
|
||||
- **An toàn từ thiết kế.** Chế độ Plan chỉ đọc. Mọi lệnh gọi rủi ro đều qua
|
||||
phê duyệt. Sandbox của hệ điều hành giữ vững — Seatbelt, Landlock, seccomp,
|
||||
bwrap. `constitution.json` của repo được biên dịch thành các chốt chặn ghi
|
||||
mà ngay cả Full Access cũng không thể bỏ qua.
|
||||
phê duyệt. Codewhale chỉ báo sandbox lệnh của hệ điều hành khi lệnh thực sự
|
||||
được bọc: Seatbelt trên macOS khi khả dụng và bubblewrap trên Linux khi đã
|
||||
cài đặt và bật rõ ràng. Windows hiện báo không có sandbox. `constitution.json`
|
||||
của repo được biên dịch thành các chốt chặn ghi mà ngay cả Full Access cũng
|
||||
không thể bỏ qua.
|
||||
- **Công việc không mất.** Fleet ghi lại từng bước vào sổ cái chỉ ghi thêm;
|
||||
`fleet resume` tiếp tục từ chỗ bạn dừng. Mỗi lượt đều để lại một biên nhận
|
||||
bạn có thể kiểm tra.
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
<!-- source: README.md sha256:acc243c2dc85 -->
|
||||
<!-- source: README.md sha256:a14f7d3aa7d1 -->
|
||||
# Codewhale
|
||||
|
||||
**一个运行时。所有模型。你的机器。**
|
||||
**一个运行时。支持的托管与本地模型。你的机器。**
|
||||
|
||||
Codewhale 是运行在终端里的编程智能体。适配受支持的托管与本地模型;开放模型优先。给它一个 provider、一个模型和一个任务:它会读你的代码、改文件、跑命令、检查自己的工作,在任务完成或需要你介入时停下。任务中途用 `/model` 切换模型。交互式工作用 TUI,脚本和 CI 用 `codewhale exec`。Rust 编写,MIT 许可,运行在你自己的机器上。
|
||||
|
||||
**为什么选 Codewhale:**
|
||||
- **不被锁定。** DeepSeek、Claude、GPT、Kimi、GLM 等 30 多家 provider,以及你自己的 vLLM、SGLang、Ollama——无需 key——都跑在同一套运行时和同一套工具之上。上下文预算与价格取自真实路由;价格未知时显示未知,绝不显示 $0。
|
||||
- **安全靠构造。** Plan 模式只读。审批把关每一次高风险调用。操作系统级沙箱守住底线——Seatbelt、Landlock、seccomp、bwrap。仓库的 `constitution.json` 会编译成写入拦截,连 Full Access 也无法跳过。
|
||||
- **安全靠构造。** Plan 模式只读。审批把关每一次高风险调用。Codewhale 只有在命令实际经由 OS 沙箱包装器运行时才报告沙箱:macOS 上是可用时启用的 Seatbelt,Linux 上是已安装且显式启用的 bubblewrap;Windows 当前报告无 OS 沙箱。仓库的 `constitution.json` 会编译成写入拦截,连 Full Access 也无法跳过。
|
||||
- **工作不丢失。** Fleet 把每一步记录在只追加的账本里;`fleet resume` 从你停下的地方继续。每一轮都留下可查验的回执。
|
||||
|
||||
它诞生于 `deepseek-tui`。社区需要更多 provider,于是我们造了一个把模型当组件、而不是当产品的运行时。
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ You will receive status updates at each phase. If the timeline slips, we will co
|
||||
### In scope (what counts)
|
||||
|
||||
- Remote code execution through crafted prompts or model responses
|
||||
- Sandbox escape — breaking out of the YOLO-mode workspace boundary or shell `cwd` confinement
|
||||
- Sandbox escape — breaking out of an active Seatbelt/bubblewrap wrapper or a declared workspace boundary
|
||||
- Credential leak — exfiltration of API keys, tokens, or environment secrets
|
||||
- Arbitrary file read/write outside the intended workspace (`PathEscape` bypass)
|
||||
- SSRF via `fetch_url` or `web_search` against internal network endpoints
|
||||
|
||||
@@ -172,7 +172,9 @@ drives turns through Chat Completions.
|
||||
- `mod.rs` - Sandbox type definitions
|
||||
- `policy.rs` - Sandbox policy configuration
|
||||
- `seatbelt.rs` - macOS Seatbelt profile generation
|
||||
- `bwrap.rs` - opt-in Linux bubblewrap command wrapper
|
||||
- `landlock.rs` - Linux Landlock detection and future helper contract
|
||||
- `seccomp.rs` - dormant Linux seccomp implementation; not wired into commands
|
||||
- `windows.rs` - Windows helper contract; not advertised until a Job
|
||||
Object process-containment helper exists
|
||||
|
||||
@@ -218,7 +220,7 @@ drives turns through Chat Completions.
|
||||
2. Tool registry looks up handler
|
||||
3. Pre-execution hooks run
|
||||
4. Approval requested when the effective permission posture and policy require it
|
||||
5. Tool executed (possibly sandboxed on macOS)
|
||||
5. Tool executed (possibly wrapped by Seatbelt on macOS or opt-in bubblewrap on Linux)
|
||||
6. Post-execution hooks run
|
||||
7. Result metadata is retained on runtime item records
|
||||
8. **LSP post-edit hook**: after a `File` write, edit, or patch action (including a replay-only legacy alias), the engine runs `run_post_edit_lsp_hook()` when LSP is enabled to collect diagnostics
|
||||
@@ -296,9 +298,10 @@ command = "echo 'Running tool: $TOOL_NAME'"
|
||||
holds. Side-effectful MCP tools use the same boundary.
|
||||
3. **Extensibility**: MCP, skills, and hooks allow customization without code changes
|
||||
4. **Cross-platform**: Core works on Linux/macOS/Windows. Sandbox guarantees
|
||||
are platform-specific: macOS Seatbelt is the active policy path; Linux and
|
||||
Windows require helper enforcement before they should be treated as full OS
|
||||
sandboxing.
|
||||
are platform-specific: macOS uses Seatbelt when available; Linux uses an
|
||||
installed bubblewrap executable only when explicitly enabled; Windows has
|
||||
no advertised OS command sandbox. Landlock, seccomp, and the Windows helper
|
||||
contract are not wired into command execution.
|
||||
5. **Minimal dependencies**: Careful dependency selection for build speed
|
||||
6. **Local-first runtime API**: HTTP/SSE endpoints are intended for trusted localhost access and are served by the `crates/tui` runtime today
|
||||
|
||||
|
||||
@@ -1285,9 +1285,10 @@ If you are upgrading from older releases:
|
||||
auto-approval.
|
||||
- `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases.
|
||||
- `sandbox_mode` (string, optional): `read-only`, `workspace-write`, `danger-full-access`, `external-sandbox`.
|
||||
Platform support is not identical. macOS uses Seatbelt for policy
|
||||
enforcement. Linux support is helper-gated around Landlock. Windows does not
|
||||
currently advertise an OS sandbox; the planned Windows helper contract starts
|
||||
Platform support is not identical. macOS uses Seatbelt when its runtime
|
||||
probe succeeds. Linux uses bubblewrap only when `prefer_bwrap = true` and
|
||||
`/usr/bin/bwrap` is executable; without that opt-in it reports no OS command
|
||||
sandbox. Windows does not currently advertise an OS sandbox; its planned helper contract starts
|
||||
with process-tree containment only and must not be described as read-only
|
||||
filesystem isolation, workspace-write enforcement, network blocking,
|
||||
registry isolation, or AppContainer isolation until those are implemented.
|
||||
|
||||
@@ -75,7 +75,7 @@ notes, and relevant issue/PR comments.
|
||||
visibility in the phase strip (PR #4474)
|
||||
- **[Fred Leitz / fleitz](https://github.com/fleitz)** — canonical `Bash`
|
||||
no-`cwd` workspace resolution and its regression test, keeping isolated
|
||||
sub-agent commands inside their selected worktree (PR #4673 / #4674)
|
||||
sub-agent commands inside their selected worktree (PR #4673; issue #4674)
|
||||
- **[Sh1Zuku / SparkofSpike](https://github.com/SparkofSpike)** — exact
|
||||
Vim-space regression reproduction and verification that the v0.9.1 input
|
||||
path already contains the needed global binding (PR #4477)
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ logs and adapter logs are stored under `.codewhale/fleet/` and
|
||||
|
||||
### Interactive and persistent status
|
||||
|
||||
CodeWhale has two similarly named status surfaces with different scopes:
|
||||
Codewhale has two similarly named status surfaces with different scopes:
|
||||
|
||||
- In the TUI, `/fleet status` (or `/subagents`) shows the sub-agents attached
|
||||
to the current interactive session. It does not read the persistent Fleet
|
||||
|
||||
+16
-12
@@ -182,7 +182,7 @@ codewhale-tui --version
|
||||
Known limitations:
|
||||
|
||||
- Commands inherit Android's per-app UID, SELinux, and seccomp protections and
|
||||
any permissions granted to Termux. Codewhale's additional Landlock/bwrap
|
||||
any permissions granted to Termux. Codewhale's opt-in bubblewrap
|
||||
child-process sandbox is Linux-only and is not built on Android, so approved
|
||||
commands receive no Codewhale-specific filesystem narrowing.
|
||||
- The Termux build has no supported Android Keystore or desktop Secret Service
|
||||
@@ -244,7 +244,7 @@ npm install -g codewhale
|
||||
codewhale --version # prints the published version that was installed
|
||||
```
|
||||
|
||||
`postinstall` downloads the right pair of binaries from the matching GitHub
|
||||
`postinstall` downloads the matching three binaries from the GitHub
|
||||
release, verifies a SHA-256 manifest, and exposes `codewhale`, `codew`, and
|
||||
`codewhale-tui` on your `PATH`.
|
||||
|
||||
@@ -274,8 +274,9 @@ Useful environment variables:
|
||||
## 4. Install via Cargo (any Tier-1 Rust target)
|
||||
|
||||
If GitHub releases are slow, blocked, or you're on an unsupported architecture,
|
||||
install from crates.io directly. Both crates are required — the dispatcher
|
||||
delegates to the TUI runtime at runtime.
|
||||
install from crates.io directly. Two Cargo packages are required:
|
||||
`codewhale-cli` installs the `codewhale` and `codew` commands, while
|
||||
`codewhale-tui` installs the `codewhale-tui` command used by the dispatcher.
|
||||
|
||||
```bash
|
||||
# Requires Rust 1.88+ (https://rustup.rs)
|
||||
@@ -481,7 +482,7 @@ explicitly. Replace `X.Y.Z` with the version you want to restore.
|
||||
# npm wrapper, only for versions that were published to npm
|
||||
npm install -g codewhale@X.Y.Z
|
||||
|
||||
# Cargo install path; both crates are required
|
||||
# Cargo path: two packages provide codewhale + codew + codewhale-tui
|
||||
cargo install codewhale-cli --version X.Y.Z --locked --force
|
||||
cargo install codewhale-tui --version X.Y.Z --locked --force
|
||||
```
|
||||
@@ -752,7 +753,8 @@ The legacy `DEEPSEEK_TUI_RELEASE_BASE_URL` name is still accepted.
|
||||
|
||||
`codewhale update` normally contacts GitHub Releases for metadata and binary
|
||||
assets. On networks where GitHub is blocked or unreliable, use the CNB source
|
||||
mirror instead and install both binaries from the release tag:
|
||||
mirror instead and install both Cargo packages from the release tag. Together,
|
||||
they provide the `codewhale`, `codew`, and `codewhale-tui` commands:
|
||||
|
||||
To check the latest release without downloading or replacing binaries, run
|
||||
`codewhale update --check`.
|
||||
@@ -786,8 +788,9 @@ The package requires the Cargo feature called `edition2024`, but that feature
|
||||
is not stabilized in this version of Cargo
|
||||
```
|
||||
|
||||
Install current stable Rust through rustup, then rerun the two Cargo install
|
||||
commands from [Section 4](#4-install-via-cargo-any-tier-1-rust-target). For
|
||||
Install current stable Rust through rustup, then rerun the two Cargo package
|
||||
install commands from [Section 4](#4-install-via-cargo-any-tier-1-rust-target).
|
||||
Together they provide `codewhale`, `codew`, and `codewhale-tui`. For
|
||||
mainland China networks, this rsproxy-based sequence has been verified to work:
|
||||
|
||||
```bash
|
||||
@@ -817,7 +820,8 @@ sudo apt-get install -y build-essential pkg-config libdbus-1-dev
|
||||
WSL2 uses the same Linux source-build path as Ubuntu. If `cargo install
|
||||
codewhale-tui --locked` fails while compiling the keyring or D-Bus secret
|
||||
storage crates, install the Linux build dependencies inside the WSL distro,
|
||||
then rerun both Cargo install commands:
|
||||
then rerun the two Cargo package install commands. Together they install
|
||||
`codewhale`, `codew`, and `codewhale-tui`:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
@@ -921,9 +925,9 @@ Use one of these paths:
|
||||
3. Install via Cargo, which builds locally and does not download GitHub release
|
||||
assets. See [Section 4](#4-install-via-cargo-any-tier-1-rust-target).
|
||||
|
||||
4. Download both `codewhale` and `codewhale-tui` manually from the
|
||||
[Releases page](https://github.com/Hmbown/CodeWhale/releases), place them
|
||||
in a directory on `PATH`, and make them executable. See
|
||||
4. Download all three matching `codewhale`, `codew`, and `codewhale-tui`
|
||||
binaries from the [Releases page](https://github.com/Hmbown/CodeWhale/releases),
|
||||
place them in a directory on `PATH`, and make them executable. See
|
||||
[Section 6](#6-manual-download-from-github-releases).
|
||||
|
||||
---
|
||||
|
||||
+144
-221
@@ -1,271 +1,194 @@
|
||||
# Sandbox threat model
|
||||
|
||||
Codewhale executes shell commands spawned by AI reasoning. The sandbox
|
||||
module restricts what those commands can do to the host system. This
|
||||
document describes what each platform's sandbox actually enforces,
|
||||
what is best-effort, and what is explicitly out of scope.
|
||||
Codewhale can launch shell commands proposed by a model. Approval policy,
|
||||
workspace-aware tools, and an operating-system command wrapper are separate
|
||||
controls: an approval is not a sandbox, and selecting `workspace-write` does
|
||||
not prove that the current platform has an OS wrapper available.
|
||||
|
||||
This document describes only behavior wired into the command execution path.
|
||||
|
||||
## Platform overview
|
||||
|
||||
| Mechanism | Platform | Type | Status |
|
||||
| Mechanism | Platform | Selection | What Codewhale reports |
|
||||
|---|---|---|---|
|
||||
| Seatbelt | macOS | Mandatory access control | Enforced |
|
||||
| Landlock | Linux | Filesystem access control | Enforced |
|
||||
| seccomp BPF | Linux | Syscall filter | Enforced |
|
||||
| Process hardening | Linux | Kernel prctl / rlimit | Enforced |
|
||||
| Bubblewrap (bwrap) | Linux | Namespace isolation | Optional |
|
||||
| Windows Job Object | Windows | Process-tree containment | v1 (PR #2220) |
|
||||
| Seatbelt (`sandbox-exec`) | macOS | Automatic when the runtime probe succeeds | `macos-seatbelt` |
|
||||
| Bubblewrap (`/usr/bin/bwrap`) | Linux | `prefer_bwrap = true` and the file is executable | `linux-bwrap` |
|
||||
| No OS wrapper | Linux without usable opt-in bwrap | Default | `none` |
|
||||
| No OS wrapper | Windows | Current implementation | `none` |
|
||||
| OpenSandbox-compatible service | Any supported host | `sandbox_backend = "opensandbox"` | External execution path |
|
||||
|
||||
## Threat model: what each layer addresses
|
||||
The repository contains Landlock and seccomp implementation modules, plus a
|
||||
future Windows helper contract. They are not wired into child-command launch
|
||||
in v0.9.1, so Codewhale does not advertise them as active sandboxes. Detecting
|
||||
a Landlock-capable kernel is not enough to claim that a command was restricted.
|
||||
|
||||
### 1. Process hardening (Linux only)
|
||||
## macOS: Seatbelt
|
||||
|
||||
**When it runs:** Before any threads are spawned, before Tokio boots,
|
||||
before any data is loaded into memory.
|
||||
Codewhale probes `/usr/bin/sandbox-exec` by running a minimal profile. When the
|
||||
probe succeeds and the selected `SandboxPolicy` requests a sandbox, the child
|
||||
command is wrapped with a generated Seatbelt profile.
|
||||
|
||||
**What it does:**
|
||||
The profile can provide:
|
||||
|
||||
- `PR_SET_DUMPABLE=0` — prevents ptrace, makes `/proc/<pid>/` root-owned
|
||||
- `PR_SET_NO_NEW_PRIVS=1` — irreversible; no child can ever gain privileges
|
||||
- `RLIMIT_CORE=0` — no core dumps, so sensitive data never hits disk
|
||||
- broad filesystem reads;
|
||||
- writes limited by the selected policy, including the workspace and specific
|
||||
runtime/cache paths needed by supported tools;
|
||||
- network access only when the policy enables it.
|
||||
|
||||
**What it protects against:**
|
||||
- Process inspection via ptrace/strace/gdb
|
||||
- Privilege escalation via setuid/setgid/fscaps
|
||||
- Core dumps leaking API keys, tokens, prompt content
|
||||
If the probe fails or `sandbox-exec` is unavailable, Codewhale reports no OS
|
||||
sandbox and launches the command without a Seatbelt wrapper. It does not set a
|
||||
Seatbelt marker on that fallback.
|
||||
|
||||
**What it does NOT protect against:**
|
||||
- A compromised child reading its parent's `/proc/<pid>/mem` (already blocked
|
||||
by `PR_SET_DUMPABLE=0` making `/proc/<pid>/` root-owned)
|
||||
- Kernel exploits that bypass prctl
|
||||
## Linux: opt-in bubblewrap
|
||||
|
||||
### 2. Landlock (Linux, kernel 5.13+)
|
||||
Linux command sandboxing is opt-in. Set the top-level configuration key:
|
||||
|
||||
**When it runs:** Applied to each child process at spawn time via a
|
||||
helper script or `landlock_restrict_self`. Only restrictable by the
|
||||
process itself — parent cannot force Landlock on a child.
|
||||
```toml
|
||||
prefer_bwrap = true
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Restricts filesystem access to a whitelist of paths
|
||||
- Handles: `EXECUTE`, `READ_FILE`, `READ_DIR`, `WRITE_FILE`, `REMOVE_DIR`,
|
||||
`REMOVE_FILE`, `MAKE_DIR`, `MAKE_REG`, `MAKE_SYM`, `TRUNCATE`
|
||||
Codewhale selects bubblewrap only when `/usr/bin/bwrap` is a regular executable
|
||||
file. The wrapper derives its mounts and network namespace from the resolved
|
||||
`SandboxPolicy`:
|
||||
|
||||
**What it protects against:**
|
||||
- Reading files outside the workspace (e.g., `/etc/passwd`, `~/.ssh`)
|
||||
- Writing to system directories (`/usr`, `/bin`, `/lib`)
|
||||
- Creating or deleting files in protected locations
|
||||
```text
|
||||
/usr/bin/bwrap \
|
||||
--unshare-all \
|
||||
[--share-net] \
|
||||
--ro-bind / / \
|
||||
--bind <writable-root> <writable-root> ... \
|
||||
--ro-bind <protected-descendant> <protected-descendant> ... \
|
||||
--chdir <cwd> \
|
||||
-- <program> <args>
|
||||
```
|
||||
|
||||
**What it does NOT protect against:**
|
||||
- Network access (Landlock is filesystem-only)
|
||||
- Process inspection (use seccomp for this)
|
||||
- Reading files that are already mapped (Landlock applies at `open()` time)
|
||||
That gives the child a read-only root view. For `workspace-write`, every safe,
|
||||
existing policy root is mounted read-write: the working directory, configured
|
||||
additional roots, `/tmp` and `TMPDIR` unless excluded, and verified Git
|
||||
worktree metadata roots. Existing `.codewhale` and `.deepseek` descendants are
|
||||
remounted read-only after their writable parent. Missing paths, non-directory
|
||||
paths, and `/` are not promoted to writable mounts.
|
||||
|
||||
**Detection:** `detect_denial()` checks stderr for `Permission denied`,
|
||||
`Operation not permitted`, `EACCES`, `EPERM`.
|
||||
For `read-only`, there are no writable binds, so the working directory remains
|
||||
inside the read-only root view. `--unshare-all` isolates the network namespace
|
||||
by default. Codewhale adds `--share-net` only when the policy's
|
||||
`network_access` is true. `danger-full-access` and `external-sandbox` bypass the
|
||||
local wrapper entirely.
|
||||
|
||||
### 3. seccomp BPF (Linux only)
|
||||
If the user does not opt in, or `/usr/bin/bwrap` is missing or non-executable,
|
||||
Codewhale reports `none` and launches the command without a Linux OS wrapper.
|
||||
There is no marker-only Landlock fallback.
|
||||
|
||||
**When it runs:** Installed via `prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)`
|
||||
on the child process.
|
||||
Install bubblewrap separately when this opt-in fits the workflow:
|
||||
|
||||
**What it does:**
|
||||
- Whitelist of ~100 safe syscalls (file I/O, memory, process, IPC,
|
||||
synchronization, signals, time)
|
||||
- **Explicitly denied:** `ptrace`, `mount`, `umount2`, `kexec_load`,
|
||||
`kexec_file_load`, `init_module`, `finit_module`, `delete_module`,
|
||||
`bpf`, `reboot`, `swapon`, `swapoff`, `pivot_root`,
|
||||
`setuid`/`setgid`/`setreuid`/`setregid`/`setresuid`/`setresgid`,
|
||||
`personality`
|
||||
- Any syscall not on the whitelist → `SECCOMP_RET_KILL_PROCESS` (SIGSYS)
|
||||
|
||||
**What it protects against:**
|
||||
- Process hijacking via ptrace
|
||||
- Mounting filesystems (bypassing Landlock read-only restrictions)
|
||||
- Loading kernel modules
|
||||
- Loading BPF programs (would bypass seccomp itself!)
|
||||
- Rebooting the system
|
||||
- Privilege changes via setuid/setgid
|
||||
|
||||
**What it does NOT protect against:**
|
||||
- Legitimate use of allowed syscalls for malicious purposes
|
||||
- Side-channel attacks via allowed syscalls (e.g., timing)
|
||||
|
||||
**Detection:** `detect_denial()` checks exit code 31 (SIGSYS) or stderr
|
||||
for `Bad system call`, `bad system call`, `SIGSYS`, `seccomp`.
|
||||
|
||||
### 4. Bubblewrap / bwrap (Linux, optional)
|
||||
|
||||
**When it runs:** If `/usr/bin/bwrap` is present AND the config key
|
||||
`[sandbox] prefer_bwrap = true` is set. Runs as an outer wrapper around
|
||||
the child command.
|
||||
|
||||
**What it does:**
|
||||
- Creates a new mount namespace with `--unshare-all`
|
||||
- Read-only bind-mounts the entire root filesystem
|
||||
- Bind-mounts the workspace directory with read-write access
|
||||
- Changes into the workspace with `--chdir`
|
||||
|
||||
**What it protects against:**
|
||||
- Any filesystem write outside the workspace (stronger than Landlock alone
|
||||
because it's enforced at the namespace level, not just filesystem access)
|
||||
- Accidental modification of system files
|
||||
|
||||
**What it does NOT protect against:**
|
||||
- Network access (bwrap does not create a network namespace by default with
|
||||
`--unshare-all`; the child still has full network access)
|
||||
- Process inspection
|
||||
- Memory attacks
|
||||
|
||||
**Installation:** User must install bubblewrap themselves:
|
||||
- Ubuntu/Debian: `apt install bubblewrap`
|
||||
- Fedora: `dnf install bubblewrap`
|
||||
- Arch: `pacman -S bubblewrap`
|
||||
|
||||
Codewhale does NOT vendor bwrap.
|
||||
Codewhale does not vendor bubblewrap.
|
||||
|
||||
**Fallback:** If bwrap is not installed, the sandbox falls back to Landlock
|
||||
only.
|
||||
## Windows: no advertised OS sandbox
|
||||
|
||||
### 5. Seatbelt (macOS)
|
||||
The Windows command path currently reports no OS sandbox. The source tree has
|
||||
a future helper contract for Job Object process-tree cleanup, but it is not
|
||||
wired into selection and must not be described as any of the following:
|
||||
|
||||
**When it runs:** Applied via the `sandbox-exec` wrapper command. The
|
||||
seatbelt profile is generated dynamically based on the `SandboxPolicy`.
|
||||
- read-only filesystem or workspace-write enforcement;
|
||||
- network blocking;
|
||||
- registry isolation;
|
||||
- restricted-token or AppContainer isolation.
|
||||
|
||||
**What it does:**
|
||||
- Restricts filesystem access based on the policy profile
|
||||
- Can restrict network access (when `network_access: false`)
|
||||
Windows host permissions and approval policy still apply, but they are not a
|
||||
Codewhale OS command sandbox.
|
||||
|
||||
**What it protects against:**
|
||||
- Reading/writing files outside allowed paths
|
||||
- Network connections (when configured)
|
||||
## Linux process hardening is not a command sandbox
|
||||
|
||||
**What it does NOT protect against:**
|
||||
- Process inspection (Seatbelt does not block ptrace)
|
||||
- Syscall-level attacks
|
||||
At startup on Linux, Codewhale best-effort applies `PR_SET_DUMPABLE=0`,
|
||||
`PR_SET_NO_NEW_PRIVS=1`, and `RLIMIT_CORE=0` to its own process. Each failure is
|
||||
logged and startup continues. These controls reduce process-inspection,
|
||||
privilege-escalation, and core-dump risk; they do not create filesystem or
|
||||
network isolation for a child command and are not listed as a sandbox backend.
|
||||
|
||||
**Detection:** Checks stderr for `file-write` and `network` denial patterns.
|
||||
## External OpenSandbox execution
|
||||
|
||||
### 6. Windows Job Object (v1, PR #2220)
|
||||
|
||||
**When it runs:** Applied at process spawn time via
|
||||
`PROC_THREAD_ATTRIBUTE_JOB_LIST` and restricted token assignment.
|
||||
|
||||
**What it does (v1):**
|
||||
- Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` — all child
|
||||
processes terminate when the parent exits
|
||||
- Memory cap: 1 GB per process, 2 GB per job
|
||||
- Active process limit: 64
|
||||
- UI restrictions: no desktop handle access
|
||||
- Restricted token: drops Administrators group SID, sets medium-low
|
||||
integrity level
|
||||
|
||||
**What is deferred (v2):**
|
||||
- WFP (Windows Filtering Platform) firewall rules — network is open in v1
|
||||
- Filesystem ACL integration at spawn time (deferred)
|
||||
- AppContainer isolation
|
||||
- Registry key isolation
|
||||
|
||||
**Detection:** Checks stderr for `Access is denied`, `STATUS_ACCESS_DENIED`,
|
||||
`ERROR_ACCESS_DENIED`, `ERROR_PRIVILEGE_NOT_HELD`,
|
||||
`ERROR_ACCESS_DISABLED_BY_POLICY`, and integrity/AppContainer patterns.
|
||||
|
||||
## Defense in depth
|
||||
|
||||
The Linux sandbox applies layers in order:
|
||||
|
||||
```
|
||||
Process hardening (prctl) ← before threads
|
||||
↓
|
||||
Landlock (filesystem) ← at child spawn
|
||||
↓
|
||||
seccomp BPF (syscalls) ← at child spawn
|
||||
↓
|
||||
bwrap (namespace isolation) ← optional outer wrapper
|
||||
```
|
||||
|
||||
Each layer addresses a different threat surface. seccomp cannot protect the
|
||||
filesystem (that's Landlock's job). Landlock cannot stop ptrace (that's
|
||||
seccomp + PR_SET_DUMPABLE). bwrap adds namespace-level isolation that
|
||||
neither Landlock nor seccomp can provide.
|
||||
|
||||
## Configuration
|
||||
|
||||
Relevant config keys in `~/.codewhale/config.toml`:
|
||||
When `sandbox_backend = "opensandbox"` is configured, shell execution is sent
|
||||
to the configured OpenSandbox-compatible HTTP endpoint instead of starting a
|
||||
local child. Codewhale validates the request/response contract, but isolation
|
||||
guarantees belong to the configured service and its operator.
|
||||
|
||||
```toml
|
||||
# Sandbox policy mode
|
||||
sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
|
||||
|
||||
# Linux bubblewrap passthrough
|
||||
prefer_bwrap = false # requires `bubblewrap` package installed
|
||||
|
||||
# External sandbox backend
|
||||
sandbox_backend = "none" # "none" or "opensandbox"
|
||||
sandbox_backend = "opensandbox"
|
||||
sandbox_url = "http://localhost:8080"
|
||||
sandbox_api_key = "YOUR_API_KEY"
|
||||
```
|
||||
|
||||
Environment variable overrides:
|
||||
`sandbox_backend = "none"` (or omitting the key) keeps local execution.
|
||||
|
||||
- `DEEPSEEK_SANDBOX_MODE` → `sandbox_mode`
|
||||
- `DEEPSEEK_PREFER_BWRAP=true` → `prefer_bwrap`
|
||||
- `DEEPSEEK_SANDBOX_BACKEND` → `sandbox_backend`
|
||||
- `DEEPSEEK_SANDBOX_URL` → `sandbox_url`
|
||||
- `DEEPSEEK_SANDBOX_API_KEY` → `sandbox_api_key`
|
||||
## Policies and fallbacks
|
||||
|
||||
## Detecting sandbox denials
|
||||
The local `sandbox_mode` values are:
|
||||
|
||||
When a command fails, the sandbox manager checks for denial patterns:
|
||||
```toml
|
||||
sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
|
||||
```
|
||||
|
||||
| Platform | Denial mechanism | Exit code | Stderr patterns |
|
||||
|---|---|---|---|
|
||||
| macOS Seatbelt | sandbox-exec violation | non-zero | `file-write`, `network` |
|
||||
| Linux Landlock | EACCES / EPERM | non-zero | `Permission denied`, `Operation not permitted` |
|
||||
| Linux seccomp | SIGSYS (31) | 31 or 159 | `Bad system call`, `SIGSYS` |
|
||||
| Linux bwrap | Mount/namespace failure | non-zero | varies |
|
||||
| Windows | Access denied / privilege | non-zero | `Access is denied`, `ERROR_PRIVILEGE_NOT_HELD` |
|
||||
- `read-only` and `workspace-write` are enforced by Seatbelt or bubblewrap only
|
||||
when that wrapper is selected and available.
|
||||
- `danger-full-access` deliberately bypasses the local OS wrapper.
|
||||
- `external-sandbox` declares that execution is already externally isolated
|
||||
and bypasses a second local wrapper.
|
||||
- When no wrapper is selected, the shell command runs without Codewhale OS
|
||||
isolation. Approval rules and workspace-aware native file tools remain
|
||||
separate controls.
|
||||
|
||||
The `was_denied()` method on `SandboxManager` aggregates all platform-specific
|
||||
checks. The `denial_message()` method returns a human-readable explanation.
|
||||
Canonical environment overrides exist for `sandbox_mode` and the external
|
||||
backend:
|
||||
|
||||
- `CODEWHALE_SANDBOX_MODE`
|
||||
- `CODEWHALE_SANDBOX_BACKEND`
|
||||
- `CODEWHALE_SANDBOX_URL`
|
||||
- `CODEWHALE_SANDBOX_API_KEY`
|
||||
|
||||
There is no `CODEWHALE_PREFER_BWRAP` environment override; use the top-level
|
||||
`prefer_bwrap` config key.
|
||||
|
||||
## Diagnostics and failure attribution
|
||||
|
||||
`codewhale setup --status`, `codewhale doctor`, `codewhale doctor --json`, and
|
||||
the `diagnostics` tool report the locally available wrapper after applying the
|
||||
resolved bubblewrap preference. An individual command can still bypass that
|
||||
wrapper when its policy does not request sandboxing. On Linux, merely finding
|
||||
a Landlock syscall or a bwrap source module does not make
|
||||
`sandbox_available` true.
|
||||
|
||||
Denial attribution is intentionally conservative:
|
||||
|
||||
- Seatbelt uses its wrapper-specific denial patterns.
|
||||
- Bubblewrap setup errors must be prefixed by `bwrap:`; a read-only-filesystem
|
||||
error from the bwrap filesystem view can also identify the boundary.
|
||||
- A child command's generic `Permission denied` or `Operation not permitted`
|
||||
is not, by itself, proof that Codewhale's sandbox blocked it.
|
||||
- Unsandboxed command failures are never labeled sandbox denials.
|
||||
|
||||
## Limitations
|
||||
|
||||
### What the sandbox does NOT protect against
|
||||
- Availability is checked before launch; the selected wrapper can still fail
|
||||
because of host policy, container restrictions, or a race after the probe.
|
||||
- Bubblewrap ignores a configured writable root if it is missing, is not a
|
||||
directory, or canonicalizes to `/`; a path can also disappear between policy
|
||||
resolution and wrapper launch.
|
||||
- Seatbelt profiles are generated at runtime and must be tested against the
|
||||
commands they are expected to support.
|
||||
- No current local wrapper is advertised on Windows.
|
||||
- An external sandbox backend is only as strong as its configured service.
|
||||
- No sandbox protects against kernel vulnerabilities or all resource-exhaustion
|
||||
and side-channel attacks.
|
||||
|
||||
- **Network attacks** — only macOS Seatbelt can block network; Linux and
|
||||
Windows v1 leave network open
|
||||
- **Memory attacks** — no platform prevents a child process from reading
|
||||
its own memory or exploiting memory corruption bugs
|
||||
- **Timing side channels** — allowed syscalls on Linux can be used for
|
||||
timing-based information leaks
|
||||
- **Resource exhaustion** — the Linux job object limits memory and process
|
||||
count, but does not limit CPU, file descriptors, or disk I/O
|
||||
- **Kernel vulnerabilities** — if the kernel itself has a vulnerability,
|
||||
the sandbox cannot prevent exploitation (this applies to all platforms)
|
||||
- **Supply chain** — if the child process downloads and executes untrusted
|
||||
code, the sandbox limits what that code can do, but does not prevent the
|
||||
download
|
||||
## Implementation references
|
||||
|
||||
### Platform-specific gaps
|
||||
|
||||
- **Linux:** Landlock only protects filesystem access. seccomp adds syscall
|
||||
filtering but uses a whitelist that may need updates for new syscalls.
|
||||
- **macOS:** Seatbelt profiles are generated at runtime. A misconfigured
|
||||
profile could be too permissive.
|
||||
- **Windows v1:** No filesystem ACL enforcement at spawn time. Network is
|
||||
fully open. Job Object is process-tree only.
|
||||
|
||||
## Related
|
||||
|
||||
- `crates/tui/src/sandbox/` — implementation
|
||||
- `crates/config/src/lib.rs` — config keys
|
||||
- `crates/tui/src/tools/diagnostics.rs` — `diagnostics` tool reports
|
||||
`sandbox_available`, `sandbox_type`, `bwrap_available`, `cgroup_version`
|
||||
- `config.example.toml` — annotated config reference
|
||||
- Issue #2180 — this document
|
||||
- Issue #2182 — seccomp filter implementation
|
||||
- Issue #2183 — process hardening
|
||||
- Issue #2184 — bwrap passthrough
|
||||
- Issue #2185 — Windows Job Object v1
|
||||
- Issue #2186 — SandboxExecutor trait unification
|
||||
- Issue #2187 — sandbox parity tests
|
||||
- `crates/tui/src/sandbox/mod.rs` — truthful selection and public capability markers
|
||||
- `crates/tui/src/sandbox/seatbelt.rs` — macOS wrapper and availability probe
|
||||
- `crates/tui/src/sandbox/bwrap.rs` — Linux opt-in wrapper
|
||||
- `crates/tui/src/sandbox/process_hardening.rs` — Linux parent-process hardening
|
||||
- `crates/tui/src/sandbox/backend.rs` — external backend selection
|
||||
- `crates/tui/src/tools/diagnostics.rs` — machine-readable diagnostics
|
||||
|
||||
+8
-7
@@ -33,9 +33,10 @@ Codewhale's security model has three distinct layers on Android:
|
||||
user has granted to Termux. See the
|
||||
[Android application sandbox](https://source.android.com/docs/security/app-sandbox)
|
||||
and [Termux filesystem layout](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout).
|
||||
2. **Codewhale's per-command sandbox backend** — Seatbelt (macOS) or
|
||||
Landlock/bwrap (Linux) can further narrow what a child command may access.
|
||||
Codewhale does not currently provide that additional layer on Android.
|
||||
2. **Codewhale's per-command sandbox backend** — Seatbelt (macOS) or the
|
||||
opt-in bubblewrap wrapper (Linux) can further narrow what a child command
|
||||
may access. Codewhale does not currently provide that additional layer on
|
||||
Android.
|
||||
3. **Codewhale's own gates** — workspace trust, approval prompts,
|
||||
`allow_shell`/`disallowed-tools`, and the file-tool permission system.
|
||||
These share the cross-platform application code path; their Android
|
||||
@@ -43,15 +44,15 @@ Codewhale's security model has three distinct layers on Android:
|
||||
|
||||
### Codewhale sandbox backend: none
|
||||
|
||||
Codewhale's existing Seatbelt and Landlock/bwrap integrations do not target
|
||||
Codewhale's existing Seatbelt and Linux bubblewrap integrations do not target
|
||||
Android. Consequently, `codewhale doctor --json` reports the sandbox as
|
||||
`{"available": false, "kind": null}` on Android. That status describes the
|
||||
absence of an additional Codewhale child-process sandbox; it does not mean
|
||||
Android or Termux provides no OS isolation.
|
||||
|
||||
- `get_platform_sandbox()` returns `None` on Android.
|
||||
- No Linux-only sandbox modules (Landlock, bwrap) are compiled into the
|
||||
Android build — they are `#[cfg(target_os = "linux")]`-gated and Rust
|
||||
- No Linux-only bubblewrap wrapper is compiled into the Android build — it is
|
||||
`#[cfg(target_os = "linux")]`-gated and Rust
|
||||
treats `android` as a distinct target from `linux`.
|
||||
- Shell commands retain Termux's Android app boundary but receive no
|
||||
Codewhale-specific filesystem narrowing. Treat every location available to
|
||||
@@ -95,7 +96,7 @@ skipped entirely on Android (Bionic libc).
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Android app sandbox | ✅ inherited | Per-app UID plus Android platform protections |
|
||||
| Codewhale command sandbox | ❌ unavailable | No Landlock/bwrap/Seatbelt backend on Android |
|
||||
| Codewhale command sandbox | ❌ unavailable | No bubblewrap/Seatbelt backend on Android |
|
||||
| Codewhale keyring backend | ❌ unavailable | Falls back to file-backed secrets |
|
||||
| Approvals / gates | ⚠️ implemented | Device QA pending |
|
||||
| File tools | ⚠️ implemented | Device QA pending |
|
||||
|
||||
@@ -17,9 +17,14 @@
|
||||
"version": "0.9.1",
|
||||
"providerCount": 34,
|
||||
"toolCount": 66,
|
||||
"sandboxBackends": [
|
||||
"seatbelt (macOS, when available)",
|
||||
"bubblewrap (Linux, opt-in when installed)"
|
||||
],
|
||||
"sources": [
|
||||
"Cargo.toml",
|
||||
"crates/tui/src/config.rs",
|
||||
"crates/tui/src/sandbox/mod.rs",
|
||||
"web/scripts/derive-facts.mjs"
|
||||
]
|
||||
},
|
||||
@@ -102,7 +107,8 @@
|
||||
"telemetry": "no Codewhale product telemetry or mandatory hosted relay",
|
||||
"account": "no account required for the local runtime",
|
||||
"plan": "Plan is read-only",
|
||||
"sandbox": "sandbox implementations and limitations are platform-specific",
|
||||
"sandbox": "Seatbelt is used on macOS when available; Linux bubblewrap is opt-in and must be installed; Windows currently reports no OS sandbox",
|
||||
"audit": "sensitive events append best-effort to $CODEWHALE_HOME/audit.log (default ~/.codewhale/audit.log); write failures are logged",
|
||||
"usage": "provider token and cache usage is shown locally when available",
|
||||
"sources": [
|
||||
"docs/PROVIDERS.md",
|
||||
@@ -110,7 +116,9 @@
|
||||
"docs/SANDBOX.md",
|
||||
"docs/RUNTIME_API.md",
|
||||
"docs/WEB.md",
|
||||
"web/app/[locale]/faq/page.tsx"
|
||||
"crates/tui/src/audit.rs",
|
||||
"web/app/[locale]/faq/page.tsx",
|
||||
"web/app/[locale]/install/page.tsx"
|
||||
]
|
||||
},
|
||||
"platforms": {
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ default model, Node engines) are never hand-written into pages:
|
||||
|
||||
1. **Build time** — `scripts/derive-facts.mjs` runs as `prebuild` (and before
|
||||
`npm run dev`), parses the parent repo (`Cargo.toml`, `crates/tui/src/config.rs`,
|
||||
`crates/tui/src/sandbox/`, `npm/codewhale/package.json`) and writes
|
||||
`crates/tui/src/sandbox/mod.rs`, `npm/codewhale/package.json`) and writes
|
||||
`lib/facts.generated.ts`. Never edit that file by hand.
|
||||
2. **Published release** — `data/latest-published-release.json` records the
|
||||
latest GitHub Release separately from the source candidate. Install commands
|
||||
|
||||
@@ -225,9 +225,9 @@ default_text_model = "openrouter/deepseek/deepseek-v4-pro"`}
|
||||
provider you select receives the prompt, project context, tool definitions,
|
||||
and tool results required for that turn. Use a loopback local-model route to
|
||||
keep model inference local.
|
||||
Sandbox backends: <strong>seatbelt</strong> (macOS), <strong>landlock</strong> (Linux), restricted tokens (Windows).
|
||||
OS command sandboxing is platform-specific: Codewhale uses <strong>Seatbelt</strong> on macOS when available. On Linux it uses <strong>bubblewrap</strong> only when <code className="inline">prefer_bwrap = true</code> and <code className="inline">/usr/bin/bwrap</code> is executable; otherwise commands have no Codewhale OS wrapper. Windows currently reports no OS sandbox.
|
||||
Workspace boundaries default to <code className="inline">--workspace</code>. <code className="inline">/trust</code> lifts them.
|
||||
Permission posture is configurable per session. All credential/approval/elevation events are written to <code className="inline">~/.codewhale/audit.log</code>.
|
||||
Permission posture is configurable per session. Sensitive credential, approval, and elevation events are appended best-effort to <code className="inline">$CODEWHALE_HOME/audit.log</code> (default <code className="inline">~/.codewhale/audit.log</code>); write failures are logged.
|
||||
</>
|
||||
),
|
||||
sources: ["SECURITY.md", "docs/PROVIDERS.md", "docs/RUNTIME_API.md"],
|
||||
@@ -572,9 +572,9 @@ default_text_model = "openrouter/deepseek/deepseek-v4-pro"`}
|
||||
Codewhale 运行时、工作区状态与审计日志保留在你的机器上;Codewhale
|
||||
没有产品遥测,也不要求经过托管中继。你选择的托管 provider 会收到本轮所需的
|
||||
prompt、项目上下文、工具定义与工具结果。若要让模型推理也保持本地,请使用回环地址上的本地模型路由。
|
||||
沙箱后端:<strong>seatbelt</strong>(macOS)、<strong>landlock</strong>(Linux)、受限令牌(Windows)。
|
||||
OS 命令沙箱因平台而异:macOS 在可用时使用 <strong>Seatbelt</strong>。Linux 仅在 <code className="inline">prefer_bwrap = true</code> 且 <code className="inline">/usr/bin/bwrap</code> 可执行时使用 <strong>bubblewrap</strong>;否则命令没有 Codewhale OS 包装器。Windows 当前报告无 OS 沙箱。
|
||||
工作区边界默认为 <code className="inline">--workspace</code>。<code className="inline">/trust</code> 可解除边界。
|
||||
权限姿态可按会话配置。所有凭证/审批/提权事件写入 <code className="inline">~/.codewhale/audit.log</code>。
|
||||
权限姿态可按会话配置。敏感的凭证、审批和提权事件会尽力追加到 <code className="inline">$CODEWHALE_HOME/audit.log</code>(默认 <code className="inline">~/.codewhale/audit.log</code>);写入失败会记录日志。
|
||||
</>
|
||||
),
|
||||
sources: ["SECURITY.md", "docs/PROVIDERS.md", "docs/RUNTIME_API.md"],
|
||||
|
||||
@@ -59,27 +59,27 @@ const FROM_SOURCE = `git clone https://github.com/Hmbown/CodeWhale
|
||||
cd CodeWhale
|
||||
cargo build --release --locked
|
||||
|
||||
# Install both binaries from the local checkout
|
||||
cargo install --path crates/cli --locked # codewhale
|
||||
# Install two Cargo packages; together they provide three commands
|
||||
cargo install --path crates/cli --locked # codewhale + codew
|
||||
cargo install --path crates/tui --locked # codewhale-tui`;
|
||||
|
||||
const CONFIG_TREE = `~/.codewhale/
|
||||
const CONFIG_TREE = `$CODEWHALE_HOME/ (default: ~/.codewhale/)
|
||||
├── config.toml api keys, model, hooks, profiles
|
||||
├── mcp.json MCP server definitions
|
||||
├── skills/ user skills (each with SKILL.md)
|
||||
├── sessions/ checkpoints + offline queue
|
||||
├── tasks/ background task store
|
||||
└── audit.log credential / approval / elevation audit trail
|
||||
└── audit.log best-effort credential / approval / elevation events
|
||||
|
||||
./.codewhale/ project-scoped config (optional, per-repo)`;
|
||||
|
||||
const CONFIG_TREE_ZH = `~/.codewhale/
|
||||
const CONFIG_TREE_ZH = `$CODEWHALE_HOME/(默认:~/.codewhale/)
|
||||
├── config.toml API 密钥、模型、钩子、配置集
|
||||
├── mcp.json MCP 服务器定义
|
||||
├── skills/ 用户技能(每个含 SKILL.md)
|
||||
├── sessions/ 检查点 + 离线队列
|
||||
├── tasks/ 后台任务存储
|
||||
└── audit.log 凭证 / 审批 / 提权审计日志
|
||||
└── audit.log 尽力写入的凭证 / 审批 / 提权事件
|
||||
|
||||
./.codewhale/ 项目级配置(可选,每个仓库)`;
|
||||
|
||||
@@ -189,8 +189,10 @@ codewhale doctor`;
|
||||
<code className="inline">curl</code> 命令覆盖更新。
|
||||
通过包管理器安装的话,用包管理器升级更稳:npm 安装的运行{" "}
|
||||
<code className="inline">npm update -g codewhale</code>;
|
||||
Cargo 安装的重跑两个 <code className="inline">cargo install</code> 命令并加{" "}
|
||||
<code className="inline">--force</code>;
|
||||
Cargo 安装的重跑两个 package 的 <code className="inline">cargo install</code> 命令并加{" "}
|
||||
<code className="inline">--force</code>(<code className="inline">codewhale-cli</code> 提供
|
||||
<code className="inline">codewhale</code> 与 <code className="inline">codew</code>,
|
||||
<code className="inline">codewhale-tui</code> 提供同名命令);
|
||||
旧版 Homebrew tap 用 <code className="inline">brew upgrade deepseek-tui</code>。
|
||||
</>
|
||||
) : (
|
||||
@@ -199,9 +201,12 @@ codewhale doctor`;
|
||||
installed with <code className="inline">install.sh</code>, re-run the same{" "}
|
||||
<code className="inline">curl</code> command to overwrite the binaries.
|
||||
If you installed via a package manager, prefer it instead: npm users run{" "}
|
||||
<code className="inline">npm update -g codewhale</code>; cargo users re-run both{" "}
|
||||
<code className="inline">cargo install</code> commands with{" "}
|
||||
<code className="inline">--force</code>; the legacy Homebrew tap updates with{" "}
|
||||
<code className="inline">npm update -g codewhale</code>; cargo users re-run the two
|
||||
package <code className="inline">cargo install</code> commands with{" "}
|
||||
<code className="inline">--force</code> (<code className="inline">codewhale-cli</code>
|
||||
provides <code className="inline">codewhale</code> and <code className="inline">codew</code>;
|
||||
<code className="inline">codewhale-tui</code> provides the command of the same name);
|
||||
the legacy Homebrew tap updates with{" "}
|
||||
<code className="inline">brew upgrade deepseek-tui</code>.
|
||||
</>
|
||||
)}
|
||||
@@ -349,16 +354,19 @@ codewhale doctor`;
|
||||
<p className="mt-3 text-sm text-ink-soft leading-relaxed max-w-2xl">
|
||||
{isZh ? (
|
||||
<>
|
||||
编译并安装 <code className="inline">codewhale</code> 和 <code className="inline">codewhale-tui</code> 到 <code className="inline">~/.cargo/bin</code>。
|
||||
两个 Cargo package 会把 <code className="inline">codewhale</code>、
|
||||
<code className="inline">codew</code> 和 <code className="inline">codewhale-tui</code>
|
||||
三个命令安装到 <code className="inline">~/.cargo/bin</code>。
|
||||
需要 Rust 1.88+;Linux 用户先安装 <code className="inline">pkg-config</code> 和{" "}
|
||||
<code className="inline">libdbus-1-dev</code> 等构建依赖。如未安装 Rust,可访问{" "}
|
||||
<a href="https://rustup.rs" className="body-link">rustup.rs</a>。
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Compiles and installs <code className="inline">codewhale</code> and{" "}
|
||||
<code className="inline">codewhale-tui</code> to{" "}
|
||||
<code className="inline">~/.cargo/bin</code>. Requires Rust 1.88+; install via{" "}
|
||||
The two Cargo packages install three commands—
|
||||
<code className="inline">codewhale</code>, <code className="inline">codew</code>, and{" "}
|
||||
<code className="inline">codewhale-tui</code>—to <code className="inline">~/.cargo/bin</code>.
|
||||
Requires Rust 1.88+; install via{" "}
|
||||
<a href="https://rustup.rs" className="body-link">rustup.rs</a> if you don't have it.
|
||||
On Linux, install build dependencies such as{" "}
|
||||
<code className="inline">pkg-config</code> and{" "}
|
||||
|
||||
@@ -47,8 +47,8 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
path: "/",
|
||||
locale,
|
||||
title: isZh
|
||||
? "Codewhale — 一个运行时,所有模型,你的机器"
|
||||
: "Codewhale — One runtime. Every model. Your machine.",
|
||||
? "Codewhale — 一个运行时,支持的托管与本地模型,你的机器"
|
||||
: "Codewhale — One runtime. Supported hosted and local models. Your machine.",
|
||||
description: isZh
|
||||
? "开源终端编程智能体,适配受支持的托管与本地模型,开放模型优先。TUI、CLI 与本地工具——运行在你自己的机器上。Rust 编写,MIT 许可。"
|
||||
: "Open-source terminal coding agent for supported hosted and local models, open models first. TUI, CLI, and local tools — runs on your machine. Rust, MIT.",
|
||||
|
||||
@@ -72,7 +72,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
|
||||
<>
|
||||
一个运行时。
|
||||
<br />
|
||||
所有模型。
|
||||
支持的托管与本地模型。
|
||||
<br />
|
||||
<span>你的机器。</span>
|
||||
</>
|
||||
@@ -80,7 +80,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
|
||||
<>
|
||||
One runtime.
|
||||
<br />
|
||||
Every model.
|
||||
Supported hosted and local models.
|
||||
<br />
|
||||
<span>Your machine.</span>
|
||||
</>
|
||||
|
||||
@@ -26,7 +26,7 @@ const tracksEn = [
|
||||
{ title: "Sub-agent parallel execution", note: "agent; 64 concurrent sessions by default, configurable to 128, with bounded result handles" },
|
||||
{ title: "RLM batched processing", note: "Persistent sandboxed Python REPL with 1–16 cheap parallel children for long-input analysis" },
|
||||
{ title: "Three operating modes", note: "Plan (read-only), Act (execution), Operate (Fleet/Workflow orchestration); orthogonal Ask / Auto-Review / Full Access posture" },
|
||||
{ title: "Per-platform sandbox", note: "seatbelt (macOS), landlock (Linux); Windows containment via restricted tokens (limited)" },
|
||||
{ title: "OS command sandbox", note: "Seatbelt on macOS when available; opt-in bubblewrap on Linux when installed. Windows currently reports no OS sandbox." },
|
||||
{ title: "Durable sessions + tasks", note: "Save, resume, rollback; background task queue with replayable timelines under ~/.codewhale/tasks/" },
|
||||
{ title: "Bidirectional MCP", note: "Consume tools from external servers; expose as server via `codewhale mcp`; ~/.codewhale/mcp.json" },
|
||||
{ title: "Skills + unified slash palette", note: "~/.codewhale/skills/ auto-loading; /help, /mode, /status, /config, /trust, /feedback" },
|
||||
@@ -84,7 +84,7 @@ const tracksZh = [
|
||||
{ title: "子 Agent 并行执行", note: "agent;默认 64 个并发会话,可配置到 128 个,通过 var_handle 有界读取结果" },
|
||||
{ title: "RLM 批量处理", note: "持久沙箱 Python REPL,支持 1–16 路廉价并行子调用,处理长文本分析" },
|
||||
{ title: "三种运行模式", note: "Plan(只读调查)、Act(执行)与 Operate(Fleet / Workflow 编排);Ask、Auto-Review 与 Full Access 权限姿态独立设置" },
|
||||
{ title: "跨平台沙箱", note: "seatbelt(macOS)、landlock(Linux);Windows 通过受限令牌实现基础隔离(功能有限)" },
|
||||
{ title: "OS 命令沙箱", note: "macOS 在可用时使用 Seatbelt;Linux 在安装后可显式启用 bubblewrap。Windows 当前报告无 OS 沙箱。" },
|
||||
{ title: "持久化会话 + 后台任务", note: "保存、恢复、回滚;后台任务队列,可回放时间线,位于 ~/.codewhale/tasks/" },
|
||||
{ title: "双向 MCP 协议", note: "消费外部服务器工具;通过 `codewhale mcp` 暴露为服务器;~/.codewhale/mcp.json" },
|
||||
{ title: "技能 + 统一命令面板", note: "~/.codewhale/skills/ 自动加载;/help、/mode、/status、/config、/trust、/feedback" },
|
||||
|
||||
@@ -6,93 +6,9 @@ import {
|
||||
type Arch,
|
||||
type UserAgentArchitecture,
|
||||
} from "@/lib/install-platform";
|
||||
import { SNIPPETS, VERIFY } from "@/lib/install-binary-snippets";
|
||||
import { InstallCodeBlock } from "./install-code-block";
|
||||
|
||||
function windowsSnippet(arch: "x64" | "arm64"): string {
|
||||
return `# PowerShell
|
||||
$ErrorActionPreference = "Stop"
|
||||
$dest = "$Env:USERPROFILE\\bin"
|
||||
New-Item -ItemType Directory -Force $dest | Out-Null
|
||||
$manifest = Invoke-WebRequest https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
|
||||
Invoke-WebRequest \`
|
||||
-Uri https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-windows-${arch}.exe \`
|
||||
-OutFile "$dest\\codewhale.exe"
|
||||
Invoke-WebRequest \`
|
||||
-Uri https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-windows-${arch}.exe \`
|
||||
-OutFile "$dest\\codewhale-tui.exe"
|
||||
|
||||
$expected = @{}
|
||||
$manifest.Content -split "\`n" | ForEach-Object {
|
||||
$parts = $_.Trim() -split "\\s+"
|
||||
if ($parts.Length -ge 2) { $expected[$parts[1]] = $parts[0].ToUpperInvariant() }
|
||||
}
|
||||
if ((Get-FileHash "$dest\\codewhale.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-windows-${arch}.exe"]) { throw "codewhale.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$dest\\codewhale-tui.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-tui-windows-${arch}.exe"]) { throw "codewhale-tui.exe checksum mismatch" }
|
||||
|
||||
$Env:Path = "$dest;$Env:Path"`;
|
||||
}
|
||||
|
||||
function windowsVerify(arch: "x64" | "arm64"): string {
|
||||
return `# PowerShell
|
||||
$manifest = Invoke-WebRequest https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
$expected = @{}
|
||||
$manifest.Content -split "\`n" | ForEach-Object {
|
||||
$parts = $_.Trim() -split "\\s+"
|
||||
if ($parts.Length -ge 2) { $expected[$parts[1]] = $parts[0].ToUpperInvariant() }
|
||||
}
|
||||
if ((Get-FileHash "$Env:USERPROFILE\\bin\\codewhale.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-windows-${arch}.exe"]) { throw "codewhale.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$Env:USERPROFILE\\bin\\codewhale-tui.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-tui-windows-${arch}.exe"]) { throw "codewhale-tui.exe checksum mismatch" }`;
|
||||
}
|
||||
|
||||
const SNIPPETS: Record<Arch, string> = {
|
||||
"macos-arm64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -o codewhale \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-macos-arm64
|
||||
curl -fsSL -o codewhale-tui \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-macos-arm64
|
||||
grep -E ' (codewhale|codewhale-tui)-macos-arm64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -
|
||||
chmod +x codewhale codewhale-tui
|
||||
xattr -d com.apple.quarantine codewhale codewhale-tui 2>/dev/null || true
|
||||
sudo mv codewhale codewhale-tui /usr/local/bin/`,
|
||||
"macos-x64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -o codewhale \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-macos-x64
|
||||
curl -fsSL -o codewhale-tui \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-macos-x64
|
||||
grep -E ' (codewhale|codewhale-tui)-macos-x64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -
|
||||
chmod +x codewhale codewhale-tui
|
||||
xattr -d com.apple.quarantine codewhale codewhale-tui 2>/dev/null || true
|
||||
sudo mv codewhale codewhale-tui /usr/local/bin/`,
|
||||
"linux-x64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -o codewhale \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-linux-x64
|
||||
curl -fsSL -o codewhale-tui \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-linux-x64
|
||||
grep -E ' (codewhale|codewhale-tui)-linux-x64$' codewhale-artifacts-sha256.txt | sha256sum -c -
|
||||
chmod +x codewhale codewhale-tui
|
||||
sudo mv codewhale codewhale-tui /usr/local/bin/`,
|
||||
"linux-arm64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -o codewhale \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-linux-arm64
|
||||
curl -fsSL -o codewhale-tui \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-linux-arm64
|
||||
grep -E ' (codewhale|codewhale-tui)-linux-arm64$' codewhale-artifacts-sha256.txt | sha256sum -c -
|
||||
chmod +x codewhale codewhale-tui
|
||||
sudo mv codewhale codewhale-tui /usr/local/bin/`,
|
||||
"windows-x64": windowsSnippet("x64"),
|
||||
"windows-arm64": windowsSnippet("arm64"),
|
||||
};
|
||||
|
||||
const VERIFY: Record<Arch, string> = {
|
||||
"macos-arm64": `grep -E ' (codewhale|codewhale-tui)-macos-arm64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -`,
|
||||
"macos-x64": `grep -E ' (codewhale|codewhale-tui)-macos-x64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -`,
|
||||
"linux-x64": `grep -E ' (codewhale|codewhale-tui)-linux-x64$' codewhale-artifacts-sha256.txt | sha256sum -c -`,
|
||||
"linux-arm64": `grep -E ' (codewhale|codewhale-tui)-linux-arm64$' codewhale-artifacts-sha256.txt | sha256sum -c -`,
|
||||
"windows-x64": windowsVerify("x64"),
|
||||
"windows-arm64": windowsVerify("arm64"),
|
||||
};
|
||||
|
||||
const LABELS: Record<Arch, string> = {
|
||||
"macos-arm64": "macOS · Apple Silicon",
|
||||
"macos-x64": "macOS · Intel",
|
||||
|
||||
@@ -74,7 +74,10 @@ function freshFacts(overrides: Partial<RepoFacts> = {}): RepoFacts {
|
||||
sourceCommittedAt: null,
|
||||
version: "0.8.64",
|
||||
crates: ["cli", "config", "tui"],
|
||||
sandboxBackends: ["landlock (Linux)", "seatbelt (macOS)"],
|
||||
sandboxBackends: [
|
||||
"seatbelt (macOS, when available)",
|
||||
"bubblewrap (Linux, opt-in when installed)",
|
||||
],
|
||||
providers: [
|
||||
{ id: "deepseek", label: "DeepSeek", env: "DEEPSEEK_API_KEY" },
|
||||
{ id: "anthropic", label: "Anthropic", env: "ANTHROPIC_API_KEY" },
|
||||
|
||||
+2
-2
@@ -158,8 +158,8 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
slug: "sandbox",
|
||||
label: { en: "Sandbox & Approval", zh: "沙箱与审批" },
|
||||
description: {
|
||||
en: "seatbelt (macOS), landlock (Linux), Windows containment, and approval policies.",
|
||||
zh: "seatbelt(macOS)、landlock(Linux)、Windows 隔离和审批策略。",
|
||||
en: "Available Seatbelt (macOS), opt-in bubblewrap (Linux), platform gaps, and approval policies.",
|
||||
zh: "可用的 Seatbelt(macOS)、显式启用的 bubblewrap(Linux)、平台缺口和审批策略。",
|
||||
},
|
||||
repoSource: "docs/SANDBOX.md",
|
||||
hasPage: false,
|
||||
|
||||
@@ -29,16 +29,18 @@ function installGitHubFixture(toolCountSource: string | null): void {
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (url.includes("/contents/crates/tui/src/sandbox?")) {
|
||||
return response(JSON.stringify([{ name: "seatbelt.rs", type: "file" }]));
|
||||
}
|
||||
|
||||
const rawPath = url.split(`/${REVISION}/`)[1];
|
||||
const sources: Record<string, string> = {
|
||||
"Cargo.toml": 'version = "0.9.2"\nmembers = ["crates/tui"]',
|
||||
"crates/tui/src/config.rs":
|
||||
'pub enum ApiProvider {\n Deepseek,\n}\nconst DEFAULT_TEXT_MODEL: &str = "remote-model";',
|
||||
"crates/tui/src/config/models.rs": "",
|
||||
"crates/tui/src/sandbox/mod.rs": `
|
||||
pub const PUBLIC_SANDBOX_BACKENDS: &[&str] = &[
|
||||
"seatbelt (macOS, when available)",
|
||||
"bubblewrap (Linux, opt-in when installed)",
|
||||
];
|
||||
`,
|
||||
"npm/codewhale/package.json": JSON.stringify({ engines: { node: ">=18" } }),
|
||||
LICENSE: "MIT License\n",
|
||||
};
|
||||
@@ -67,6 +69,14 @@ describe("deriveFactsFromRemote", () => {
|
||||
expect(facts?.sourceRevision).toBe(REVISION);
|
||||
expect(facts?.version).toBe("0.9.2");
|
||||
expect(facts?.toolCount).toBe(73);
|
||||
expect(facts?.sandboxBackends).toEqual([
|
||||
"seatbelt (macOS, when available)",
|
||||
"bubblewrap (Linux, opt-in when installed)",
|
||||
]);
|
||||
const fetchMock = vi.mocked(fetch);
|
||||
expect(
|
||||
fetchMock.mock.calls.some(([input]) => String(input).includes("/contents/")),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("fails derivation when the exact revision has no valid tool count", async () => {
|
||||
|
||||
+11
-36
@@ -51,29 +51,6 @@ async function fetchText(
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchListing(
|
||||
dir: string,
|
||||
revision: string,
|
||||
ghToken?: string,
|
||||
): Promise<string[] | null> {
|
||||
// Use GitHub Contents API to list a directory.
|
||||
const url = `https://api.github.com/repos/Hmbown/CodeWhale/contents/${dir}?ref=${revision}`;
|
||||
const headers: Record<string, string> = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "codewhale-web-drift",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
};
|
||||
if (ghToken) headers["Authorization"] = `Bearer ${ghToken}`;
|
||||
try {
|
||||
const r = await fetch(url, { headers });
|
||||
if (!r.ok) return null;
|
||||
const arr = (await r.json()) as { name: string; type: string }[];
|
||||
return arr.filter((e) => e.type === "file").map((e) => e.name);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSourceMarker(ghToken?: string): Promise<SourceMarker | null> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/vnd.github+json",
|
||||
@@ -184,16 +161,12 @@ function deriveDefaultModel(cfg: string): string | null {
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
function deriveSandboxBackends(files: string[]): string[] {
|
||||
const map: Record<string, string> = {
|
||||
seatbelt: "seatbelt (macOS)",
|
||||
landlock: "landlock (Linux)",
|
||||
};
|
||||
return files
|
||||
.map((f) => f.replace(/\.rs$/, ""))
|
||||
.filter((name) => !["mod", "policy", "backend", "opensandbox", "windows"].includes(name))
|
||||
.sort()
|
||||
.map((name) => map[name] ?? name);
|
||||
function deriveSandboxBackends(source: string): string[] {
|
||||
const marker = source.match(
|
||||
/pub const PUBLIC_SANDBOX_BACKENDS\s*:\s*&\[&str\]\s*=\s*&\[([\s\S]*?)\];/,
|
||||
);
|
||||
if (!marker) return [];
|
||||
return [...marker[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
||||
}
|
||||
|
||||
async function fetchLatestPublishedRelease(
|
||||
@@ -262,11 +235,11 @@ export async function deriveFactsFromRemote(ghToken?: string): Promise<RepoFacts
|
||||
const source = await fetchSourceMarker(ghToken);
|
||||
if (!source) return null;
|
||||
|
||||
const [cargo, configRs, configModels, sandboxFiles, npmPkg, licText, generatedFacts, latestPublishedRelease] = await Promise.all([
|
||||
const [cargo, configRs, configModels, sandboxSource, npmPkg, licText, generatedFacts, latestPublishedRelease] = await Promise.all([
|
||||
fetchText("Cargo.toml", source.revision, ghToken),
|
||||
fetchText("crates/tui/src/config.rs", source.revision, ghToken),
|
||||
fetchText("crates/tui/src/config/models.rs", source.revision, ghToken),
|
||||
fetchListing("crates/tui/src/sandbox", source.revision, ghToken),
|
||||
fetchText("crates/tui/src/sandbox/mod.rs", source.revision, ghToken),
|
||||
fetchText("npm/codewhale/package.json", source.revision, ghToken),
|
||||
fetchText("LICENSE", source.revision, ghToken),
|
||||
fetchText("web/lib/facts.generated.ts", source.revision, ghToken),
|
||||
@@ -288,7 +261,9 @@ export async function deriveFactsFromRemote(ghToken?: string): Promise<RepoFacts
|
||||
sourceCommittedAt: source.committedAt,
|
||||
version: deriveVersion(cargo),
|
||||
crates: deriveCrates(cargo),
|
||||
sandboxBackends: sandboxFiles ? deriveSandboxBackends(sandboxFiles) : BUILD_FACTS.sandboxBackends,
|
||||
sandboxBackends: sandboxSource
|
||||
? deriveSandboxBackends(sandboxSource)
|
||||
: BUILD_FACTS.sandboxBackends,
|
||||
providers: deriveProvidersFromConfig(configRs),
|
||||
defaultModel: deriveDefaultModel(`${configRs}\n${configModels ?? ""}`),
|
||||
nodeEngines: (() => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface RepoFacts {
|
||||
}
|
||||
|
||||
export const FACTS: RepoFacts = {
|
||||
"generatedAt": "2026-07-21T23:08:04.820Z",
|
||||
"generatedAt": "2026-07-22T00:51:16.685Z",
|
||||
"sourceRevision": null,
|
||||
"sourceCommittedAt": null,
|
||||
"version": "0.9.1",
|
||||
@@ -52,11 +52,8 @@ export const FACTS: RepoFacts = {
|
||||
"workflow-js"
|
||||
],
|
||||
"sandboxBackends": [
|
||||
"bwrap",
|
||||
"landlock (Linux)",
|
||||
"process_hardening",
|
||||
"seatbelt (macOS)",
|
||||
"seccomp"
|
||||
"seatbelt (macOS, when available)",
|
||||
"bubblewrap (Linux, opt-in when installed)"
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { Arch } from "./install-platform";
|
||||
|
||||
function windowsSnippet(arch: "x64" | "arm64"): string {
|
||||
return `# PowerShell
|
||||
$ErrorActionPreference = "Stop"
|
||||
$dest = "$Env:USERPROFILE\\bin"
|
||||
New-Item -ItemType Directory -Force $dest | Out-Null
|
||||
$manifest = Invoke-WebRequest https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
|
||||
Invoke-WebRequest \`
|
||||
-Uri https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-windows-${arch}.exe \`
|
||||
-OutFile "$dest\\codewhale.exe"
|
||||
Invoke-WebRequest \`
|
||||
-Uri https://github.com/Hmbown/CodeWhale/releases/latest/download/codew-windows-${arch}.exe \`
|
||||
-OutFile "$dest\\codew.exe"
|
||||
Invoke-WebRequest \`
|
||||
-Uri https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-windows-${arch}.exe \`
|
||||
-OutFile "$dest\\codewhale-tui.exe"
|
||||
|
||||
$expected = @{}
|
||||
$manifest.Content -split "\`n" | ForEach-Object {
|
||||
$parts = $_.Trim() -split "\\s+"
|
||||
if ($parts.Length -ge 2) { $expected[$parts[1]] = $parts[0].ToUpperInvariant() }
|
||||
}
|
||||
if ((Get-FileHash "$dest\\codewhale.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-windows-${arch}.exe"]) { throw "codewhale.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$dest\\codew.exe" -Algorithm SHA256).Hash -ne $expected["codew-windows-${arch}.exe"]) { throw "codew.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$dest\\codewhale-tui.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-tui-windows-${arch}.exe"]) { throw "codewhale-tui.exe checksum mismatch" }
|
||||
|
||||
$Env:Path = "$dest;$Env:Path"`;
|
||||
}
|
||||
|
||||
function windowsVerify(arch: "x64" | "arm64"): string {
|
||||
return `# PowerShell
|
||||
$manifest = Invoke-WebRequest https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
$expected = @{}
|
||||
$manifest.Content -split "\`n" | ForEach-Object {
|
||||
$parts = $_.Trim() -split "\\s+"
|
||||
if ($parts.Length -ge 2) { $expected[$parts[1]] = $parts[0].ToUpperInvariant() }
|
||||
}
|
||||
if ((Get-FileHash "$Env:USERPROFILE\\bin\\codewhale.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-windows-${arch}.exe"]) { throw "codewhale.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$Env:USERPROFILE\\bin\\codew.exe" -Algorithm SHA256).Hash -ne $expected["codew-windows-${arch}.exe"]) { throw "codew.exe checksum mismatch" }
|
||||
if ((Get-FileHash "$Env:USERPROFILE\\bin\\codewhale-tui.exe" -Algorithm SHA256).Hash -ne $expected["codewhale-tui-windows-${arch}.exe"]) { throw "codewhale-tui.exe checksum mismatch" }`;
|
||||
}
|
||||
|
||||
export const SNIPPETS: Record<Arch, string> = {
|
||||
"macos-arm64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-macos-arm64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codew-macos-arm64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-macos-arm64
|
||||
grep -E ' (codewhale|codew|codewhale-tui)-macos-arm64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -
|
||||
chmod +x codewhale-macos-arm64 codew-macos-arm64 codewhale-tui-macos-arm64
|
||||
xattr -d com.apple.quarantine codewhale-macos-arm64 codew-macos-arm64 codewhale-tui-macos-arm64 2>/dev/null || true
|
||||
sudo mv codewhale-macos-arm64 /usr/local/bin/codewhale
|
||||
sudo mv codew-macos-arm64 /usr/local/bin/codew
|
||||
sudo mv codewhale-tui-macos-arm64 /usr/local/bin/codewhale-tui`,
|
||||
"macos-x64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-macos-x64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codew-macos-x64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-macos-x64
|
||||
grep -E ' (codewhale|codew|codewhale-tui)-macos-x64$' codewhale-artifacts-sha256.txt | shasum -a 256 -c -
|
||||
chmod +x codewhale-macos-x64 codew-macos-x64 codewhale-tui-macos-x64
|
||||
xattr -d com.apple.quarantine codewhale-macos-x64 codew-macos-x64 codewhale-tui-macos-x64 2>/dev/null || true
|
||||
sudo mv codewhale-macos-x64 /usr/local/bin/codewhale
|
||||
sudo mv codew-macos-x64 /usr/local/bin/codew
|
||||
sudo mv codewhale-tui-macos-x64 /usr/local/bin/codewhale-tui`,
|
||||
"linux-x64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-linux-x64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codew-linux-x64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-linux-x64
|
||||
grep -E ' (codewhale|codew|codewhale-tui)-linux-x64$' codewhale-artifacts-sha256.txt | sha256sum -c -
|
||||
chmod +x codewhale-linux-x64 codew-linux-x64 codewhale-tui-linux-x64
|
||||
sudo mv codewhale-linux-x64 /usr/local/bin/codewhale
|
||||
sudo mv codew-linux-x64 /usr/local/bin/codew
|
||||
sudo mv codewhale-tui-linux-x64 /usr/local/bin/codewhale-tui`,
|
||||
"linux-arm64": `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-linux-arm64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codew-linux-arm64
|
||||
curl -fsSL -O \\
|
||||
https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-tui-linux-arm64
|
||||
grep -E ' (codewhale|codew|codewhale-tui)-linux-arm64$' codewhale-artifacts-sha256.txt | sha256sum -c -
|
||||
chmod +x codewhale-linux-arm64 codew-linux-arm64 codewhale-tui-linux-arm64
|
||||
sudo mv codewhale-linux-arm64 /usr/local/bin/codewhale
|
||||
sudo mv codew-linux-arm64 /usr/local/bin/codew
|
||||
sudo mv codewhale-tui-linux-arm64 /usr/local/bin/codewhale-tui`,
|
||||
"windows-x64": windowsSnippet("x64"),
|
||||
"windows-arm64": windowsSnippet("arm64"),
|
||||
};
|
||||
|
||||
function unixVerify(platform: string, checksumCommand: string): string {
|
||||
return `curl -fsSL -O https://github.com/Hmbown/CodeWhale/releases/latest/download/codewhale-artifacts-sha256.txt
|
||||
verify_binary() {
|
||||
asset="$1"
|
||||
installed="$2"
|
||||
expected=$(awk -v asset="$asset" '$2 == asset { print $1 }' codewhale-artifacts-sha256.txt)
|
||||
actual=$(${checksumCommand} "$installed" | awk '{ print $1 }')
|
||||
if [ -z "$expected" ] || [ "$actual" != "$expected" ]; then
|
||||
echo "$installed checksum mismatch" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
verify_binary codewhale-${platform} /usr/local/bin/codewhale
|
||||
verify_binary codew-${platform} /usr/local/bin/codew
|
||||
verify_binary codewhale-tui-${platform} /usr/local/bin/codewhale-tui`;
|
||||
}
|
||||
|
||||
export const VERIFY: Record<Arch, string> = {
|
||||
"macos-arm64": unixVerify("macos-arm64", "shasum -a 256"),
|
||||
"macos-x64": unixVerify("macos-x64", "shasum -a 256"),
|
||||
"linux-x64": unixVerify("linux-x64", "sha256sum"),
|
||||
"linux-arm64": unixVerify("linux-arm64", "sha256sum"),
|
||||
"windows-x64": windowsVerify("x64"),
|
||||
"windows-arm64": windowsVerify("arm64"),
|
||||
};
|
||||
@@ -6,7 +6,7 @@ export const SITE_URL = "https://codewhale.net";
|
||||
export const SITE_NAME = "Codewhale";
|
||||
|
||||
/** The one-line product identity, used as the default OG image alt text. */
|
||||
export const IDENTITY_PHRASE = "One runtime. Every model. Your machine.";
|
||||
export const IDENTITY_PHRASE = "One runtime. Supported hosted and local models. Your machine.";
|
||||
|
||||
/** Shared OG card rendered by app/opengraph-image.tsx (1200×630 PNG). */
|
||||
const OG_IMAGE = {
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { FACTS } from "./facts.generated";
|
||||
import { SNIPPETS } from "./install-binary-snippets";
|
||||
|
||||
const root = new URL("../../", import.meta.url);
|
||||
|
||||
@@ -13,7 +25,12 @@ type PublicSurfaceMatrix = {
|
||||
license: string;
|
||||
terminology: Record<string, string>;
|
||||
};
|
||||
sourceCandidate: { version: string; providerCount: number; toolCount: number };
|
||||
sourceCandidate: {
|
||||
version: string;
|
||||
providerCount: number;
|
||||
toolCount: number;
|
||||
sandboxBackends: string[];
|
||||
};
|
||||
latestPublishedRelease: {
|
||||
tag: string;
|
||||
version: string;
|
||||
@@ -94,6 +111,7 @@ describe("public surface contracts", () => {
|
||||
expect(matrix.sourceCandidate.version).toBe(FACTS.version);
|
||||
expect(matrix.sourceCandidate.providerCount).toBe(FACTS.providers.length);
|
||||
expect(matrix.sourceCandidate.toolCount).toBe(FACTS.toolCount);
|
||||
expect(matrix.sourceCandidate.sandboxBackends).toEqual(FACTS.sandboxBackends);
|
||||
expect({
|
||||
tag: matrix.latestPublishedRelease.tag,
|
||||
version: matrix.latestPublishedRelease.version,
|
||||
@@ -160,6 +178,137 @@ describe("public surface contracts", () => {
|
||||
expect(changelog).not.toContain("compare/v0.9.1...HEAD");
|
||||
});
|
||||
|
||||
it("distinguishes two Cargo packages from the three installed commands", () => {
|
||||
const installDoc = text("docs/INSTALL.md");
|
||||
const installPage = text("web/app/[locale]/install/page.tsx");
|
||||
|
||||
expect(installDoc).toContain("Two Cargo packages are required");
|
||||
expect(installDoc).toContain(
|
||||
"`codewhale-cli` installs the `codewhale` and `codew` commands",
|
||||
);
|
||||
expect(installDoc).toContain(
|
||||
"Download all three matching `codewhale`, `codew`, and `codewhale-tui`",
|
||||
);
|
||||
expect(installPage).toContain(
|
||||
"# Install two Cargo packages; together they provide three commands",
|
||||
);
|
||||
expect(installPage).toContain("# codewhale + codew");
|
||||
expect(installPage).toContain("The two Cargo packages install three commands");
|
||||
expect(installPage).not.toContain("Install both binaries");
|
||||
expect(installDoc).not.toContain("install both binaries from the release tag");
|
||||
for (const platform of [
|
||||
"macos-arm64",
|
||||
"macos-x64",
|
||||
"linux-arm64",
|
||||
"linux-x64",
|
||||
] as const) {
|
||||
expect(SNIPPETS[platform], platform).toContain(`codew-${platform}`);
|
||||
expect(SNIPPETS[platform], platform).toContain(
|
||||
`sudo mv codew-${platform} /usr/local/bin/codew`,
|
||||
);
|
||||
}
|
||||
for (const arch of ["x64", "arm64"] as const) {
|
||||
expect(SNIPPETS[`windows-${arch}`], arch).toContain(`codew-windows-${arch}.exe`);
|
||||
expect(SNIPPETS[`windows-${arch}`], arch).toContain(
|
||||
'Get-FileHash "$dest\\codew.exe"',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("checks Unix release assets under their manifest filenames before renaming", () => {
|
||||
const scratch = mkdtempSync(join(tmpdir(), "codewhale-install-checksum-"));
|
||||
const mockBin = join(scratch, "bin");
|
||||
const curlPath = join(mockBin, "curl");
|
||||
const checksumPath = join(mockBin, "checksum");
|
||||
|
||||
try {
|
||||
const mkdir = spawnSync("/bin/mkdir", ["-p", mockBin]);
|
||||
expect(mkdir.status, mkdir.stderr.toString()).toBe(0);
|
||||
|
||||
writeFileSync(
|
||||
curlPath,
|
||||
`#!/bin/sh
|
||||
output=""
|
||||
url=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) shift; output="$1" ;;
|
||||
http*) url="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
[ -n "$output" ] || output=$(basename "$url")
|
||||
if [ "$output" = codewhale-artifacts-sha256.txt ]; then
|
||||
for platform in macos-arm64 macos-x64 linux-arm64 linux-x64; do
|
||||
for binary in codewhale codew codewhale-tui; do
|
||||
printf 'fixture-hash %s-%s\\n' "$binary" "$platform"
|
||||
done
|
||||
done > "$output"
|
||||
else
|
||||
printf 'fixture payload for %s\\n' "$output" > "$output"
|
||||
fi
|
||||
`,
|
||||
);
|
||||
writeFileSync(
|
||||
checksumPath,
|
||||
`#!/bin/sh
|
||||
while [ "$#" -gt 0 ]; do shift; done
|
||||
while read -r _hash filename; do
|
||||
if [ ! -f "$filename" ]; then
|
||||
echo "manifest target missing: $filename" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
`,
|
||||
);
|
||||
chmodSync(curlPath, 0o755);
|
||||
chmodSync(checksumPath, 0o755);
|
||||
for (const command of ["shasum", "sha256sum"]) {
|
||||
const link = spawnSync("/bin/ln", ["-s", checksumPath, join(mockBin, command)]);
|
||||
expect(link.status, link.stderr.toString()).toBe(0);
|
||||
}
|
||||
|
||||
for (const platform of [
|
||||
"macos-arm64",
|
||||
"macos-x64",
|
||||
"linux-arm64",
|
||||
"linux-x64",
|
||||
] as const) {
|
||||
const lines = SNIPPETS[platform].split("\n");
|
||||
const checksumLine = lines.findIndex((line) => line.includes(" -c -"));
|
||||
expect(checksumLine, platform).toBeGreaterThan(-1);
|
||||
const result = spawnSync(
|
||||
"/bin/bash",
|
||||
["-o", "pipefail", "-eu", "-c", lines.slice(0, checksumLine + 1).join("\n")],
|
||||
{
|
||||
cwd: scratch,
|
||||
env: { ...process.env, PATH: `${mockBin}:${process.env.PATH ?? ""}` },
|
||||
},
|
||||
);
|
||||
expect(result.status, `${platform}: ${result.stderr.toString()}`).toBe(0);
|
||||
}
|
||||
} finally {
|
||||
rmSync(scratch, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("qualifies the resolved audit path and best-effort persistence", () => {
|
||||
const installPage = text("web/app/[locale]/install/page.tsx");
|
||||
|
||||
expect(matrix.trust.audit).toContain("best-effort");
|
||||
expect(matrix.trust.audit).toContain("$CODEWHALE_HOME");
|
||||
expect(installPage).toContain(
|
||||
"const CONFIG_TREE = `$CODEWHALE_HOME/ (default: ~/.codewhale/)",
|
||||
);
|
||||
expect(installPage).toContain(
|
||||
"best-effort credential / approval / elevation events",
|
||||
);
|
||||
expect(installPage).toContain("尽力写入的凭证 / 审批 / 提权事件");
|
||||
expect(installPage).not.toContain(
|
||||
"audit.log credential / approval / elevation audit trail",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps modes, permission postures, and idle shortcuts exact", () => {
|
||||
const modes = text("docs/MODES.md");
|
||||
const keys = text("docs/KEYBINDINGS.md");
|
||||
|
||||
+11
-10
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* Sources of truth:
|
||||
* - <repo>/Cargo.toml → version, workspace crates
|
||||
* - <repo>/crates/tui/src/sandbox/*.rs → sandbox backends
|
||||
* - <repo>/crates/tui/src/sandbox/mod.rs → enforced sandbox markers
|
||||
* - <repo>/crates/tui/src/config.rs → provider list (ApiProvider enum), DEFAULT_TEXT_MODEL
|
||||
* - <repo>/npm/codewhale/package.json → node engines
|
||||
* - <repo>/crates/tui/src/tools/*.rs → tool count (ToolSpec impls)
|
||||
@@ -42,15 +42,16 @@ export function deriveCrates() {
|
||||
}
|
||||
|
||||
export function deriveSandboxBackends() {
|
||||
const dir = join(REPO_ROOT, "crates/tui/src/sandbox");
|
||||
if (!existsSync(dir)) return [];
|
||||
const files = readdirSync(dir)
|
||||
.filter((f) => f.endsWith(".rs"))
|
||||
.map((f) => f.replace(/\.rs$/, ""))
|
||||
.filter((f) => !["mod", "policy", "backend", "opensandbox", "windows"].includes(f))
|
||||
.sort();
|
||||
const map = { seatbelt: "seatbelt (macOS)", landlock: "landlock (Linux)" };
|
||||
return files.map((f) => map[f] ?? f);
|
||||
const source = read("crates/tui/src/sandbox/mod.rs");
|
||||
return source ? deriveSandboxBackendsFromSource(source) : [];
|
||||
}
|
||||
|
||||
export function deriveSandboxBackendsFromSource(source) {
|
||||
const marker = source.match(
|
||||
/pub const PUBLIC_SANDBOX_BACKENDS\s*:\s*&\[&str\]\s*=\s*&\[([\s\S]*?)\];/,
|
||||
);
|
||||
if (!marker) return [];
|
||||
return [...marker[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user