{% extends "_base.html" %} {% from "_macros.html" import tabs %} {% block title %}Settings · {{ project.name | humanize }} · Urika{% endblock %} {% block heading %}Settings{% endblock %} {% block breadcrumb %} {% endblock %} {% block content %}
{% call(active) tabs("project-settings", [ {"id": "basics", "label": "Basics"}, {"id": "data", "label": "Data"}, {"id": "privacy", "label": "Privacy"}, {"id": "models", "label": "Models"}, {"id": "notifications", "label": "Notifications"}, {"id": "secrets", "label": "Secrets"} ]) %} {# ---- Basics tab ---------------------------------------------------- #}
Project name is immutable.
{# ---- Data tab ----------------------------------------------------- #}
One path per line. Stored under [project].data_paths.
One key=value pair per line. Values stored as strings.
{# ---- Models tab --------------------------------------------------- #} {# Always-three-columns layout (Agent / Endpoint / Model) per mode, mirroring the global Models tab: open → Endpoint cell shows literal "open" (read-only); a hidden ``endpoint[]=open`` carries the value. Model column is a cloud-models of named private endpoints — picking one sets BOTH ``endpoint[]`` (the endpoint name) AND ``model[]`` (its default_model) via hidden inputs Alpine-bound to local state. hybrid → Endpoint cell is a UI category cat=private → named-private-endpoint {% for m in known_cloud_models %} {% endfor %} {% else %} {# Private mode → free-text (local server model names vary). #} {% endif %} Sets [runtime].model. Leave blank to inherit the global per-mode default. {% if project_privacy_mode == "open" %} {# Open mode: Endpoint cell = literal "open" + hidden input. Model cell = cloud-models {% endfor %} {% elif project_privacy_mode == "private" %} {# Private mode: Endpoint cell = literal "private". Model cell = {% for opt in private_endpoint_options %} {% endfor %} {% endfor %} {% else %}

No private endpoints with a default model are defined yet. Configure endpoints on the Privacy tab first — give each one a default model so it can be selected here.

{% endif %} {% else %} {# Hybrid mode: Endpoint cell = UI category {% if not is_locked %} {% endif %} {# Cloud variant: free dropdown of known cloud models. No name= — the hidden [model] input below carries it. #} {# Private variant: {% for opt in private_endpoint_options %} {% endfor %} {% endfor %} {% endif %} Per-agent overrides are written to [runtime.models.<agent>]. Placeholders show the global per-mode default for the project's current mode. {# ---- Privacy tab (editable, mode is required) -------------------- #}
Mode is required and is set at project creation (urika new). Switching modes here writes a fresh [privacy] block to urika.toml.
{# Open block #}

Open mode

{# Private block. Wrapped in its own Alpine scope so the Test button can read the *live* URL + key-env values (not whatever is saved in urika.toml). Inputs use x-model to keep the scope's ``ep`` mirror in sync with what the user types. #}

Private mode

{% if inherited_endpoint and inherited_endpoint.private %}

Inherits global endpoint: {{ inherited_endpoint.private.name }} ({{ inherited_endpoint.private.base_url }}). Leave the URL blank to use it; type a URL to override for this project.

{% endif %}
Enter the name of an environment variable (e.g. URIKA_API_KEY), not the key itself. Leave blank if the endpoint doesn't require auth.
{# Test endpoint button — Alpine state is shared with the inputs above. Uses .endpoint-test (centered, natural-width) instead of .form-row (which would stretch the button to full width). #}
API key :
{# Hybrid block. Same Alpine pattern as the Private block — scoped state mirrors the private URL + key-env inputs so the Test button probes whatever the user has typed. #}

Hybrid mode

{% if inherited_endpoint and inherited_endpoint.hybrid %}

Inherits global endpoint: {{ inherited_endpoint.hybrid.name }} ({{ inherited_endpoint.hybrid.base_url }}). Leave the private URL blank to use it; type a URL to override for this project.

{% endif %}
Enter the name of an environment variable (e.g. URIKA_API_KEY), not the key itself.
{# Test endpoint button — probes the private URL the user typed. Uses .endpoint-test wrapper (centered, natural width). #}
API key :
{# ---- Notifications tab (2-state: enabled / disabled) ------------- #}

Tick a channel to enable it for this project. New projects are pre-seeded from the global Notifications settings (each channel with Auto-enable on new projects turned on starts ticked here). Channel connection details — SMTP host, Slack channel, etc. — are shared globally.

{# Email #}

Email

{# Canonical key is ``to`` — the loader merges these into the global ``to`` list. Older TOML files used ``extra_to``; prefer the canonical key but fall back so existing files still populate the field. #} Project-specific addresses appended to the global to list.
{# Slack #}

Slack

{# Telegram #}

Telegram

{# Canonical key is ``chat_id`` (loader does cfg.update so the project key must match the channel-readable key). Legacy TOML files used ``override_chat_id``; fall back so they still populate the field. #}
{# ---- Secrets tab -------------------------------------------------- #} {# Per-project secrets: the list shows BOTH project-level and global secrets in one place, with origin badges per row. Project secrets live in /.urika/secrets.env and override globals for this project. #}

Project-tier secrets override global secrets for this project only. Saved values live in {{ project.name }}/.urika/secrets.env (chmod 0600). Process environment variables always take precedence — exports in your shell are never overwritten.

Same name your agent / tool reads via os.environ.get(NAME).
Loading secrets…
No secrets configured.
{# Three sections, same as the global Secrets tab. The project tab's row buttons differ (Override / Use global instead) so the per-section blocks can't be DRY'd against the global tab. #} {# ── LLM Providers ────────────────────────────────────────────── #}

LLM Providers

{# ── Used by your config ──────────────────────────────────────── #}

Used by your config

{# ── Other Integrations ───────────────────────────────────────── #}

Other Integrations

{% endcall %}
{# ---- Project Secrets tab Alpine component --------------------------- #} {# Mirrors secretsTab() in global_settings.html but routes to the per-project /api/projects//secrets endpoints. #} {# ---- Danger zone ----------------------------------------------------- Lives outside the settings
so the type-name confirm input doesn't get submitted when the user saves regular settings. The Move-to-trash button posts DELETE /api/projects/ directly via HTMX; on success the server emits HX-Redirect: /projects so the user lands back on the (now-shorter) project list. #}

Danger zone

Moving the project to trash unregisters it from Urika and moves the project folder to ~/.urika/trash/{{ project.name }}-<timestamp>/. Files are preserved; empty the trash folder manually when you're sure.

{% if active_lock_path %}

Stop the running process first — lock file present at {{ active_lock_path }}.

{% else %}
{% endif %}
{% endblock %}