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
@@ -46,42 +46,55 @@ The generic engine is also what enables the **BYO-endpoint escape hatch**: an
46
advanced user or enterprise can point siGit Code at their own OpenAI/Azure
47
endpoint. That is a power-user override, **not** the front door.
48
49
-## Backend selection (precedence)
49
+## Backend selection: identity and model choice are separate
50
51
-Resolved in `sigit/src/provider.rs::active_provider()`, first match wins:
51
+Being signed in does **not** change which model runs. Login is auth; the model
52
+is the user's explicit choice in `/models`. The startup default
53
+(`provider::active_provider()`) is local-first, with one exception:
54
53
-1. **Override (power user / BYO):** `OPENAI_BASE_URL` + `OPENAI_API_KEY` env vars,
54
- or the active profile in `~/.config/sigit/providers.toml`.
55
-2. **siGit Code Cloud (default product):** when logged in (`sigit login` stored a
56
- token). Baked-in endpoint, token auth, neutral tier. The user provides nothing
57
- but their login.
58
-3. **On-device:** not logged in, no override → runs locally via `onde::ChatEngine`.
55
+1. **Override (power user / BYO):** `OPENAI_BASE_URL` + `OPENAI_API_KEY`, or the
56
+ active profile in `~/.config/sigit/providers.toml`. This is the only thing that
57
+ changes the *startup* backend.
58
+2. **On-device by default:** no override means local, signed in or not.
59
60
-A missing piece in the override path is loud, not silent: `OPENAI_BASE_URL` set
61
-without `OPENAI_API_KEY` logs a warning and falls back to on-device, rather than
62
-silently looking like "the cloud didn't work."
60
+The user then picks a model in `/models` (see below), which swaps the running
61
+backend in place. `OPENAI_BASE_URL` set without `OPENAI_API_KEY` logs a warning
62
+and stays on-device, rather than silently looking like "the cloud didn't work."
63
64
-## Quality tiers (neutral by design)
64
+## `/models`: on-device models and cloud tiers together
65
66
-The user picks a tier, never a model id or provider. `SIGIT_TIER` selects it
67
-(default `balanced`); `provider.rs::tier_to_model` maps it:
66
+`/models` always lists the on-device models **and** the siGit Code Cloud tiers
67
+(`Fast`/`Balanced`/`Large`), so the cloud option is discoverable to everyone.
68
+Access is gated at **selection**, not visibility:
69
69
-| Tier | Wire model id (Onde Cloud) |
70
-|------|----------------------------|
71
-| `fast` | `onde-fast` |
72
-| `balanced` | `onde-balanced` |
73
-| `large` | `onde-large` |
70
+- **Pick a cloud tier while signed in** → the backend hot-swaps to that tier
71
+ immediately (no restart). `provider::cloud_tier_provider(tier)` builds the
72
+ client from the stored token.
73
+- **Pick a cloud tier while signed out** → a prompt: sign in with `/login`, or
74
+ create an account at sigit.si. The backend does not change.
75
+- **Pick an on-device model** → loads and routes locally, as before.
76
75
-The UI shows `siGit Code Cloud · Balanced`, never the model id or "Anthropic".
76
-`ProviderConfig.display_name` carries this label; the title bar uses it, and
77
-`InferenceBackend::is_remote()` ensures a persisted *local* model name can never
78
-override the cloud label.
77
+The cloud tiers are real picker entries backed by a synthetic
78
+`GgufModelConfig` whose id is `sigit-cloud:<tier>` (no `/`, so it never collides
79
+with a HuggingFace id and on-device matching code stays inert). `cloud_tier:
80
+Option<String>` on `ModelPickerItem` distinguishes them. The tier maps to a wire
81
+model id via `provider::tier_to_model` (`fast`→`onde-fast`, etc.); the UI shows
82
+`siGit Code Cloud · Balanced`, never the model id or "Anthropic".
83
+
84
+Cloud tiers are a TUI-picker feature today. The ACP session-config paths use
85
+`models::local_picker_items()` (cloud filtered out), because cloud inference over
86
+ACP still needs the trait rewire (see Open items).
87
88
## Account / auth (via sigit.si)
89
82
-`sigit login | logout | whoami` are plain CLI verbs dispatched in
83
-`sigit/src/main.rs` before the TUI/ACP split, implemented in
84
-`sigit/src/account.rs`.
90
+Account management is exposed as **slash commands** (`/login <email> <password>`,
91
+`/logout`, `/whoami`) so it works inside an ACP session (Zed) and the terminal
92
+TUI alike, since siGit Code has no separate place to run a CLI verb in those
93
+contexts. Both surfaces parse `/`-prefixed input and call the I/O-free core
94
+functions in `sigit/src/account.rs` (`authenticate`, `end_session`,
95
+`status_line`). Slash input is not echoed to the transcript, so the password
96
+does not persist there. `/login` only signs in; it does not change the active
97
+model. To use the cloud after signing in, pick a tier in `/models`.
98
99
- **Trust boundary is `sigit.si`.** It holds the confidential smbCloud
100
`app_secret` server-side and returns the client only a **bearer token**. The
@@ -89,11 +102,13 @@ override the cloud label.
102
`smbcloud-auth` skill's public-client rule).
103
- **Base URL:** `$SIGIT_API_URL`, else `https://sigit.si` (dev:
104
`http://localhost:8088`).
92
-- **Endpoints (sigit.si `/api/v1`, token-based):** `POST /users/sign_in` →
93
- `access_token`; `GET /me`; `DELETE /users/sign_out`. These reuse the planned
94
- `/api/v1` JSON surface in sigit-si (see the `smbcloud-auth` skill); confirm the
95
- exact routes/response shapes against that app and keep them in sync with the
96
- desktop `account/command_*` set.
105
+- **Endpoints (sigit.si `/api/v1`, token-based):** `POST /api/v1/auth/sign_in`
106
+ returns an `AccountStatus` (`"NotFound"` | `{"Ready":{"access_token":…}}` |
107
+ `{"Incomplete":{"status":<u32>}}`); `GET /api/v1/me` returns
108
+ `{id,email,created_at,updated_at}`; `DELETE /api/v1/auth/sign_out` returns 204.
109
+ Failures return `ErrorResponse {error_code, message}`. These shapes are a shared
110
+ contract with the desktop client; keep them in sync (see `smbcloud-auth`). The
111
+ controllers live at `app/controllers/api/v1/{sessions,me}_controller.rb`.
112
- **Credential store:** `sigit/src/credentials.rs` writes
113
`~/.config/sigit/credentials.toml` (`$SIGIT_CONFIG_DIR` override), mode `0600`,
114
holding `access_token` (+ email for display).
@@ -125,9 +140,11 @@ Repo: `onde-cloud` (Rust/axum, OpenAI-compatible).
140
cd ~/Repositories/onde-cloud && set -a && . ./.env && set +a && ./target/debug/onde-cloud
141
# → listens on $PORT (e.g. 8090); DEV_MODE=true accepts any bearer
142
128
-# 2a. Product path: point siGit Code Cloud at the local API and log in
129
-SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 \
130
- sigit login # stores a token; then `sigit` defaults to the cloud
143
+# 2a. Product path: point siGit Code Cloud at the local API, start sigit
144
+SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 sigit
145
+# then inside the session:
146
+# /login <email> <password> # sign in
147
+# /models # pick a siGit Code Cloud tier (hot-swaps)
148
149
# 2b. BYO/override path (no login needed): explicit endpoint
150
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.
157
158
## Key files
159
143
-- `sigit/src/account.rs`: `login`/`logout`/`whoami`, sigit.si `/api/v1` client.
160
+- `sigit/src/account.rs`: account core (`authenticate`/`end_session`/`status_line`), sigit.si `/api/v1` client. Surfaced as `/login`, `/logout`, `/whoami`.
161
- `sigit/src/credentials.rs`: local token store.
162
- `sigit/src/provider.rs`: backend precedence, tiers, cloud default.
163
- `sigit/src/backend.rs`: `InferenceBackend` trait, `LocalBackend`, `OpenAiBackend`.
@@ -152,8 +169,8 @@ while testing to keep upstream spend low.
169
`app_id:app_secret`; the product needs it to accept the sigit.si-issued user
170
token (sigit.si fronts/mints inference auth, mirroring the `git_token` exchange
171
in `sigit-app`). Until then, dev relies on `DEV_MODE`.
155
-- **`sigit.si/api/v1` routes** are assumed from the desktop contract, confirm and
156
- keep in sync with the `smbcloud-auth` skill.
172
+- **`sigit.si/api/v1` auth routes exist** (`sessions`/`me` controllers). The
173
+ remaining gap is the inference-token link above, not the account endpoints.
174
- **Usage/billing:** `onde-cloud` returns zeroed `usage`; wire Anthropic's
175
returned token counts through for metering.
176
- **Tool-call id fingerprint:** Onde Cloud passes Anthropic's `toolu_…` tool-call
.agents/skills/sigit-code/SKILL.md
+2
-1
@@ -24,7 +24,8 @@ runs over ACP in editors like Zed and as an interactive terminal TUI. The repo i
24
- `provider.rs`: backend selection and quality tiers. Precedence: an explicit
25
override (`OPENAI_BASE_URL` + `OPENAI_API_KEY`, or `~/.config/sigit/providers.toml`),
26
then siGit Code Cloud when logged in, then on-device.
27
-- `account.rs`: `login` / `logout` / `whoami` against the account API.
27
+- `account.rs`: account core against the account API, surfaced as the `/login`,
28
+ `/logout`, `/whoami` slash commands (both TUI and ACP).
29
- `credentials.rs`: the local session-token store.
30
- `tools.rs`: tool implementations and their JSON schemas.
31
- `models.rs`, `setup.rs`: local model picker and HuggingFace cache setup.
app/controllers/pages_controller.rb
+2
@@ -6,6 +6,8 @@ class PagesController < ApplicationController
6
end
7
end
8
9
+ def code; end
10
+
11
def about; end
12
13
def contact; end
app/views/pages/code.html.erb
new
+162
@@ -0,0 +1,162 @@
1
+<% content_for :title, "siGit Code" %>
2
+
3
+<div class="min-h-screen bg-surface-900">
4
+ <%# ── Hero ── %>
5
+ <section class="border-b border-surface-600 bg-surface-800">
6
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 py-20 sm:py-28">
7
+ <div class="max-w-3xl">
8
+ <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">
9
+ <span class="h-1.5 w-1.5 rounded-full bg-brand-500"></span>
10
+ siGit Code
11
+ </span>
12
+
13
+ <h1 class="mt-6 max-w-2xl text-4xl font-semibold tracking-tight text-gray-100 sm:text-6xl">
14
+ A coding agent that runs on your machine.
15
+ </h1>
16
+
17
+ <p class="mt-6 max-w-xl text-base leading-7 text-gray-400 sm:text-lg">
18
+ siGit Code is a local AI coding agent. It works in your editor over the
19
+ Agent Client Protocol and runs on-device by default, so your code stays
20
+ on your machine. When a task needs a bigger model, sign in and switch to
21
+ siGit Code Cloud.
22
+ </p>
23
+
24
+ <div class="mt-10 flex flex-col gap-3 sm:flex-row">
25
+ <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>
26
+ <a href="#install" class="btn-secondary px-6 py-3 text-sm justify-center">Install from your terminal</a>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </section>
31
+
32
+ <%# ── What it is ── %>
33
+ <section class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
34
+ <div class="grid grid-cols-1 gap-px overflow-hidden rounded-sm border border-surface-500 bg-surface-500 sm:grid-cols-3">
35
+ <div class="bg-surface-800 p-8 sm:p-10">
36
+ <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">
37
+ <span class="text-lg font-semibold">01</span>
38
+ </div>
39
+ <h2 class="text-lg font-semibold text-gray-100">On-device by default</h2>
40
+ <p class="mt-3 text-sm leading-6 text-gray-400">
41
+ Inference runs locally, on Apple silicon and other hardware. Your code
42
+ never leaves your machine unless you pick a cloud tier, and it costs
43
+ nothing to run.
44
+ </p>
45
+ </div>
46
+
47
+ <div class="bg-surface-800 p-8 sm:p-10">
48
+ <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">
49
+ <span class="text-lg font-semibold">02</span>
50
+ </div>
51
+ <h2 class="text-lg font-semibold text-gray-100">Lives in your editor</h2>
52
+ <p class="mt-3 text-sm leading-6 text-gray-400">
53
+ siGit Code speaks the Agent Client Protocol, so it runs in Zed and other
54
+ ACP editors, or as a terminal chat. It is the same agent either way.
55
+ </p>
56
+ </div>
57
+
58
+ <div class="bg-surface-800 p-8 sm:p-10">
59
+ <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">
60
+ <span class="text-lg font-semibold">03</span>
61
+ </div>
62
+ <h2 class="text-lg font-semibold text-gray-100">Tools and git, built in</h2>
63
+ <p class="mt-3 text-sm leading-6 text-gray-400">
64
+ It reads files, runs commands, searches the codebase, and handles git
65
+ itself, instead of telling you what to type. That keeps you in the work.
66
+ </p>
67
+ </div>
68
+ </div>
69
+ </section>
70
+
71
+ <%# ── siGit Code Cloud ── %>
72
+ <section class="border-y border-surface-600 bg-surface-800">
73
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
74
+ <div class="max-w-2xl">
75
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">siGit Code Cloud</h2>
76
+ <p class="mt-4 text-sm leading-7 text-gray-400 sm:text-base">
77
+ Use the local model for everyday work, and switch to a hosted tier when a
78
+ 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>,
79
+ and your account decides which ones you can use. You never paste an API key.
80
+ </p>
81
+ </div>
82
+
83
+ <div class="mt-10 grid grid-cols-1 gap-4 sm:grid-cols-3">
84
+ <div class="card p-6">
85
+ <h3 class="text-base font-semibold text-gray-100">Fast</h3>
86
+ <p class="mt-2 text-sm leading-6 text-gray-400">Quick edits and snappy back-and-forth.</p>
87
+ </div>
88
+ <div class="card p-6">
89
+ <h3 class="text-base font-semibold text-gray-100">Balanced</h3>
90
+ <p class="mt-2 text-sm leading-6 text-gray-400">The everyday tier for most agent work.</p>
91
+ </div>
92
+ <div class="card p-6">
93
+ <h3 class="text-base font-semibold text-gray-100">Large</h3>
94
+ <p class="mt-2 text-sm leading-6 text-gray-400">The heaviest tier, for hard multi-step problems.</p>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="mt-8 flex flex-col gap-3 sm:flex-row">
99
+ <% if signed_in? %>
100
+ <%= link_to "Manage your account", settings_path, class: "btn-secondary px-6 py-3 text-sm justify-center" %>
101
+ <% else %>
102
+ <%= link_to "Create an account", signup_path, class: "btn-primary px-6 py-3 text-sm justify-center" %>
103
+ <%= link_to "Sign in", signin_path, class: "btn-secondary px-6 py-3 text-sm justify-center" %>
104
+ <% end %>
105
+ </div>
106
+ </div>
107
+ </section>
108
+
109
+ <%# ── Install ── %>
110
+ <section id="install" class="max-w-6xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
111
+ <div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
112
+ <div>
113
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">Install</h2>
114
+ <p class="mt-4 text-sm leading-7 text-gray-400">
115
+ Grab it however you ship Rust, Node, or Python tools, then run
116
+ <code class="rounded-sm bg-surface-700 px-1.5 py-0.5 text-gray-300">sigit</code>
117
+ in a project.
118
+ </p>
119
+ <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>
120
+cargo install sigit
121
+
122
+<span class="text-gray-500"># Node</span>
123
+npm install -g @smbcloud/sigit
124
+
125
+<span class="text-gray-500"># Python</span>
126
+pip install sigit-code
127
+
128
+<span class="text-gray-500"># then, in any repository</span>
129
+sigit</pre>
130
+ </div>
131
+
132
+ <div>
133
+ <h2 class="text-2xl font-semibold tracking-tight text-gray-100 sm:text-3xl">Commands</h2>
134
+ <p class="mt-4 text-sm leading-7 text-gray-400">
135
+ Everything is a slash command, in the editor or the terminal.
136
+ </p>
137
+ <dl class="mt-6 divide-y divide-surface-600 overflow-hidden rounded-sm border border-surface-500 bg-surface-800 text-sm">
138
+ <div class="flex gap-4 px-4 py-3">
139
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/models</dt>
140
+ <dd class="text-gray-400">List models and tiers, and switch between them.</dd>
141
+ </div>
142
+ <div class="flex gap-4 px-4 py-3">
143
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/login</dt>
144
+ <dd class="text-gray-400">Sign in to unlock siGit Code Cloud.</dd>
145
+ </div>
146
+ <div class="flex gap-4 px-4 py-3">
147
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/whoami</dt>
148
+ <dd class="text-gray-400">Show the signed-in account.</dd>
149
+ </div>
150
+ <div class="flex gap-4 px-4 py-3">
151
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/logout</dt>
152
+ <dd class="text-gray-400">Sign out on this machine.</dd>
153
+ </div>
154
+ <div class="flex gap-4 px-4 py-3">
155
+ <dt class="w-40 shrink-0 font-mono text-brand-500">/help</dt>
156
+ <dd class="text-gray-400">List every command.</dd>
157
+ </div>
158
+ </dl>
159
+ </div>
160
+ </div>
161
+ </section>
162
+</div>
app/views/shared/_navbar.html.erb
+1
@@ -8,6 +8,7 @@
8
<% end %>
9
10
<div class="hidden sm:flex items-center gap-1">
11
+ <%= link_to "Code", code_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
12
<%= link_to "Models", models_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
13
<%= link_to "Repos", repos_path, class: "nav-link px-2 py-1 rounded hover:bg-surface-600" %>
14
</div>
config/routes.rb
+1
@@ -3,6 +3,7 @@ Rails.application.routes.draw do
3
4
# Static pages — declared before the "/:username" matcher so these literal
5
# slugs aren't read as profiles.
6
+ get "/code", to: "pages#code", as: :code
7
get "/about", to: "pages#about", as: :about
8
get "/contact", to: "pages#contact", as: :contact
9
get "/privacy", to: "pages#privacy", as: :privacy