Web UI & REST API
The Flask app serves the console pages and a JSON/SSE REST API. Page routes live in routes_pages.py; the query stream in routes_query.py; workspaces/usage in routes_workspaces.py; the file explorer in routes_files.py; guest tokens in guests.py (+ management routes in routes_status.py).
Pages
| Path | Handler | Notes |
|---|---|---|
/ |
index() (routes_pages.py:57) |
Renders templates/index.html. Injects cache-bust versions = file mtimes for css/js (routes_pages.py:35-54). API_TOKEN is injected into the page only when the request already presented a valid token; otherwise the page renders a token input and JS keeps it in localStorage (routes_pages.py:75-88). remote_mode (env KUCATOO_REMOTE=1) hides the :5001 supervisor controls. |
/help |
help_page() (routes_pages.py:95) |
Static help: Direct/Agent, Tools, Memory, etc. |
/vision |
vision_page() (routes_pages.py:103) |
Qwen text-to-image generation/editing. |
/video |
video_page() (routes_pages.py:111) |
MiniMax-H3 text/image-to-video. |
/wiki, /wiki/<slug> |
wiki_index() / wiki_page() (routes_pages.py:163-182) |
Renders docs/wiki/*.md server-side via markdown-it (commonmark + tables, raw HTML disabled); slugs must be alnum/-/_; the page list is built from each file's first # heading with INDEX sorted first. ```mermaid fences render client-side. |
POST /api/theme |
api_theme() (routes_pages.py:186) |
Persist dark/light in the Flask session. |
Main page layout (templates/index.html)
Header: theme/font-size controls, Direct vs Agent toggle, agent-mode selector (explore / plan / write_test / write_no_test; index.html:49-64), links to Vision/Video/Help/Wiki, temperature slider, API-token input (only when API_TOKEN is set), server Start/Stop/Restart (via the supervisor on :5001, hidden when remote), role + guest-quota badges.
Workspace bar (index.html:139-204): workspace dropdown + tree picker, New/Subdir buttons, collapsible stats panel, and the voice header control (status dot, connect button, STS model/voice/verbosity selects, timer, cost readout).
Main area is a 4-column grid (<main class="app-main">, index.html:209):
- Model column (
app-col-models, index.html:212) — model list card, description, per-model prompt options. - Prompt column (
app-col-prompt, index.html:280) — prompt textarea, drop zone, voice wake chip + cheat-sheet popover, dictation panel. - Response column (
app-col-response, index.html:472) — streamed output, run stats, approval/question cards. - Console column (
app-col-console, index.html:576) — tabbed capability console (below).
Hidden auxiliary columns: voice-commands column (app-col-voice, index.html:262), tool/skill runner columns (toolRunCol/skillRunCol, index.html:984,995), and the trace-analysis aside (traceAnalysisPanel, index.html:1065).
Console tabs
data-tab buttons at index.html:579-588: Tools (enable for agent runs / run directly), Sites (registered sites, ports, dev→beta→prod staging), Memory (STM/MTM/LTM recall + remember), Skills (browse/execute), Files (workspace explorer), Usage (per-project costs, per-query drill-down), Audio (provider TTS/STT/STS settings, personas, cloning, spend), Status (harness health/cost + guest-token management), History (completed runs).
Query API + SSE event stream
POST /api/query (routes_query.py:212) — body fields: prompt (required), model, use_agent, agent_type (react/planner/reflective), tools, temperature, session_id, mode (explore/plan/write_test/write_no_test; invalid → explore; forced to explore when the active workspace is the app's own source tree, routes_query.py:230), auto_confirm, images (≤4 data-URLs, ≤10 MB total), context_files (≤20 workspace-relative text files, 200 KB total, 20 KB kept per file, routes_query.py:97-101), reasoning_effort, thinking, memory_recall, max_steps (default 60), max_time_sec (0 = unlimited).
Response: text/event-stream with SSE_HEADERS (Cache-Control: no-cache, X-Accel-Buffering: no, Connection: keep-alive, streaming.py:109). Wire format: data: {"event": <name>, "data": <payload>}\n\n (streaming.py:38-46).
SSE event types
| Event | Emitted when | Payload |
|---|---|---|
content |
Text delta (direct mode: token chunks; agent mode: output chunks) | string |
reasoning |
Thinking-model reasoning delta, real-time via queue pump (routes_query.py:725) | string |
memory_proposal |
A "remember …" intent short-circuits the model call (routes_query.py:605-616) | {fact} |
thought |
Agent step thought; only new suffixes re-emitted (routes_query.py:1046-1055) | string |
tool_call |
Agent ACTING transition, deduped per step+action (routes_query.py:1058-1065) | {action, input} |
step |
Agent step on status transitions only | AgentStep dict |
observation |
Tool result on the OBSERVING transition | string |
permission_request |
Confirmation-gated tool with auto_confirm off (routes_query.py:951) |
{request_id, action, detail} |
question |
Agent ask_user (routes_query.py:970) |
{request_id, text, choices, multi} |
cap_warning |
Time/step/token budget threshold crossed (routes_query.py:1081) | {kind, pct, elapsed, budget, message} |
error |
Stream failure | string |
done |
Terminal. Direct: {model_used, cost_usd, tokens{prompt,completion,total,cached}, elapsed, estimated} (routes_query.py:865). Agent adds {agent_type, tokens.context_prompt, plan_file, stop_reason, resumable, max_time_sec} (routes_query.py:1247) |
object |
Related endpoints: POST /api/permissions/respond ({request_id, approved} or {request_id, answer}, routes_query.py:356); POST /api/agent/auto-approve flips auto_confirm_tools on a live run (404 when none, routes_query.py:380); POST /api/agent/instruct queues a mid-run instruction (routes_query.py:402); POST /api/agent/continue resumes a capped run from its persisted message history as a fresh SSE stream (routes_query.py:426). Plan-mode runs persist output to Plan-N.md in the workspace root (routes_query.py:71-89).
Workspaces + usage stats
A workspace is any subdirectory of the workspaces root (WORKSPACE_DIR env, else <parent of data dir>/workspace, routes_workspaces.py:57). Activation calls set_workspace_root() and persists to <DATA_DIR>/active_workspace.json (routes_workspaces.py:405).
Every finished query (direct, agent, or audio) appends one JSONL record to <DATA_DIR>/workspace_stats/<slug>.jsonl via record_workspace_stats() (routes_workspaces.py:112). Keys: workspace rel path, or project:<name> for a registered projects.json root (_usage_rel_for, routes_workspaces.py:83).
GET /api/workspaces— list with per-workspace stat summaries + active marker (routes_workspaces.py:297).GET /api/workspaces/tree?depth=1..5— picker tree; skips hidden/noise dirs (.git,node_modules, …), 500-node cap (routes_workspaces.py:333).POST /api/workspaces/create/POST /api/workspaces/activate— create under root / activate (routes_workspaces.py:384, 405).GET /api/workspaces/stats?workspace=— aggregate totals: queries, tokens, cost, elapsed, per-model breakdown, skilldag injections, audio bucket, first/last activity (_aggregate_stats, routes_workspaces.py:147).GET /api/workspaces/usage?workspace=&limit=&offset=— aggregate +by_providerbreakdown + newest-first page of raw records (≤500 per page) (routes_workspaces.py:445).GET /api/workspaces/usage/export?workspace=— writes the full log as TSV to<project>/docs/costs/usage-<slug>-<timestamp>.tsv; columns_EXPORT_COLUMNS(routes_workspaces.py:476): ts, workspace, mode, kind, provider, model, temperature, thinking, reasoning_effort, prompt/completion/cached/total tokens, cost_usd, elapsed_s, skilldag_chars, audio_chars/seconds, prompt, response (tabs/newlines sanitized, routes_workspaces.py:485).GET /api/projects,POST /api/projects/activate— registered Kucatoo-Sites projects only (never arbitrary paths); the app's own tree is flaggedselfand locks agent mode to explore (routes_workspaces.py:529-598).
Files API (routes_files.py)
All paths are resolved inside the active workspace via resolve_path() — traversal is rejected (routes_files.py:43). .git, venv, .venv, __pycache__, node_modules, .kucatoo_backups are excluded from listings/zips/backups (routes_files.py:34).
GET /api/files/list?path=&show_hidden=— directory entries (dirs first).GET /api/files/read?path=— UTF-8 text ≤512 KB; images return{kind: "image", url}instead (routes_files.py:148).GET /api/files/image?path=— serves an image after PIL verification.POST /api/files/write— overwrite with automatic pre-write snapshot into.kucatoo_backups/<id>/(routes_files.py:179, 88).POST /api/files/mkdir,POST /api/files/delete({paths: []}),POST /api/files/upload(multipart, filenames stripped to basename).POST /api/files/zip({paths}, ≤100) — zip download.POST /api/files/backup,GET /api/files/backups?path=,POST /api/files/restore— manual snapshots; restore snapshots current state first (routes_files.py:369-395).POST /api/files/gitignore—{action: add|remove, paths}edits the workspace.gitignore.
Guest tokens
guests.py is a SQLite store at <DATA_DIR>/guests.db, one guest_tokens table, no ORM (guests.py:21-33). Tokens are 32-byte urlsafe random strings with primary-key exact lookup (no enumeration). Defaults (env-overridable): GUEST_DEFAULT_DAYS=2, GUEST_DEFAULT_QUERIES=10, GUEST_DEFAULT_TOKENS=50000 (guests.py:76-86). GUEST_MODELS (default deepseek-v4-flash,glm-5.2) is the guest model allowlist (guests.py:195).
Enforcement path:
- Role resolution
owner > token > guestinauth.resolve_role(); a presented guest token yieldsg.user_role="guest"+g.guest_record(auth.py:142-160). - Guests get 403
guest_forbiddenon/api/visionand/api/videoprefixes (GUEST_BLOCKED_PREFIXES, auth.py:46), and 403 on voice chat (/api/audio/voice-key, routes_audio.py:424-426). /api/querygates: model allowlist (400 listing allowed models), quota check → 429{event:"error", data:"quota_exceeded", usage}, thenrecord_query()per accepted query andrecord_tokens()at stream done (routes_query.py:268-286, 812, 1124).- Guests are sandboxed to
<workspace>/guests/<token-prefix>/viaguest_scoped_root(auth.py:18-21).
Management (owner/token role, routes_status.py:156-184): GET /api/guests lists all tokens with computed expired flag; POST /api/guests mints one ({label, days, max_queries, max_tokens}) and returns the share link <host>/?token=<token>; POST /api/guests/<token>/revoke revokes (idempotent).
Full REST route table
The table below is generated by scripts/wiki_tables.py — do not hand-edit; re-run the script to refresh.
<!-- AUTO:routes -->
| Route | Methods |
|---|---|
/ |
GET |
/api/agent/auto-approve |
POST |
/api/agent/continue |
POST |
/api/agent/instruct |
POST |
/api/agents |
GET |
/api/analysis/ask |
POST |
/api/analysis/log |
GET |
/api/analysis/response |
POST |
/api/analysis/save |
POST |
/api/analysis/series |
POST |
/api/audio/clone-voice |
POST |
/api/audio/omni-voices |
GET |
/api/audio/pricing |
GET |
/api/audio/settings |
GET |
/api/audio/settings |
POST |
/api/audio/tts |
POST |
/api/audio/voice-key |
GET |
/api/audio/voice-session-end |
POST |
/api/audio/voices |
GET |
/api/audio/voices/<path:voice_id> |
DELETE |
/api/auth/me |
GET |
/api/auth/settings |
GET,POST |
/api/billing/change |
POST |
/api/billing/checkout |
POST |
/api/billing/clients |
GET |
/api/billing/manage |
GET |
/api/billing/webhook |
POST |
/api/domains |
GET |
/api/domains/detect |
POST |
/api/drive/backup |
POST |
/api/drive/callback |
GET |
/api/drive/connect |
GET |
/api/drive/status |
GET |
/api/eval/run |
POST |
/api/eval/runs |
GET |
/api/eval/suites |
GET |
/api/feedback |
POST |
/api/files/backup |
POST |
/api/files/backups |
GET |
/api/files/delete |
POST |
/api/files/gitignore |
POST |
/api/files/image |
GET |
/api/files/list |
GET |
/api/files/mkdir |
POST |
/api/files/read |
GET |
/api/files/restore |
POST |
/api/files/upload |
POST |
/api/files/write |
POST |
/api/files/zip |
POST |
/api/fly/apps |
GET |
/api/fly/change-tier |
POST |
/api/fly/destroy |
POST |
/api/fly/provision |
POST |
/api/fly/provision-status/<name> |
GET |
/api/fly/sweep |
POST |
/api/fly/terminate |
POST |
/api/fly/terminate-undo |
POST |
/api/guards |
GET |
/api/guests |
GET,POST |
/api/guests/<token>/revoke |
POST |
/api/history |
GET |
/api/hosting/hook |
POST |
/api/job-runs |
GET |
/api/job-runs/<execution_id> |
DELETE |
/api/loop/balance |
GET |
/api/loop/history |
GET |
/api/loop/policy |
|
/api/loop/policy |
GET |
/api/loop/run |
POST |
/api/loop/runs |
GET |
/api/memory |
GET |
/api/memory/candidates |
GET |
/api/memory/commit |
POST |
/api/memory/remember |
POST |
/api/memory/stats |
GET |
/api/models |
GET |
/api/permissions/respond |
POST |
/api/profile-loop/status |
GET |
/api/profile-proposals |
GET |
/api/profile-proposals/<name> |
GET |
/api/profile-proposals/<name>/adopt |
POST |
/api/profile-proposals/<name>/dismiss |
POST |
/api/profile-proposals/<name>/estimate |
GET |
/api/profile-proposals/<name>/evaluate |
POST |
/api/projects |
GET |
/api/projects/activate |
POST |
/api/prompt/tighten |
POST |
/api/query |
POST |
/api/save/prompt |
POST |
/api/save/response |
POST |
/api/schedules |
GET |
/api/schedules |
POST |
/api/schedules/<sid> |
|
/api/schedules/<sid> |
DELETE |
/api/schedules/<sid>/run |
POST |
/api/server/stop |
POST |
/api/settings/domain |
DELETE |
/api/settings/domain |
GET |
/api/settings/domain |
POST |
/api/settings/domain/verify |
POST |
/api/settings/keys |
GET |
/api/settings/keys |
POST |
/api/settings/keys/<provider> |
DELETE |
/api/settings/tiers |
|
/api/settings/tiers |
GET |
/api/sites |
GET |
/api/sites/backup |
POST |
/api/sites/log |
GET |
/api/sites/promote |
POST |
/api/sites/start |
POST |
/api/sites/stop |
POST |
/api/sites/tags |
GET |
/api/sites/worktree |
POST |
/api/skills |
GET |
/api/skills/commit |
POST |
/api/skills/examples |
GET |
/api/skills/examples/reset |
POST |
/api/skills/execute |
POST |
/api/status |
GET |
/api/storage/open/<kind> |
POST |
/api/storage/paths |
GET |
/api/stt/assist |
POST |
/api/theme |
POST |
/api/tools |
GET |
/api/tools/examples |
GET |
/api/tools/examples/reset |
POST |
/api/tools/execute |
POST |
/api/trace/analyze |
POST |
/api/trace/ask |
POST |
/api/trace/save |
POST |
/api/trace/settings |
GET |
/api/trace/settings |
POST |
/api/trace/state |
GET |
/api/traces |
GET |
/api/video/enhance-prompt |
POST |
/api/video/query |
POST |
/api/video/regenerate |
POST |
/api/vision/gallery |
GET |
/api/vision/gallery/delete |
POST |
/api/vision/gallery/download |
POST |
/api/vision/query |
POST |
/api/wiki/history |
GET |
/api/wiki/history/<name> |
GET |
/api/wiki/search |
GET |
/api/workflows/approve |
POST |
/api/workflows/definitions |
GET |
/api/workflows/executions |
GET |
/api/workspaces |
GET |
/api/workspaces/activate |
POST |
/api/workspaces/create |
POST |
/api/workspaces/stats |
GET |
/api/workspaces/tree |
GET |
/api/workspaces/usage |
GET |
/api/workspaces/usage/export |
GET |
/audio/<path:filename> |
GET |
/auth/callback |
GET |
/auth/google |
GET |
/auth/login |
GET |
/auth/logout |
GET |
/billing/return |
GET |
/favicon.ico |
GET |
/help |
GET |
/images/<path:filename> |
GET |
/jobs |
GET |
/prompts/<path:filename> |
GET |
/responses/<path:filename> |
GET |
/settings |
GET |
/video |
GET |
/videos/<path:filename> |
GET |
/vision |
GET |
/wiki |
GET |
/wiki/<slug> |
GET |
| <!-- /AUTO:routes --> |