Add /code product showcase; refresh siGit Code Cloud skills
- pages#code at /code: siGit Code showcase (on-device, ACP, cloud tiers, install, slash commands), matching the design system; Code link in the navbar - skills: document the decoupled login/model selection, the always-shown /models cloud tiers with sign-in gated at selection, slash account commands, and the onde-cloud SelfHosted/Upstream backend rename Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Seto Elkahfi committed
Jun 22, 2026 at 22:49 UTC
6f22e40ab4f800d1c7c5ec73fa05fa77029ca64d
6 files changed
+222
-38
.agents/skills/sigit-code-cloud/SKILL.md
+54
-37
index 28d2a7a..ec13fbe 100644
--- a/.agents/skills/sigit-code-cloud/SKILL.md
+++ b/.agents/skills/sigit-code-cloud/SKILL.md
@@ -46,42 +46,55 @@ The generic engine is also what enables the **BYO-endpoint escape hatch**: an
advanced user or enterprise can point siGit Code at their own OpenAI/Azure
endpoint. That is a power-user override, **not** the front door.
-## Backend selection (precedence)
+## Backend selection: identity and model choice are separate
-Resolved in `sigit/src/provider.rs::active_provider()`, first match wins:
+Being signed in does **not** change which model runs. Login is auth; the model
+is the user's explicit choice in `/models`. The startup default
+(`provider::active_provider()`) is local-first, with one exception:
-1. **Override (power user / BYO):** `OPENAI_BASE_URL` + `OPENAI_API_KEY` env vars,
- or the active profile in `~/.config/sigit/providers.toml`.
-2. **siGit Code Cloud (default product):** when logged in (`sigit login` stored a
- token). Baked-in endpoint, token auth, neutral tier. The user provides nothing
- but their login.
-3. **On-device:** not logged in, no override → runs locally via `onde::ChatEngine`.
+1. **Override (power user / BYO):** `OPENAI_BASE_URL` + `OPENAI_API_KEY`, or the
+ active profile in `~/.config/sigit/providers.toml`. This is the only thing that
+ changes the *startup* backend.
+2. **On-device by default:** no override means local, signed in or not.
-A missing piece in the override path is loud, not silent: `OPENAI_BASE_URL` set
-without `OPENAI_API_KEY` logs a warning and falls back to on-device, rather than
-silently looking like "the cloud didn't work."
+The user then picks a model in `/models` (see below), which swaps the running
+backend in place. `OPENAI_BASE_URL` set without `OPENAI_API_KEY` logs a warning
+and stays on-device, rather than silently looking like "the cloud didn't work."
-## Quality tiers (neutral by design)
+## `/models`: on-device models and cloud tiers together
-The user picks a tier, never a model id or provider. `SIGIT_TIER` selects it
-(default `balanced`); `provider.rs::tier_to_model` maps it:
+`/models` always lists the on-device models **and** the siGit Code Cloud tiers
+(`Fast`/`Balanced`/`Large`), so the cloud option is discoverable to everyone.
+Access is gated at **selection**, not visibility:
-| Tier | Wire model id (Onde Cloud) |
-|------|----------------------------|
-| `fast` | `onde-fast` |
-| `balanced` | `onde-balanced` |
-| `large` | `onde-large` |
+- **Pick a cloud tier while signed in** → the backend hot-swaps to that tier
+ immediately (no restart). `provider::cloud_tier_provider(tier)` builds the
+ client from the stored token.
+- **Pick a cloud tier while signed out** → a prompt: sign in with `/login`, or
+ create an account at sigit.si. The backend does not change.
+- **Pick an on-device model** → loads and routes locally, as before.
-The UI shows `siGit Code Cloud · Balanced`, never the model id or "Anthropic".
-`ProviderConfig.display_name` carries this label; the title bar uses it, and
-`InferenceBackend::is_remote()` ensures a persisted *local* model name can never
-override the cloud label.
+The cloud tiers are real picker entries backed by a synthetic
+`GgufModelConfig` whose id is `sigit-cloud:<tier>` (no `/`, so it never collides
+with a HuggingFace id and on-device matching code stays inert). `cloud_tier:
+Option<String>` on `ModelPickerItem` distinguishes them. The tier maps to a wire
+model id via `provider::tier_to_model` (`fast`→`onde-fast`, etc.); the UI shows
+`siGit Code Cloud · Balanced`, never the model id or "Anthropic".
+
+Cloud tiers are a TUI-picker feature today. The ACP session-config paths use
+`models::local_picker_items()` (cloud filtered out), because cloud inference over
+ACP still needs the trait rewire (see Open items).
## Account / auth (via sigit.si)
-`sigit login | logout | whoami` are plain CLI verbs dispatched in
-`sigit/src/main.rs` before the TUI/ACP split, implemented in
-`sigit/src/account.rs`.
+Account management is exposed as **slash commands** (`/login <email> <password>`,
+`/logout`, `/whoami`) so it works inside an ACP session (Zed) and the terminal
+TUI alike, since siGit Code has no separate place to run a CLI verb in those
+contexts. Both surfaces parse `/`-prefixed input and call the I/O-free core
+functions in `sigit/src/account.rs` (`authenticate`, `end_session`,
+`status_line`). Slash input is not echoed to the transcript, so the password
+does not persist there. `/login` only signs in; it does not change the active
+model. To use the cloud after signing in, pick a tier in `/models`.
- **Trust boundary is `sigit.si`.** It holds the confidential smbCloud
`app_secret` server-side and returns the client only a **bearer token**. The
@@ -89,11 +102,13 @@ override the cloud label.
`smbcloud-auth` skill's public-client rule).
- **Base URL:** `$SIGIT_API_URL`, else `https://sigit.si` (dev:
`http://localhost:8088`).
-- **Endpoints (sigit.si `/api/v1`, token-based):** `POST /users/sign_in` →
- `access_token`; `GET /me`; `DELETE /users/sign_out`. These reuse the planned
- `/api/v1` JSON surface in sigit-si (see the `smbcloud-auth` skill); confirm the
- exact routes/response shapes against that app and keep them in sync with the
- desktop `account/command_*` set.
+- **Endpoints (sigit.si `/api/v1`, token-based):** `POST /api/v1/auth/sign_in`
+ returns an `AccountStatus` (`"NotFound"` | `{"Ready":{"access_token":…}}` |
+ `{"Incomplete":{"status":<u32>}}`); `GET /api/v1/me` returns
+ `{id,email,created_at,updated_at}`; `DELETE /api/v1/auth/sign_out` returns 204.
+ Failures return `ErrorResponse {error_code, message}`. These shapes are a shared
+ contract with the desktop client; keep them in sync (see `smbcloud-auth`). The
+ controllers live at `app/controllers/api/v1/{sessions,me}_controller.rb`.
- **Credential store:** `sigit/src/credentials.rs` writes
`~/.config/sigit/credentials.toml` (`$SIGIT_CONFIG_DIR` override), mode `0600`,
holding `access_token` (+ email for display).
@@ -125,9 +140,11 @@ Repo: `onde-cloud` (Rust/axum, OpenAI-compatible).
cd ~/Repositories/onde-cloud && set -a && . ./.env && set +a && ./target/debug/onde-cloud
# → listens on $PORT (e.g. 8090); DEV_MODE=true accepts any bearer
-# 2a. Product path: point siGit Code Cloud at the local API and log in
-SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 \
- sigit login # stores a token; then `sigit` defaults to the cloud
+# 2a. Product path: point siGit Code Cloud at the local API, start sigit
+SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 sigit
+# then inside the session:
+# /login <email> <password> # sign in
+# /models # pick a siGit Code Cloud tier (hot-swaps)
# 2b. BYO/override path (no login needed): explicit endpoint
OPENAI_BASE_URL=http://localhost:8090/v1 OPENAI_API_KEY=dev:dev SIGIT_TIER=fast sigit
@@ -140,7 +157,7 @@ while testing to keep upstream spend low.
## Key files
-- `sigit/src/account.rs`: `login`/`logout`/`whoami`, sigit.si `/api/v1` client.
+- `sigit/src/account.rs`: account core (`authenticate`/`end_session`/`status_line`), sigit.si `/api/v1` client. Surfaced as `/login`, `/logout`, `/whoami`.
- `sigit/src/credentials.rs`: local token store.
- `sigit/src/provider.rs`: backend precedence, tiers, cloud default.
- `sigit/src/backend.rs`: `InferenceBackend` trait, `LocalBackend`, `OpenAiBackend`.
@@ -152,8 +169,8 @@ while testing to keep upstream spend low.
`app_id:app_secret`; the product needs it to accept the sigit.si-issued user
token (sigit.si fronts/mints inference auth, mirroring the `git_token` exchange
in `sigit-app`). Until then, dev relies on `DEV_MODE`.
-- **`sigit.si/api/v1` routes** are assumed from the desktop contract, confirm and
- keep in sync with the `smbcloud-auth` skill.
+- **`sigit.si/api/v1` auth routes exist** (`sessions`/`me` controllers). The
+ remaining gap is the inference-token link above, not the account endpoints.
- **Usage/billing:** `onde-cloud` returns zeroed `usage`; wire Anthropic's
returned token counts through for metering.
- **Tool-call id fingerprint:** Onde Cloud passes Anthropic's `toolu_…` tool-call
.agents/skills/sigit-code/SKILL.md
+2
-1
index 76327e9..0dc210a 100644
--- a/.agents/skills/sigit-code/SKILL.md
+++ b/.agents/skills/sigit-code/SKILL.md
@@ -24,7 +24,8 @@ runs over ACP in editors like Zed and as an interactive terminal TUI. The repo i
- `provider.rs`: backend selection and quality tiers. Precedence: an explicit
override (`OPENAI_BASE_URL` + `OPENAI_API_KEY`, or `~/.config/sigit/providers.toml`),
then siGit Code Cloud when logged in, then on-device.
-- `account.rs`: `login` / `logout` / `whoami` against the account API.
+- `account.rs`: account core against the account API, surfaced as the `/login`,
+ `/logout`, `/whoami` slash commands (both TUI and ACP).
- `credentials.rs`: the local session-token store.
- `tools.rs`: tool implementations and their JSON schemas.
- `models.rs`, `setup.rs`: local model picker and HuggingFace cache setup.
app/controllers/pages_controller.rb
+2
index 8d88739..8bb7f09 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -6,6 +6,8 @@ class PagesController < ApplicationController
end
end
+ def code; end
+
def about; end
def contact; end
app/views/pages/code.html.erb
+162
new file mode 100644
index 0000000..84a869d
--- /dev/null
+++ b/app/views/pages/code.html.erb
@@ -0,0 +1,162 @@
+<% content_for :title, "siGit Code" %>
+
+<div class="min-h-screen bg-surface-900">
+ <%# ── Hero ── %>
+ <section class="border-b border-surface-600 bg-surface-800">
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 py-20 sm:py-28">
+ <div class="max-w-3xl">
+ <span class="inline-flex items-center gap-2 rounded-sm border border-surface-500 bg-surface-700 px-3 py-1 text-xs font-medium text-gray-300">
+ <span class="h-1.5 w-1.5 rounded-full bg-brand-500"></span>
+ siGit Code
+ </span>
+
+ <h1 class="mt-6 max-w-2xl text-4xl font-semibold tracking-tight text-gray-100 sm:text-6xl">
+ A coding agent that runs on your machine.
+ </h1>
+
+ <p class="mt-6 max-w-xl text-base leading-7 text-gray-400 sm:text-lg">
+ siGit Code is a local AI coding agent. It works in your editor over the
+ Agent Client Protocol and runs on-device by default, so your code stays
+ on your machine. When a task needs a bigger model, sign in and switch to
+ siGit Code Cloud.
+ </p>
+
+ <div class="mt-10 flex flex-col gap-3 sm:flex-row">
+ <a href="https://getsigit.5mb.app/" class="btn-primary px-6 py-3 text-sm justify-center" target="_blank" rel="noopener noreferrer">Get siGit Code</a>
+ <a href="#install" class="btn-secondary px-6 py-3 text-sm justify-center">Install from your terminal</a>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <%# ── What it is ── %>
+ <section class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
+ <div class="grid grid-cols-1 gap-px overflow-hidden rounded-sm border border-surface-500 bg-surface-500 sm:grid-cols-3">
+ <div class="bg-surface-800 p-8 sm:p-10">
+ <div class="mb-6 flex h-10 w-10 items-center justify-center rounded-sm border border-surface-500 bg-surface-700 text-brand-500">
+ <span class="text-lg font-semibold">01</span>
+ </div>
+ <h2 class="text-lg font-semibold text-gray-100">On-device by default</h2>
+ <p class="mt-3 text-sm leading-6 text-gray-400">
+ Inference runs locally, on Apple silicon and other hardware. Your code
+ never leaves your machine unless you pick a cloud tier, and it costs
+ nothing to run.
+ </p>
+ </div>
+
+ <div class="bg-surface-800 p-8 sm:p-10">
+ <div class="mb-6 flex h-10 w-10 items-center justify-center rounded-sm border border-surface-500 bg-surface-700 text-brand-500">
+ <span class="text-lg font-semibold">02</span>
+ </div>
+ <h2 class="text-lg font-semibold text-gray-100">Lives in your editor</h2>
+ <p class="mt-3 text-sm leading-6 text-gray-400">
+ siGit Code speaks the Agent Client Protocol, so it runs in Zed and other
+ ACP editors, or as a terminal chat. It is the same agent either way.
+ </p>
+ </div>
+
+ <div class="bg-surface-800 p-8 sm:p-10">
+ <div class="mb-6 flex h-10 w-10 items-center justify-center rounded-sm border border-surface-500 bg-surface-700 text-brand-500">
+ <span class="text-lg font-semibold">03</span>
+ </div>
+ <h2 class="text-lg font-semibold text-gray-100">Tools and git, built in</h2>
+ <p class="mt-3 text-sm leading-6 text-gray-400">
+ It reads files, runs commands, searches the codebase, and handles git
+ itself, instead of telling you what to type. That keeps you in the work.
+ </p>
+ </div>
+ </div>
+ </section>
+
+ <%# ── siGit Code Cloud ── %>
+ <section class="border-y border-surface-600 bg-surface-800">
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
+ <div class="max-w-2xl">
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">siGit Code Cloud</h2>
+ <p class="mt-4 text-sm leading-7 text-gray-400 sm:text-base">
+ Use the local model for everyday work, and switch to a hosted tier when a
+ task calls for a larger model. You pick a tier in <code class="rounded-sm bg-surface-700 px-1.5 py-0.5 text-gray-300">/models</code>,
+ and your account decides which ones you can use. You never paste an API key.
+ </p>
+ </div>
+
+ <div class="mt-10 grid grid-cols-1 gap-4 sm:grid-cols-3">
+ <div class="card p-6">
+ <h3 class="text-base font-semibold text-gray-100">Fast</h3>
+ <p class="mt-2 text-sm leading-6 text-gray-400">Quick edits and snappy back-and-forth.</p>
+ </div>
+ <div class="card p-6">
+ <h3 class="text-base font-semibold text-gray-100">Balanced</h3>
+ <p class="mt-2 text-sm leading-6 text-gray-400">The everyday tier for most agent work.</p>
+ </div>
+ <div class="card p-6">
+ <h3 class="text-base font-semibold text-gray-100">Large</h3>
+ <p class="mt-2 text-sm leading-6 text-gray-400">The heaviest tier, for hard multi-step problems.</p>
+ </div>
+ </div>
+
+ <div class="mt-8 flex flex-col gap-3 sm:flex-row">
+ <% if signed_in? %>
+ <%= link_to "Manage your account", settings_path, class: "btn-secondary px-6 py-3 text-sm justify-center" %>
+ <% else %>
+ <%= link_to "Create an account", signup_path, class: "btn-primary px-6 py-3 text-sm justify-center" %>
+ <%= link_to "Sign in", signin_path, class: "btn-secondary px-6 py-3 text-sm justify-center" %>
+ <% end %>
+ </div>
+ </div>
+ </section>
+
+ <%# ── Install ── %>
+ <section id="install" class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
+ <div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
+ <div>
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">Install</h2>
+ <p class="mt-4 text-sm leading-7 text-gray-400">
+ Grab it however you ship Rust, Node, or Python tools, then run
+ <code class="rounded-sm bg-surface-700 px-1.5 py-0.5 text-gray-300">sigit</code>
+ in a project.
+ </p>
+ <pre class="mt-6 overflow-x-auto rounded-sm border border-surface-500 bg-surface-800 p-4 font-mono text-sm leading-7 text-gray-300"><span class="text-gray-500"># Rust</span>
+cargo install sigit
+
+<span class="text-gray-500"># Node</span>
+npm install -g @smbcloud/sigit
+
+<span class="text-gray-500"># Python</span>
+pip install sigit-code
+
+<span class="text-gray-500"># then, in any repository</span>
+sigit</pre>
+ </div>
+
+ <div>
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">Commands</h2>
+ <p class="mt-4 text-sm leading-7 text-gray-400">
+ Everything is a slash command, in the editor or the terminal.
+ </p>
+ <dl class="mt-6 divide-y divide-surface-600 overflow-hidden rounded-sm border border-surface-500 bg-surface-800 text-sm">
+ <div class="flex gap-4 px-4 py-3">
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/models</dt>
+ <dd class="text-gray-400">List models and tiers, and switch between them.</dd>
+ </div>
+ <div class="flex gap-4 px-4 py-3">
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/login</dt>
+ <dd class="text-gray-400">Sign in to unlock siGit Code Cloud.</dd>
+ </div>
+ <div class="flex gap-4 px-4 py-3">
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/whoami</dt>
+ <dd class="text-gray-400">Show the signed-in account.</dd>
+ </div>
+ <div class="flex gap-4 px-4 py-3">
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/logout</dt>
+ <dd class="text-gray-400">Sign out on this machine.</dd>
+ </div>
+ <div class="flex gap-4 px-4 py-3">
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/help</dt>
+ <dd class="text-gray-400">List every command.</dd>
+ </div>
+ </dl>
+ </div>
+ </div>
+ </section>
+</div>
app/views/shared/_navbar.html.erb
+1
index f419f4c..675cd1f 100644
--- a/app/views/shared/_navbar.html.erb
+++ b/app/views/shared/_navbar.html.erb
@@ -8,6 +8,7 @@
<% end %>
<div class="hidden sm:flex items-center gap-1">
+ <%= link_to "Code", code_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
<%= link_to "Models", models_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
<%= link_to "Repos", repos_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
</div>
config/routes.rb
+1
index 0f388c0..3ffffe0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,6 +3,7 @@ Rails.application.routes.draw do
# Static pages — declared before the "/:username" matcher so these literal
# slugs aren't read as profiles.
+ get "/code", to: "pages#code", as: :code
get "/about", to: "pages#about", as: :about
get "/contact", to: "pages#contact", as: :contact
get "/privacy", to: "pages#privacy", as: :privacy