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