Define one or more named endpoints. Each can be referenced from
the Models tab to route specific agents through it. The runtime
loader treats open as the cloud (Claude) endpoint
implicitly; don't define one with that name.
{# Hidden input is the actual carrier for the multi-endpoint
submission. Alpine keeps :value reactively synced to the
endpoints array; HTMX serializes it like any normal form
field. Avoids the timing pitfalls of :name= bindings inside
. #}
+ Add endpoint
{# ---- Models tab — per-mode sub-tabs ------------------------------ #}
{# Sub-tabs (Open / Private / Hybrid) swap which mode's grid is
visible. Every grid is the same shape: a 3-column table with
Agent / Endpoint / Model. The Endpoint cell varies per mode
(read-only text vs UI-category select) but the visual rhythm
stays consistent across modes. #}
Open
Private
Hybrid
Each mode has its own default model + per-agent grid. Projects
inherit the matching mode's grid at runtime.
{# ---- Open mode grid ------------------------------------------ #}
{# In open mode every agent uses the cloud (Claude) endpoint, so
the Endpoint cell renders as the literal text "open" (read-
only). A hidden ``[endpoint]=open`` carries the value to the
server. The Model column is a cloud-models
. #}
{% set grid = mode_grids["open"] %}
Open mode
Default model for open mode
{% for m in known_cloud_models %}
{{ m }}
{% endfor %}
Agent
Endpoint
Model
{% for row in grid.rows %}
{{ row.agent }}
open
{% for m in known_cloud_models %}
{{ m }}
{% endfor %}
{% endfor %}
{# ---- Private mode grid --------------------------------------- #}
{# In private mode every agent uses a private endpoint, so the
Endpoint cell renders as the literal text "private" (read-
only). The Model column header is "Model (Endpoint)" to flag
the asymmetry: picking a row in this column actually selects
a named private endpoint AND its bundled default_model.
Hidden inputs carry both ``[endpoint]`` (the chosen endpoint
name) and ``[model]`` (its default_model). #}
{% set grid = mode_grids["private"] %}
Private mode
{% if private_endpoint_options %}
Default endpoint for private mode
{% for opt in private_endpoint_options %}
{{ opt.default_model }} ({{ opt.name }})
{% endfor %}
Picks the default model + endpoint for agents in private
mode that don't have their own per-agent override below.
Agent
Endpoint
Model (Endpoint)
{% for row in grid.rows %}
{# Per-row Alpine state holds the chosen endpoint name. The
visible has no name= so it's not submitted; two
hidden inputs carry [endpoint] and [model] derived from
the local ``ep`` value. #}
{% set initial_ep = row.endpoint if row.endpoint in private_endpoint_options|map(attribute='name')|list else private_endpoint_options[0].name %}
{{ row.agent }}
private
{% for opt in private_endpoint_options %}
{{ opt.default_model }} ({{ opt.name }})
{% endfor %}
{% endfor %}
{% else %}
Private mode is unavailable.
Private mode needs at least one named private endpoint with a default model.
Add one on the Privacy tab
first.
{% endif %}
{# ---- Hybrid mode grid ---------------------------------------- #}
{# Hybrid's Endpoint cell is a UI category dropdown (open /
private), NOT directly submitted. It drives the Model column,
which swaps:
cat=open → cloud-models
cat=private → named-private-endpoints
Hidden inputs carry the resolved [endpoint] and [model]:
cat=open → endpoint=open, model=
cat=private → endpoint=, model=
data_agent: category locked to "private" (only option, disabled).
tool_builder: defaults to "private" but user-switchable. #}
{% set grid = mode_grids["hybrid"] %}
Hybrid mode
{% if not private_endpoint_options %}
Hybrid mode is unavailable.
Hybrid mode mixes cloud and private endpoints — it needs at
least one named private endpoint with a default model.
Add one on the Privacy tab
first.
{% else %}
Default model for hybrid mode
{% for m in known_cloud_models %}
{{ m }}
{% endfor %}
Agent
Endpoint
Model (Endpoint)
{% for row in grid.rows %}
{% set is_locked = row.agent == "data_agent" %}
{% set defaults_private = row.agent in ["data_agent", "tool_builder"] %}
{# Initial UI category:
locked agent (data_agent) → "private"
default-private agent (tool_builder) with no override → "private"
explicit override on row pointing at a named endpoint → "private"
explicit "open" override → "open"
else → "open" #}
{% if is_locked %}
{% set initial_cat = "private" %}
{% elif row.endpoint == "open" %}
{% set initial_cat = "open" %}
{% elif row.endpoint in defined_endpoint_names %}
{% set initial_cat = "private" %}
{% elif defaults_private %}
{% set initial_cat = "private" %}
{% else %}
{% set initial_cat = "open" %}
{% endif %}
{# Initial chosen private endpoint (used when cat=private). #}
{% if row.endpoint in private_endpoint_options|map(attribute='name')|list %}
{% set initial_ep = row.endpoint %}
{% elif private_endpoint_options %}
{% set initial_ep = private_endpoint_options[0].name %}
{% else %}
{% set initial_ep = "" %}
{% endif %}
{# Initial chosen cloud model (used when cat=open). #}
{% set initial_cloud_model = row.model or grid.default_model or "claude-opus-4-6" %}
{{ row.agent }}
{# UI category select — NOT submitted directly (no name=).
data_agent: only "private" + disabled (locked).
tool_builder + others: both options. #}
{% if not is_locked %}
open
{% endif %}
private
{# Hidden input carries the resolved endpoint:
cat=open → "open"
cat=private → the chosen private endpoint name. #}
{% if is_locked %}
Locked: data_agent must use a private endpoint.
{% endif %}
{# Cloud variant — a free of known cloud models.
No name=; the hidden [model] input below carries the
resolved value. #}
{% for m in known_cloud_models %}
{{ m }}
{% endfor %}
{# Private variant — same shape as the Private mode grid:
of named private endpoints, label is
" ()". Drives ``ep``. #}
{% for opt in private_endpoint_options %}
{{ opt.default_model }} ({{ opt.name }})
{% endfor %}
{# Hidden input carries the resolved model:
cat=open → the chosen cloud model
cat=private → that endpoint's default_model. #}
{% endfor %}
{% endif %}
Defaults are written to [runtime.modes.<mode>]
and per-agent overrides to
[runtime.modes.<mode>.models.<agent>].
{# ---- Preferences tab --------------------------------------------- #}
{# ---- Notifications tab ------------------------------------------- #}
Connection details are shared across all projects. Tick
Auto-enable on new projects to opt every freshly
created project into a channel — existing projects are
unaffected and can still toggle the channel from their own
Notifications tab.
{# Email — connection details + auto-enable hint. #}
{# Slack — connection details + auto-enable hint. #}
{# Telegram — connection details + auto-enable hint. #}
{# Test-send affordance. Sends a synthetic notification through
every channel the form configures (un-saved values), reports
per-channel success / failure. Tests what's IN THE FORM, not
what's saved. ``$el.closest('form')`` walks DOM ancestors to
find the surrounding
{# ---- Secrets tab -------------------------------------------------- #}
{# Manage credentials Urika and its agents need (API keys, tokens,
passwords). The Secrets tab uses its own CRUD endpoints under
/api/secrets — it does NOT share the surrounding settings form's
PUT. Process-env-set secrets show up with the [process env]
badge and no Edit/Delete buttons (the user must `unset KEY` in
the shell that launched the dashboard). #}
Manage credentials Urika and your agents need (API keys, tokens,
passwords). Values are stored encrypted in your OS keyring when
available, or in a chmod-0600 file at
~/.urika/secrets.env otherwise. Process environment
variables always take precedence — exports in your shell are
never overwritten.
Storage backend: {{ secrets_backend_label }}
+ Add secret
{# Add / Edit modal-ish form. Inline rather than a true modal so
it stays inside the tab. Hidden until the user clicks + Add or
a row's Set / Edit / Override button. #}
{# Inline pseudo-form. Cannot use a nested
{% endcall %}