# Changelog

All notable changes to `@aexol/spectral` are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- Added built-in `web_fetch` tool for fetching public web pages as text,
  Markdown, or raw HTML. Includes SSRF protection, redirect handling, timeout
  and size limits, plus unit and integration tests.

## [0.9.40] - 2026-06-22

## [0.9.39] - 2026-06-22

### Fixed

- Fixed packaged desktop login/splash pages failing to load because
  `getLoginPath` and `getSplashPath` resolved against `Resources/assets`
  instead of the assets bundled inside `app.asar`. Both pages now use a
  path relative to `__dirname`.
- Fixed a race where successfully closing the login window after API-key or
  OAuth sign-in could trigger the "Login window was closed before completing
  sign-in" error. The closed-event handler is now detached before the window
  is closed on a successful login.

## [0.9.38] - 2026-06-22

### Added

- Desktop Electron app now shows a built-in sign-in window when no Spectral
  credentials are found. New users can authenticate with a team API key or
  via browser OAuth, so `spectral serve` can register the machine on first
  launch instead of failing with "Not logged in".

### Fixed

- Fixed the packaged desktop login/splash pages failing to load because
  `getLoginPath` and `getSplashPath` resolved against `Resources/assets`
  instead of the assets bundled inside `app.asar`. Both pages now use a
  path relative to `__dirname`, so the login window appears in the packaged
  app on macOS, Windows, and Linux.
- Fixed a race where successfully closing the login window after API-key or
  OAuth sign-in could trigger the "Login window was closed before completing
  sign-in" error. The closed-event handler is now detached before the window
  is closed on a successful login.

## [0.9.37] - 2026-06-22

### Fixed

- Fixed MCP servers (e.g. `basecamp-mcp`) failing to start in the packaged
  Electron desktop app with `Executable not found in $PATH: "npx"`. The
  desktop bootstrap now augments the child process PATH with system
  directories such as `/usr/local/bin`, `/opt/homebrew/bin`, `/etc/paths`,
  `/usr/local/git/bin`, the latest nvm Node binary directory, and common
  Windows Node/Git locations, so `npx`/`npm` are discoverable when the app
  is launched from the GUI.
- Switched `npx-resolver.ts` to use `cross-spawn` instead of
  `node:child_process`, so `npm`/`npx` resolution works on Windows where
  executables have a `.cmd` extension.

## [0.8.4] - 2026-06-02

### Added

- Added `contextWindow` override field to the Built-in UserModels admin form,
  allowing admins to set a custom context window for built-in models.
  The field appears between Credits and System Prompt in the edit/create
  form. Also added to model config export/import and GraphQL selector.

## [0.8.3] - 2026-06-02

### Fixed

- Fixed aggressive context compaction on every turn when using built-in models
  (e.g. "Aexol Coder"). The `contextWindow` was always `null` for built-in
  `UserModel` entries because the lookup used pipe-separated keys
  (`provider|modelId`) but `UserModel.baseModel` stores values with a colon
  separator (`anthropic:claude-sonnet-4-20250514`). The backend now correctly
  inherits `contextWindow` from the linked `BaseModel`.

## [0.7.2] - 2026-05-25

### Changed

- Version bump for monorepo release

### Added

- Admin OpenRouter catalog picker in `/studio/admin/models` (Base Models tab):
  search across 368 models from `https://openrouter.ai/api/v1/models` and add to
  the whitelist with one click. Picker marks models already present in the
  whitelist (disabled add) regardless of their enabled state.
- `Query.adminOpenRouterCatalog: [OpenRouterModel!]!` — admin-gated, with a 1h
  in-memory TTL cache of the upstream catalog.
- `Mutation.adminCreateBaseModel(input: BaseModelCreateInput!): BaseModel!` with
  duplicate guard on `(provider, modelId)`. Defaults `enabled=true`.
- Verbatim `modelId` routing for `provider="openrouter"` in the backend
  inference proxy: no `${provider}/${modelId}` rebuild — picker-sourced rows
  carry the canonical OR ID and are sent through unchanged. Legacy rebuild
  fallback is preserved for older naive-provider rows (`provider="deepseek"`,
  `"google"`, etc.).
- Per-session model selection: choose AI model from a whitelist managed by
  admins. Selection persists per session in localStorage and is sent in the
  `prompt` envelope to apply via pi `setModel()`.
- New SQLite column `sessions.model_id` for cross-restart recovery of selected
  model.
- Synthetic pi providers `spectral-proxy-anthropic` and `spectral-proxy-openai`,
  registered at `PiBridge` start, that point pi's `ModelRegistry` at the
  backend's `/v1` proxy. `AuthStorage.inMemory()` and `ModelRegistry.inMemory()`
  skip on-disk pi credentials in `serve` mode.
- Backend `/v1/messages` and `/v1/chat/completions` machine-JWT auth branch with
  raw `modelId` resolution against the `BaseModel` whitelist.
- TTL-cached `fetchAllowedModels` GraphQL query used by `spectral serve` to
  discover the team's allowed models from the backend at startup.
- Startup info log on `spectral serve`:
  `✓ Inference routed via backend proxy (N model(s) available)`.

### Changed

- Soft-remove a model from the whitelist by reusing the existing per-row
  `Switch` toggle (no separate Remove button). Disabled rows remain visible in
  the admin table and can be re-enabled with the same Switch.
- Available models are now read from a backend-managed `BaseModel` table (synced
  from https://models.dev/api.json by admins) instead of a hardcoded frontend
  whitelist.
- `spectral serve` inference now routes through the backend proxy (centralized
  API keys) instead of reading `~/.pi/agent/auth.json`. CLI machines no longer
  need provider API keys locally; the backend manages them. The per-machine
  machine JWT carries auth, and the per-team `BaseModel` whitelist gates which
  models can be used.

### Fixed

- Admin "Add from OpenRouter" picker GraphQL syntax error
  (`Expected Name, found String "modalities"`). Mutations `adminCreateBaseModel`
  and `adminUpdateBaseModel` now pass their input objects via Zeus `$()`
  variables instead of inline serialization, so nested array fields
  (`modalities`, `supportedParameters`) parse correctly server-side.

### Removed

- Hardcoded `landing/config/model-whitelist.ts` allowlist (replaced by the
  DB-backed whitelist).

### Migration

- `spectral` (CLI / TUI subprocess mode): no change — still uses local
  `~/.pi/agent/auth.json`.
- `spectral serve`: ensure the backend has the relevant provider keys configured
  (`ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, etc.). Local
  `~/.pi/agent/auth.json` is ignored when running `serve`.

## [0.1.0] — 2026-04-29

Initial release.

### Added

- `spectral login` — interactive authentication. Verifies the team API key
  against the Aexol MCP backend before persisting credentials to
  `~/.spectral/config.json` (mode `0600`).
- `spectral logout` — removes stored credentials. Idempotent.
- `spectral serve` — always-on agent that connects this machine to the Aexol
  relay over a single long-lived WebSocket.
  - Registers a machine identity with your team on first run; reuses the issued
    JWT on subsequent runs.
  - Reconnects automatically with exponential backoff.
  - Graceful shutdown on `SIGINT` / `SIGTERM`: drains in-flight responses and
    closes the relay cleanly before exiting.
  - `--machine-name <name>` overrides the default `os.hostname()`.
- Browser-driven sessions through the Aexol web UI:
  - Machine picker for switching between paired devices.
  - Multi-tab sync of project and session lifecycle changes (create, rename,
    delete) via a per-machine meta channel.
  - Stuck-turn watchdog re-enables the composer after 60s of silence.
- Local-first storage: projects, sessions, and messages live in a SQLite
  database at `~/.spectral/sessions.db`. They never leave the machine.
- Bundled Aexol MCP extension auto-loaded for the local TUI path so `spectral`
  (no subcommand) acts as a fully-configured pi session.
- Plain pi pass-through: any flag that isn't a Spectral subcommand is forwarded
  verbatim to `pi`.

### Notes

- Backend storage is identity + machine metadata only. Message content, code,
  and model API keys never leave your machine.
- One SQLite database per machine — switching machines in the browser shows a
  different project list, by design.
- Pi auth tokens (Anthropic, OpenAI, Cerebras, Google, custom endpoints) are
  managed by pi itself in `~/.pi/agent/auth.json` and are not read or
  transmitted by Spectral.
