This guide gives AI coding agents the current project scope, architecture, and workflows for CLIRO.
CLIRO is a Wails desktop control plane for a local OpenAI-compatible + Anthropic-compatible proxy.
- Backend: Go 1.23+
- Frontend: Svelte + TypeScript + Vite
- Desktop shell: Wails v2.11+
- Current release: v0.4.0
- Main entry point:
main.go - Wails app bridge:
app.go
CLIRO currently focuses on:
- Multi-account routing across Codex and Kiro providers.
- OAuth/device/social auth flows and token lifecycle handling.
- Quota-aware account health + cooldown + scheduling.
- API Router controls (proxy runtime, model aliasing, Cloudflared, endpoint tester, one-click CLI config sync).
- Local desktop-first UX with persisted JSON state in
~/.cliro/.
GET /v1/modelsexposes canonical model IDs only (no published-thinkingaliases).- Requests with
-thinkingsuffix are still normalized during model resolution for compatibility. - Model effort suffixes enable automatic reasoning parameter injection:
-low/-minimal→ 4096 budget tokens (OpenAI:effort: "low")-medium→ 10000 budget tokens (OpenAI:effort: "medium")-high→ 16384 budget tokens (OpenAI:effort: "high")-xhigh→ 32768 budget tokens (OpenAI:effort: "xhigh")
- Example:
gpt-5.4-highauto-injectsreasoning: {effort: "high"}for Codex,thinking: {budget_tokens: 16384}for Anthropic - Cross-protocol reasoning/thinking conversion:
- OpenAI
reasoning.effort↔ Anthropicthinking.budget_tokensbidirectional mapping - Automatic parameter filtering to prevent "Unknown parameter" errors
- Response format:
reasoning_contentfield in OpenAI endpoints, thinking blocks in Anthropic endpoints
- OpenAI
- Kiro runtime uses
q.us-east-1.amazonaws.com/generateAssistantResponsefirst and falls back tocodewhisperer.us-east-1.amazonaws.com/generateAssistantResponseon runtime failure. - Authorization mode (when enabled) requires the configured proxy API key on all proxy routes.
- Smart quota refresh skips accounts that are disabled/banned/not-yet-reset exhausted; force refresh bypasses smart skip.
- One-click CLI config sync targets in API Router:
claude-codeopencode-clikilo-clicodex-ai
CLIRO mimics official client user agents to ensure compatibility with upstream provider APIs.
Implementation: hardcoded in internal/provider/codex/service.go
Current Version: codex-tui/0.118.0
Format: codex-tui/{version} ({os}; {arch}) {app} (codex-tui; {version})
Example:
codex-tui/0.118.0 (Mac OS 26.3.1; arm64) iTerm.app/3.6.9 (codex-tui; 0.118.0)
Headers Sent on Inference Requests:
User-Agent:codex-tuiformat aboveOriginator:codex-tuiVersion:0.118.0Origin:https://chatgpt.comReferer:https://chatgpt.com/Session_id: Random UUID per request
Implementation: internal/auth/kiro/types.go, internal/provider/kiro/service.go
Current Version: KiroIDE-0.11.107
AWS SDK Version: aws-sdk-js/1.2.15
Headers Sent:
User-Agent:aws-sdk-js/1.2.15 ua/2.1 os/linux lang/js md/nodejs#22.21.1 api/codewhispererstreaming#1.2.15 m/E KiroIDE-0.11.107-{machineID}x-amz-user-agent:aws-sdk-js/1.2.15 KiroIDE 0.11.107x-amzn-kiro-agent-mode:spec
Social Auth Specific:
User-Agent:KiroIDE-0.11.107-{uuid}(for auth endpoints)
Headers Sent:
User-Agent:aws-sdk-rust/1.3.9 os/macos lang/rust/1.87.0x-amz-user-agent:aws-sdk-rust/1.3.9 ua/2.1 api/ssooidc/1.88.0 os/macos lang/rust/1.87.0 m/E app/AmazonQ-For-CLIx-amzn-kiro-agent-mode:vibe
Headers Sent:
User-Agent:aws-sdk-js/1.2.15 ua/2.1 os/linux lang/js md/nodejs#22.21.1 api/sso-oidc#1.2.15 m/E KiroIDEx-amz-user-agent:aws-sdk-js/1.2.15 KiroIDE
When updating user agent versions:
- Codex TUI: Update Codex version/user-agent constants in
internal/provider/codex/service.go - Kiro IDE: Update version strings in:
internal/provider/kiro/service.go(runtime constants)internal/auth/kiro/types.go(auth constants)internal/auth/kiro/device.go(OIDC headers)
- Test: Verify auth flows and API requests still work after version bump
- Reference: Check latest versions at:
- Codex CLI:
https://www.npmjs.com/package/@openai/codex - Kiro CLI:
https://kiro.dev/changelog
- Codex CLI:
Kiro requests include a random machine ID suffix to simulate unique client instances:
- Generated via
uuid.NewString()with hyphens removed - Appended to user agent string (e.g.,
KiroIDE-0.11.107-a1b2c3d4e5f6...) - Regenerated per request for social auth mode
CLIRO persists runtime data under ~/.cliro/:
config.json- proxy, scheduling, cloudflared, model aliases, etc.accounts.json- account and token/quota state.stats.json- proxy usage counters.app.log- persistent app log.bin/cloudflared(.exe)- local Cloudflared binary.
cd frontend && npm install && cd ..
wails devcd frontend && npm run check && cd ..
go test . ./internal/...wails buildOutput (Windows): build/bin/CLIRO.exe
app.goexposes methods used by frontend.main.gobindsAppinto Wails runtime.- Generated bindings live in
frontend/wailsjs/go/main/. - Frontend calls into backend exclusively through
frontend/src/backend/layer.
Current frontend layout:
frontend/src/
App.svelte # app entry — mounts AppFrame
main.ts # Svelte bootstrap
app/ # app-level orchestration
bootstrap/
app-bootstrap.ts # initializeAppBootstrap() — startup sequence
app-events.ts # bindAppRuntimeEvents / bindAppActivityEvents
modals/
AppCloseModal.svelte # close-to-tray / confirm-quit modal
ConfigurationRecoveryModal.svelte
UpdateRequiredModal.svelte
overlays/
AppOverlayHost.svelte # stacks all app-level modals
routes/
app-routes.ts # APP_ROUTES registry — static + lazy per tab
RouteOutlet.svelte # renders the active route component
services/
app-controller.ts # central controller — AppShellState + all action namespaces
logs-subscription.ts # Wails event → ring-log bridge
startup-warnings.ts # maps startup warnings to display entries
shell/
AppFrame.svelte # top-level shell: header + tabs + route outlet + footer
AppHeader.svelte
AppFooter.svelte
types/
index.ts # AppState, LogEntry, UpdateInfo
utils/
backup.ts
tabs.ts # APP_TABS, AppTabId
backend/ # all backend access — ONLY place that imports wailsjs
client/
browser.ts # browser-mode stubs
runtime-events.ts # typed Wails event subscription helpers
wails-client.ts # raw Wails JS bindings wrapper
gateways/
accounts-gateway.ts # account CRUD calls
auth-gateway.ts # codex + kiro auth calls
logs-gateway.ts # getLogs / clearLogs
router-gateway.ts # proxy / cloudflared / alias / cli-sync calls
system-gateway.ts # getState / openDataDir / confirmQuit / hideToTray etc.
models/
wails.ts # raw Wails DTO types (WailsAccount, WailsAppState, …)
system.ts
features/ # domain features
accounts/
api/
accounts-api.ts
auth-api.ts
components/
AccountsScreen.svelte # container: wires store + actions → AccountsWorkspace
AccountsWorkspace.svelte
connect/
AccountsConnectSection.svelte
ConnectPromptModal.svelte
KiroConnectModal.svelte
list/
AccountActions.svelte / AccountCard.svelte / AccountRow.svelte
AccountsGrid.svelte / AccountsListSection.svelte
AccountsTable.svelte / AccountsToolbar.svelte / ProviderAvatar.svelte
modals/
AccountDetailModal.svelte / AccountSyncModal.svelte
AccountsWorkspaceModals.svelte / BatchDeleteModal.svelte / CredentialField.svelte
store/
accounts-actions.ts # createAccountsScreenActions()
accounts-store.ts # createAccountsStoreState()
utils/
account.ts / account-quota.ts / auth-session.ts
preferences.ts / presenter.ts / sync.ts
workspace.ts / workspace-controller.ts
index.ts / types.ts
router/
components/
cli-sync/
CliSyncInfoModal.svelte / CliSyncPanel.svelte
cloudflared/
CloudflaredPanel.svelte
endpoint-tester/
EndpointTesterPanel.svelte
model-alias/
ModelAliasPanel.svelte
proxy/
ProxyControlsPanel.svelte / ProxyInlineSwitch.svelte
ProxyRuntimeCard.svelte / ProxySecurityCard.svelte
scheduling/
SchedulingPanel.svelte
store/
router-actions.ts
router-store.ts
utils/
alias-form.ts / cli-sync.ts / cloudflared.ts
endpoint-tester.ts / scheduling.ts
index.ts / types.ts
logs/
components/
SystemLogsWorkspace.svelte
utils/
logs-view.ts # maps LogEntry → display columns (Level/Source/Account/Detail/Time)
index.ts
usage/
components/
UsageWorkspace.svelte
utils/
request-log.ts
index.ts
settings/ # settings feature
components/
BackupToolsCard.svelte # export / import / restore backup
DataFolderCard.svelte # open data folder
SettingsScreen.svelte # top-level settings layout
store/
task-state.ts # deriveSettingsViewState / createAsyncTaskState
utils/
backup.ts # validateBackupPayload / assertBackupPayloadRestorable
index.ts / types.ts
components/common/ # reusable UI primitives
BaseModal.svelte / Button.svelte / CollapsibleSurfaceSection.svelte
ControlWorkspaceCard.svelte / ModalBackdrop.svelte / ModalWindowHeader.svelte
OpsPanelSection.svelte / StatusBadge.svelte / SurfaceCard.svelte
ToastViewport.svelte / ToggleSwitch.svelte
shared/ # cross-feature utilities
stores/
theme.ts / toast.ts
utils/
async.ts / browser.ts / cn.ts / copy.ts
error.ts / formatters.ts / storage.ts
tabs/ # thin tab wrappers (compose features)
AccountsTab.svelte
ApiRouterTab.svelte
DashboardTab.svelte
SystemLogsTab.svelte
UsageTab.svelte
# SettingsTab.svelte removed — settings routes directly to SettingsScreen
styles/
index.css # imports all partials
base/base.css
tokens/theme.css
primitives/components.css
features/
accounts.css / logs.css / router.css / settings.css / usage.css
Key frontend files:
- App bootstrap and shell:
frontend/src/app/bootstrap/app-bootstrap.ts—initializeAppBootstrap()startup sequencefrontend/src/app/bootstrap/app-events.ts— runtime + activity event bindingfrontend/src/app/services/app-controller.ts—AppShellState,AppActions,AccountsActions,RouterActions,LogsActions,SettingsActionsfrontend/src/app/routes/app-routes.ts—APP_ROUTESstatic + lazy route registryfrontend/src/app/routes/RouteOutlet.svelte— renders active tab routefrontend/src/app/shell/AppFrame.sveltefrontend/src/app/utils/tabs.ts—APP_TABS,AppTabId
- Backend access layer:
frontend/src/backend/client/wails-client.ts— grouped raw Wails JS binding adapterfrontend/src/backend/gateways/— final domain-oriented frontend backend surface
- Accounts feature:
frontend/src/features/accounts/components/AccountsScreen.svelte— containerfrontend/src/features/accounts/store/accounts-actions.tsfrontend/src/features/accounts/store/accounts-store.ts
- Router feature:
frontend/src/features/router/store/router-actions.tsfrontend/src/features/router/store/router-store.ts
- Settings feature:
frontend/src/features/settings/components/SettingsScreen.sveltefrontend/src/features/settings/utils/backup.ts
- Logs and usage:
frontend/src/features/logs/utils/logs-view.tsfrontend/src/features/usage/utils/request-log.ts
- Proxy Shell:
internal/proxy/http/- server lifecycle, mux wiring, common request context, shared route shell
- OpenAI/Codex Proxy Face:
internal/proxy/codex/- OpenAI-style request/response DTOs
- OpenAI-style request normalization and response encoding
- OpenAI-style streaming helpers and route-specific execution helpers
- Anthropic Proxy Face:
internal/proxy/anthropic/- Anthropic request/response DTOs
- Anthropic request normalization and response encoding
- Anthropic streaming helpers and route-specific execution helpers
- Canonical Proxy Model:
internal/proxy/models/- shared typed request/response/content/tool/image/thinking model
- validation rules, tool arg remapping, model/provider resolution, model catalog exposure
- Proxy Shared Helpers:
internal/proxy/shared/- small response/error helpers shared by protocol-facing proxy packages
- Auth:
internal/auth/- Codex OAuth flow + Kiro auth flows
- Provider Services:
internal/provider/- root package keeps shared health/failure classification only
internal/provider/codex/owns Codex runtime executioninternal/provider/kiro/owns Kiro runtime payload building, runtime fallback, stream parsing, and quota fetch
- Config Storage:
internal/config/- snapshot + atomic updates over JSON files
- Structured Logging:
internal/logger/- in-memory + persistent JSONL log storage
- structured entries with
level,scope,event,requestId,message, andfields
- Sync Services:
internal/sync/internal/sync/cliconfig/for one-click CLI config patch/read/writeinternal/sync/authtoken/for account auth token sync into supported CLIs
- Cloudflared:
internal/cloudflared/manager.go- install, start/stop tunnel, parse URL/status
internal/contract/,internal/gateway/,internal/route/, andinternal/protocol/*are gone. Do not recreate them.- Shared request/response ownership now lives in
internal/proxy/models/. - Protocol-specific behavior belongs in
internal/proxy/codex/orinternal/proxy/anthropic/, not in generic proxy glue. - Provider-specific runtime behavior belongs in
internal/provider/codex/orinternal/provider/kiro/. - Kiro path is live and usable, but edge-case hardening is still most likely needed around images, tool-result fidelity, and streaming parity.
CLI sync lives under API Router and is implemented in internal/sync/cliconfig/.
Account auth-sync is separate and implemented in internal/sync/authtoken/.
Targets and config files:
claude-code->~/.claude/settings.json+~/.claude.jsonopencode-cli->~/.config/opencode/opencode.jsonkilo-cli->~/.config/kilo/opencode.jsoncodex-ai->~/.codex/config.toml+~/.codex/auth.json
For OpenCode/Kilo JSON config generation:
$schemais set tohttps://opencode.ai/config.json- provider key is
CLIRO permission.bash = "allow"- selected model is injected from local catalog (
GetLocalModelCatalog())
Kilo install detection:
- Treated as installed when
~/.config/kiloexists (even if binary is not in PATH).
- Backend emits log events via Wails runtime events.
- Frontend subscribes via
frontend/src/app/services/logs-subscription.tsand renders in system logs UI. - Structured log entries include
level,scope,event,requestId,message, and optionalfields. - The system logs table is optimized around
Level / Source / Account / Detail / Time; updatefrontend/src/features/logs/utils/logs-view.tswhenlogger.Entryshape changes.
Important methods exposed in app.go:
GetState()GetAccounts()GetProxyStatus()RefreshCloudflaredStatus()GetLogs(limit int)ClearLogs()GetHostName()
ConfirmQuit()HideToTray()RestoreWindow()
StartProxy()/StopProxy()SetProxyPort(port int)SetAllowLAN(enabled bool)SetAutoStartProxy(enabled bool)SetProxyAPIKey(apiKey string)RegenerateProxyAPIKey()SetAuthorizationMode(enabled bool)SetSchedulingMode(mode string)GetModelAliases()/SetModelAliases(aliases map[string]string)
InstallCloudflared()StartCloudflared()StopCloudflared()SetCloudflaredConfig(mode, token string, useHTTP2 bool)
StartCodexAuth()/GetCodexAuthSession()/CancelCodexAuth()/SubmitCodexAuthCode()StartKiroAuth()/StartKiroSocialAuth()/GetKiroAuthSession()/CancelKiroAuth()/SubmitKiroAuthCode()RefreshAccount(accountID string)RefreshAccountWithQuota(accountID string)RefreshQuota(accountID string)RefreshAllQuotas()ForceRefreshAllQuotas()ToggleAccount(accountID string, enabled bool)DeleteAccount(accountID string)ImportAccounts(accounts []config.Account)ClearCooldown(accountID string)
GetLocalModelCatalog()GetCLISyncStatuses()SyncCLIConfig(appID, model string)GetCLISyncFileContent(appID, path string)SaveCLISyncFileContent(appID, path, content string)
SyncCodexAccountToKiloAuth(accountID string)SyncCodexAccountToCodexCLI(accountID string)SyncCodexAccountToOpencodeAuth(accountID string)
OpenExternalURL(rawURL string)OpenDataDir()
Default base URL: http://localhost:8095
POST /v1/responsesPOST /v1/chat/completionsPOST /v1/completionsPOST /v1/messagesPOST /v1/messages/count_tokensGET /v1/modelsGET /v1/statsGET /health
- Keep changes idiomatic and small per package boundary.
- Wrap errors with context (
fmt.Errorf("context: %w", err)). - Prefer immutable snapshot reads + atomic update closures for config/account mutations.
- Avoid panics in runtime paths.
- All backend access goes through
frontend/src/backend/— never import fromwailsjsdirectly in features or UI components. - Use the domain gateway modules in
backend/gateways/as the import point for backend calls in feature API modules. - Keep data access inside feature
api/orstore/modules, not ad-hoc in UI components. - Reuse
components/common/primitives andshared/stores/utils. - Keep tab files thin; place business logic in
features/*andapp/*modules. - Feature screens use a container component (e.g.,
AccountsScreen.svelte,SettingsScreen.svelte) that wires store state + actions, then passes them down to workspace components. - Route registration lives in
app/routes/app-routes.ts— add new tabs there using static or lazyload:routes. - Keep router sub-surfaces under feature-owned folders (
proxy/,cloudflared/,cli-sync/,endpoint-tester/,model-alias/,scheduling/). - When system log structure changes, update both
frontend/src/features/logs/utils/logs-view.tsandfrontend/src/features/logs/components/SystemLogsWorkspace.sveltetogether. - Styles split by feature under
frontend/src/styles/features/— add a new CSS file per new feature. - Use
npm run checkbefore finalizing changes.
- Add exported method to
app.go. - Run
wails devorwails buildto regenerate JS/TS bindings infrontend/wailsjs/. - Expose via the appropriate gateway in
frontend/src/backend/gateways/. - Wire into a feature-level action or store.
- Create the tab component under
frontend/src/tabs/(thin wrapper) or a screen underfeatures/<name>/components/. - Register a new route entry in
frontend/src/app/routes/app-routes.ts— useload:for lazy routes. - Add the tab ID to
APP_TABSinfrontend/src/app/utils/tabs.ts. - Add the tab button to
AppHeader.svelte.
- Update protocol-facing codec logic in
internal/proxy/codex/and/orinternal/proxy/anthropic/if protocol mapping is needed. - Update route shell wiring in
internal/proxy/http/if a new endpoint or handler path is needed. - Update validation and model resolution in
internal/proxy/models/. - Add tests for both OpenAI and Anthropic request paths.
- Add
Appconstant +appDefinitionininternal/sync/cliconfig/service.go. - Implement read/patch logic for status + sync.
- Extend frontend type union (
CliSyncAppID) and router card metadata. - Add tests in
internal/sync/cliconfig/service_test.go.
go test . ./internal/...passes.cd frontend && npm run checkpasses.- Proxy reliability is currently strongest on Codex/OpenAI paths. Kiro is live and routed, but agents should expect the remaining risk to concentrate in image handling, tool-result fidelity, and stream edge cases.
- Manually verify API Router flows if router state/config behavior changed:
- proxy start/stop
- model alias save/apply
- Cloudflared install/start/stop
- one-click CLI sync statuses and write paths
- Manually verify System Logs if logger schema/UI changes:
- level/source/account/detail/time columns render correctly
- request/account-related fields are summarized into the expected columns
- copy/export still includes the structured entry content
- Tokens are local-file persisted; never print tokens in logs.
- When enabling LAN binding (
allowLan=true), strongly pair with authorization mode. - API key headers accepted in auth mode:
Authorization: Bearer <key>X-API-Key: <key>
- Proxy won't start: port conflict; change port or free process.
- No available accounts: check enabled flag, cooldown, quota status, and auth validity.
- Cloudflared URL missing: ensure proxy is running and Cloudflared status refreshed.
- CLI sync says unsupported target/model: verify target ID union and local model catalog membership.
- Library:
fyne.io/systray v1.12.0(replacesgithub.com/getlantern/systray) - Implementation:
internal/tray/controller_windows.go - Entry point:
systray.Register(onReady, onExit)— non-blocking, hooks into the existing Wails/WebView2 message pump on the main thread. Do not switch back togo systray.Run(...)— that pattern breaks tray menu delivery on Windows because it creates the tray HWND on a goroutine without a proper shell-accessible message loop. - Why: Wails owns the OS main thread.
systray.Run()fromgetlantern/systraytried to own it too (via a goroutine), causing Windows to silently drop tray shell messages (WM_RBUTTONUP,WM_LBUTTONUP).fyne.io/systray'sRegister()is specifically designed for embedding alongside webview/toolkit event loops. - Controller interface: unchanged —
Start(),SetProxyRunning(),Close(),Supported(),Available()all remain stable. - Non-Windows:
controller_other.gois a noop stub; no changes needed there.
- Wails docs:
https://wails.io/docs/introduction - Svelte docs:
https://svelte.dev/docs - Project overview:
README.md - Release notes:
CHANGELOG.md - Frontend package notes:
frontend/README.md