Skip to content

Commit 3e47c58

Browse files
committed
refactor: move pkg/client to internal/api and use domain types
- Delete pkg/client/ - internal packages shouldn't be imported externally - Add api.NewServices() constructor that creates client internally - Update all callers to use services instead of client directly - Use domain.AccountID for SetAccountID throughout - Use domain.WorkosOrganizationID for RefreshTokenWithOrganization - Use domain.OrganizationID in auth command's org struct - Extract timeout constant in workos/client.go
1 parent 7be0384 commit 3e47c58

268 files changed

Lines changed: 12179 additions & 22370 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.golangci.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,19 @@ linters:
3535

3636
# Type safety
3737
- forcetypeassert # Unchecked type assertions can panic
38+
- exhaustive # Exhaustive enum switches
3839

3940
# Additional checks
4041
- nilerr # Returns nil even when error is not nil
4142
- nolintlint # Validates nolint directives
4243
- usestdlibvars # Use stdlib constants
4344

4445
settings:
46+
exhaustive:
47+
check: switch
48+
default-signifies-exhaustive: true
49+
check-generated: false
50+
4551
govet:
4652
enable-all: true
4753
disable:

‎POWERSYNC_UPLOAD_PROTOCOL.md‎

Lines changed: 0 additions & 163 deletions
This file was deleted.

‎docs/ARCHITECTURE.md‎

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,40 @@ The CLI never implements intelligence. It doesn't analyze log patterns, calculat
3333
│ └───────┬───────┘ │
3434
└─────────────────────────┼───────────────────────────────────────────────┘
3535
│
36-
│ PowerSync (bidirectional sync)
37-
▼
38-
┌─────────────────────────────────────────────────────────────────────────┐
39-
│ Control Plane │
40-
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
41-
│ │ GraphQL │ │ Chat │ │ Catalog │ │
42-
│ │ API │ │ Service │ │ Service │ │
43-
│ └───────────┘ └─────┬─────┘ └───────────┘ │
44-
│ │ │
45-
│ ▼ │
46-
│ ┌───────────────┐ │
47-
│ │ Claude │ AI reasoning, tool calls │
48-
│ └───────────────┘ │
49-
└─────────────────────────────────────────────────────────────────────────┘
36+
┌───────────────┴───────────────┐
37+
│ │
38+
▼ ▼
39+
┌─────────────────────┐ ┌─────────────────────────────────────────────┐
40+
│ Chat API (direct) │ │ Control Plane │
41+
│ │ │ ┌───────────┐ ┌───────────┐ │
42+
│ Stateless HTTP │ │ │ GraphQL │ │ Catalog │ │
43+
│ Client sends full │ │ │ API │ │ Service │ │
44+
│ message history │ │ └───────────┘ └───────────┘ │
45+
│ │ │ │ ▲ │
46+
│ ▼ │ │ │ Upload queue (durability) │
47+
│ ┌───────────┐ │ └────────┼────────────────────────────────────┘
48+
│ │ Claude │ │ │
49+
│ └───────────┘ │ │ PowerSync (catalog sync)
50+
└─────────────────────┘ │
51+
│ │
52+
└──────────────────────────┘
53+
SSE stream back to client
5054
```
5155

5256
**The chat loop:**
5357

54-
1. User types a message in the command bar
55-
2. Client writes user message to local SQLite
56-
3. PowerSync syncs the message to the control plane
57-
4. Control plane loads conversation history, builds system prompt, calls Claude
58-
5. Claude responds (possibly with tool calls for views, context changes, etc.)
59-
6. Control plane writes assistant message to database
60-
7. PowerSync syncs the response back to the client
61-
8. Client renders the new message
58+
1. User types a message and hits enter
59+
2. Client creates conversation in SQLite if new, then writes user message
60+
3. Client calls Chat API directly with the full message history from SQLite
61+
4. Chat API is stateless—it builds the prompt from the messages you send, calls Claude
62+
5. Claude streams response blocks back via SSE (text, thinking, tool calls)
63+
6. Client renders blocks in real-time as they arrive
64+
7. When stream completes (stop_reason received), client writes assistant message to SQLite
65+
8. Upload queue (background) persists messages to GraphQL for durability
6266

63-
The client never calls Claude directly. It writes messages and renders responses. The control plane handles everything in between.
67+
**Key principle: the client is the exclusive writer.** SQLite is the source of truth. The Chat API never persists anything—it's a pure function from messages to response stream. The upload queue handles durability by persisting to the server via GraphQL, but this is decoupled from the chat flow.
68+
69+
**Why client sends full history:** The Chat API is 100% stateless. No database lookups, no sync concerns, no race conditions. What you send is what the model sees. The upload queue can be behind—doesn't matter for chat correctness. It only matters for durability and multi-device sync.
6470

6571
---
6672

@@ -107,11 +113,12 @@ For background processes (goroutines, channels), you need a bridge to convert Go
107113
3. The bridge returns the message as a `tea.Cmd` so Bubbletea propagates it
108114
4. Any component in the tree can handle the message
109115

110-
For example, when a user message starts uploading:
116+
For example, streaming chat responses:
111117

112-
1. `upload.Uploader` (background goroutine) emits `MessageProcessingEvent` to a channel
113-
2. `database.Uploader` (TUI bridge) receives it and emits `database.UploadEventMsg`
114-
3. `chat.Messages` handles the message and shows the spinner
118+
1. User submits message, TUI starts a goroutine that calls Chat API
119+
2. Chat API streams SSE events, goroutine converts each to a `tea.Msg`
120+
3. TUI receives messages and updates UI in real-time (text deltas, thinking blocks, etc.)
121+
4. When stream completes, TUI writes the assistant message to SQLite
115122

116123
The bridge is transparent—it converts and emits. Producers don't know about consumers. Consumers import the message type from the producer package. Dependencies point in the natural direction.
117124

@@ -228,13 +235,18 @@ Views are tool results. When the AI creates a view, it's a tool call with a view
228235

229236
When the user submits:
230237

231-
1. Client writes user message to local SQLite
232-
2. PowerSync syncs to control plane
233-
3. Control plane processes (calls Claude, etc.)
234-
4. Assistant message syncs back
235-
5. Client renders it
238+
1. Client creates conversation in SQLite (if new)
239+
2. Client writes user message to SQLite immediately
240+
3. Client reads full message history from SQLite
241+
4. Client calls Chat API directly with history + new message
242+
5. Chat API streams response blocks via SSE
243+
6. Client renders blocks in real-time as they arrive (ephemeral UI state)
244+
7. When stream completes, client writes assistant message to SQLite
245+
8. UI re-renders from SQLite (now showing persisted message)
236246

237-
The client doesn't wait for a response. It writes optimistically and renders when data arrives via sync.
247+
The Chat API is stateless—it doesn't store messages. The client sends the full conversation history on every request. This eliminates sync concerns: what you send is what the model sees.
248+
249+
The upload queue runs independently, persisting messages to GraphQL for durability. It doesn't block chat—if it's behind, chat still works correctly.
238250

239251
---
240252

@@ -306,17 +318,22 @@ This is why navigation is instant. Filtering, sorting, searching—all local que
306318

307319
### What's Local vs Remote
308320

309-
**Local (via PowerSync):**
310-
- Catalog entities (services, log events, policies, etc.)
311-
- Conversations and messages
321+
**Local (SQLite, client-owned):**
322+
- Catalog entities (services, log events, policies, etc.) — synced via PowerSync
323+
- Conversations and messages — written by client, uploaded for durability
312324
- Conversation context
313325
- Views
314326

315327
**Remote (via API):**
316-
- Actual telemetry (raw logs, metric data points)
317-
- Actions requiring immediate server processing
328+
- Chat inference (Chat API) — stateless, client sends full history, gets streamed response
329+
- Actual telemetry (raw logs, metric data points) — proxied from vendor
318330
- Authentication flows
319331

332+
**Durability (via GraphQL upload queue):**
333+
- Messages are written locally first, then uploaded to GraphQL for persistence
334+
- Upload queue runs independently — doesn't block chat
335+
- Server-side messages enable multi-device sync and recovery
336+
320337
The catalog describes data. The telemetry is the actual data. When you need real logs, Tero proxies the request to the vendor.
321338

322339
### Context

‎go.mod‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,12 @@ require (
4040
github.com/godbus/dbus/v5 v5.1.0 // indirect
4141
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
4242
github.com/inconshreveable/mousetrap v1.1.0 // indirect
43-
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
4443
github.com/kr/pretty v0.3.1 // indirect
4544
github.com/mattn/go-runewidth v0.0.19 // indirect
4645
github.com/muesli/cancelreader v0.2.2 // indirect
4746
github.com/sahilm/fuzzy v0.1.1 // indirect
4847
github.com/spf13/pflag v1.0.10 // indirect
4948
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
50-
github.com/zeebo/xxh3 v1.1.0 // indirect
5149
golang.org/x/mod v0.28.0 // indirect
5250
golang.org/x/sync v0.18.0 // indirect
5351
golang.org/x/sys v0.38.0 // indirect

‎go.sum‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
7878
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
7979
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
8080
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
81-
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
82-
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
8381
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
8482
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
8583
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -128,8 +126,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
128126
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
129127
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
130128
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
131-
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
132-
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
133129
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
134130
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
135131
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=

0 commit comments

Comments
 (0)