Build BoltAI

KINDAback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

An AppKit application that streams from several providers behind one interface, keeps a searchable history that stays fast at ten thousand messages, handles attachments, reaches you anywhere through a global panel that captures the selected text, and reports what each conversation cost.

estimated effort multi-daythe files for this build are in the project pack

LanguageSwift 6 with AppKit, not a web viewStreamingURLSession with server-sent events per providerStorageSQLite with full text searchProvidersOne adapter per provider behind a single protocolLocal modelsAn OpenAI-compatible local endpoint as another provider

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree if you own one

    Why The point of this build is native AppKit behaviour, which exists nowhere else.

    Get it Apple menu, then About This Mac.

    Verify About This Mac reports macOS 13 or higher

  2. installfree

    Why Builds the application and provides Instruments, which you will need for the streaming and scrolling work.

    Get it Mac App Store or developer.apple.com/xcode. open ↗

    Verify xcodebuild -version prints 16 or higher

  3. API keypay per token; a development session is typically cents

    Why Phase 3 proves the abstraction, and one provider does not. Two hosted plus a local endpoint is the real test.

    Get it Create an account with two providers and generate a key for each, setting a spend limit at the same time.

    Verify Both stream a response through the same code path

  4. installfree, uses your own memory

    Why The third provider, and the one that proves the abstraction is real rather than two hosted APIs that happen to look alike.

    Get it Download from ollama.com and pull a model sized for your machine. open ↗

    Verify The local endpoint streams through the same code path

  5. 99 USD per year

    Why Product phase only. Without it there is no notarization and a warning on other machines.

    Get it Enrol at developer.apple.com/programs. open ↗

    Verify A Developer ID Application certificate appears in Xcode settings

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- providers(id, name, base_url, keychain_ref, default_model, enabled)
- conversations(id, title, provider_id, model, created_at, updated_at, archived, system_prompt)
- messages(id, conversation_id, ordinal, role, content, attachments_json, created_at, prompt_tokens, completion_tokens, cost_usd)
- messages_fts · full text over message content, updated on insert
- presets(id, name, system_prompt, model, temperature, capture_kind) · capture_kind is none, selection or clipboard
- attachments(id, message_id, path, kind, bytes, extracted_text)
- API keys live in the keychain and are referenced, never stored in the database.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired~/Library/Application Support/Chat/chat.dbConversations and messages. Contains everything you have discussed, so treat backups accordingly.
DEFAULT_PROVIDERrequiredlocalWhich provider is active on launch. Local means nothing leaves the machine.
STREAM_FLUSH_HZrequired60How often buffered tokens are flushed to the view. Frame rate, not token rate.
HISTORY_PAGE_SIZErequired80Messages loaded per page in a conversation.
COST_WARN_USDrequired1.50Conversation cost at which a warning appears.
SPARKLE_FEED_URLoptionalhttps://example.com/appcast.xmlProduct phase only.

The build, in order

  1. Streaming that feels native

    A response streams in smoothly and the window stays responsive while it does.

    1. Server-sent events over URLSession, parsed incrementally off the main thread.

      Files Providers/Stream.swift

    2. Buffer tokens, flush on a display link. This single decision is most of the perceived speed.

      Files ChatView.swift

    3. Cancel the task and the request. Stopping the display while tokens keep arriving still costs money.

      Files Providers/Stream.swift

    done when · tick each as it passes
    watch out
    • Appending each token directly to the text view forces a full layout pass per token, which pins the main thread and makes a native application feel worse than a web one. Buffer tokens and flush on a display-linked interval, so layout happens at frame rate rather than at token rate.
  2. History that stays fast

    Ten thousand messages load, scroll and search without a pause.

    1. Load the visible window of messages, not the whole conversation.

      Files Store.swift

    2. Updated on insert. Search is how you actually navigate a large history.

      Files Search.swift

    3. Ten thousand messages across several conversations, before any optimisation.

      Files Fixtures.swift

    done when · tick each as it passes
    watch out
    • Loading an entire conversation into a single text view works until a conversation is long, then costs seconds and hundreds of megabytes. Page the message list and render rows, which is what a native list view is for and is the reason to write this natively at all.
  3. Several providers, one interface

    Switching provider or model is one action and costs nothing structural.

    1. Streaming, model list, token usage and a normalised error type. Everything provider-specific stays inside the adapter.

      Files Providers/Provider.swift

    2. Two hosted and one local. The third proves the abstraction rather than just implementing it twice.

      Files Providers/

    3. Referenced from the database, never stored in it. Inspect the database file to confirm.

      Files Keychain.swift

    done when · tick each as it passes
    watch out
    • Providers differ in how they report errors, count tokens and format streaming chunks, and letting those differences leak into the interface layer means every provider addition touches the whole application. Normalise inside the adapter, including errors, so the rest of the application sees one shape.
  4. Reaching it from anywhere

    A hotkey brings up a panel that already knows what you were looking at.

    1. A non-activating panel so the previous application keeps its context.

      Files QuickPanel.swift

    2. Clipboard needs no permission. Request Accessibility only when a selection preset is used.

      Files Capture.swift

    3. Write, send Cmd-V, restore the previous pasteboard after a delay if your value is still there.

      Files PasteBack.swift

    done when · tick each as it passes
    watch out
    • Reading the selected text from another application requires Accessibility, which is a serious permission. Offer the clipboard route first, which needs nothing, and request Accessibility only when a preset that needs the selection is actually used, naming the preset.
  5. Attachments and cost

    You can send a file and you always know what a conversation has cost.

    1. Check the model's capability first and fail clearly rather than sending something it will reject.

      Files Attachments.swift

    2. Including context tokens, which is the number that explains the bill.

      Files Cost.swift

    3. Compare your total with the provider's dashboard for the same window. Investigate a gap.

      Files Cost.swift

    done when · tick each as it passes
    watch out
    • Sending a long document as context on every turn re-sends it with each message, so a twenty turn conversation pays for the document twenty times. Show the per-turn token count including context, because that number is what explains a bill that looks impossible.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more ai assistants.