cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Document the config file, themes and API key setup

Dami committed 1429607
New docs/themes.md; docs/jev.md's setup is now set-key, the credential
store and jev-api-key-command; architecture explains the config
registry, the credential store choice and scheme rendering; extending
lists options and roles as registries; testing and UX cover config
isolation and full-width bars.
commit
1429607a4a37a3b6429d93ee60906a4eb42633a0
parent
710b5e6b52f1f4666a35faf4cc4f4c1e9bf68fcc
author
Dami <dami.etoile@gmail.com>
11 files changed, +261 -20
MREADME.md8++-
Mdocs/README.md4+-
Mdocs/UX.md7++-
Mdocs/architecture.md66++++++++++++++++++-
Mdocs/config.md2+-
Mdocs/extending.md13+++-
Mdocs/jev.md64++++++++++++++++--
Mdocs/observability.md4+
Mdocs/testing.md7++-
Adocs/themes.md104++++++++++++++++++++++++++++++
Minternal/config/registry.go2+-
M README.md +7 -1
M docs/README.md +3 -1
M docs/UX.md +6 -1
M docs/architecture.md +63 -3
@@ -4,17 +4,19 @@
44 Lip Gloss v2.55 66 ```7-cmd/012 entry point: flags, JEV setup, opening or importing a file7+cmd/012 entry point: flags and config, 012 config, JEV setup, opening or importing a file8+internal/config the config file and the registry of options89 internal/sheet the engine: cells, evaluation, functions, recalculation, undo, files910 internal/formula the formula language: references, lexer, parser, printer, rewriting1011 internal/numfmt number formats, rounding, General, date serials1112 internal/fileio import and export: CSV, TSV, XLSX, SQLite, Parquet, Lotus .wk11213 internal/chart chart layout, text rendering and kitty image encoding13-internal/jev JEV configuration, answer cache and TypeSafe client14+internal/jev the API key's resolution, answer cache and TypeSafe client15+internal/keyring the OS credential store the API key lives in1416 internal/telemetry opt-in JSON log and OTLP export of spans, events and frame stats1517 internal/stress synthetic worst-case sheets for the -tags stress benchmarks1618 internal/ui the Bubble Tea model: modes, menus, overlays, rendering17- theme style roles and the widgets drawn with them (frames, key chips)19+ theme style roles, color schemes and the widgets drawn with them (frames, key chips)1820 rowtext laying out a row of cell text across the columns on screen1921 formula reading the formula being typed (F4, the word and call at the caret)2022 e2e/ end-to-end tests through libghostty (separate module, cgo)
@@ -203,6 +205,64 @@ saved with sheets. Work is bounded by the chart's size, not its data: only
203205 the categories that fit are drawn, pie slices are found by binary search,204206 and a pie image supersamples only pixels on a slice edge or the rim.205207 208+## Configuration and secrets209+210+`internal/config` reads one Ghostty-style file (`key = value`, `#`211+comments, `config-file` includes) from `$XDG_CONFIG_HOME/012` or the OS212+config directory; the same directory holds `themes/` and 012 serve's host213+key. Every option is one entry in `config.Options`: name, type, default,214+environment variables, flag, whether Reload config applies it, and a215+description. Parsing and validation, the flags `cmd/012` accepts, `012216+config`'s listing and default file, and [config.md](config.md)'s217+reference (checked by a test) all come from that table. Values are layered defaults < file < environment < flags, each218+remembering its source; problems are warnings, never fatal. The UI gets a219+`ui.Settings` (the config, a reload function, the credential store, a JEV220+connector) through `Model.Configure`, so tests hand it a temporary config221+and a store in memory.222+223+The JEV API key is never in the config. `jev.ResolveKey` takes it from224+`TYPESAFE_API_KEY`, then the credential store, then225+`jev-api-key-command` (split into words and run without a shell, with a226+timeout, only when a sheet first asks JEV something). The base URL comes227+from the config or environment only and must be https unless it's228+loopback, so a file next to a sheet can't redirect the key.229+230+`internal/keyring` is the credential store, chosen for the least231+dependency weight that is still correct on each OS, with `CGO_ENABLED=0`:232+233+| OS | Store | How |234+|---|---|---|235+| macOS | Keychain | `/usr/bin/security`. Writing uses `security -i`, which reads its command from stdin, with the key hex-encoded (`-X`), so it's never in any process's arguments. |236+| Linux, BSD | Secret Service (GNOME Keyring, KWallet, KeePassXC) | libsecret's `secret-tool`, which reads the secret to store from stdin. |237+| Windows | Credential Manager | `CredReadW`, `CredWriteW`, `CredDeleteW` from advapi32 through `golang.org/x/sys/windows`, already in the module graph. |238+239+The alternatives were a keyring module (zalando/go-keyring, which does240+the same on macOS but brings godbus for Linux, or 99designs/keyring,241+which brings several backends and needs cgo for the macOS Keychain) or242+cgo bindings to Security.framework and libsecret, which would end the243+pure-Go build. Two244+small command-line programs and one system DLL cover the three OSes with245+no new module. The stores share a `Store` interface; tests use a246+recording runner or `keyring.Memory`, and the e2e binary is built with247+`-tags fakekeyring`, whose store is a file under `XDG_CONFIG_HOME`, so no248+test reaches a real keychain.249+250+## Themes251+252+`theme.Theme` is a set of style roles, each defined on the 16 ANSI colors253+and the default background and foreground (`theme.New`). The terminal254+theme sends them as ANSI numbers, so the terminal's palette applies. A255+color scheme (`theme.Palette`: VHS's embedded catalog, or a Ghostty,256+kitty or VHS JSON file in `themes/`) is drawn by `theme.FromPalette`,257+which maps each ANSI color to the scheme's, adds full-width bands for the258+bars and a screen background, and corrects contrast (see259+[themes.md](themes.md)). Bands are applied by `theme.Fill`, one pass over260+a rendered line's escape sequences that sets the band's colors at the261+start and after each reset and pads to the width; `View` fills the bar262+lines before overlays are composited and the whole screen after, so bars263+reach the edge under menus too. Charts take their colors from the scheme264+when there is one, and from the terminal's palette otherwise.265+206266 ## Telemetry207267 208268 `internal/telemetry` is off unless asked for. When on, spans, events and
M docs/config.md +1 -1
M docs/extending.md +9 -4
@@ -18,10 +18,15 @@ shows it is derived from the table rather than listing it again.
1818 | `ui.command` | an action: id, title, description, run, enabled, checked | key bindings, menu bar, context menus, palette, shortcuts help |1919 | `fileio` formats (`formats.go`) | a format: name, extensions, labels, traits, importer, exporter | `Import`, `Export`, detection, import picker, File > Download, command line |2020 | `chart.types`, with `sheet.ChartTypes` | a type: name and order (sheet, saved in files), a layout drawing text and image (chart) | `chart.Draw`, `chart.Image`, chart editor, Insert > Chart |21-| theme roles | a role: dark and light styles on the 16 ANSI colors | every style in the UI |22-23-Adding a function, command, format or chart type means adding an entry24-(and its file), not editing switch statements elsewhere. When a list has to21+| theme roles (`theme.Theme`) | a role: dark and light styles on the 16 ANSI colors, with a contrast minimum for schemes (`minContrast`) | every style in the UI, drawn in the terminal's palette or any color scheme (`FromPalette`); `TestEveryThemeReadable` checks each role under all 349 schemes |22+| `config.Options` | an option: name, type, default, environment variables, flag, live or not, description, check | parsing and warnings, flags, `012 config` and its default file, [config.md](config.md), Reload config |23+24+Adding a function, command, format, chart type, option or role means25+adding an entry (and its file), not editing switch statements elsewhere.26+A new process-wide setting is a `config.Options` entry read with27+`Config.String`, `Bool`, `Int` or `Duration`; one that belongs to a28+workbook goes in the file instead (pattern 9). A new role goes in29+`theme.Theme` with its ANSI colors in `New`; schemes pick it up. When a list has to2530 be kept in sync by hand, turn it into a registry.2631 2732 ### 2. Components own their state
M docs/jev.md +57 -7
@@ -16,11 +16,60 @@ background (cells show `Loading…`), are cached by question, and the context
1616 line shows the confidence for the selected cell. Data > Ask JEV again1717 re-asks the selection.1818 19-Set `TYPESAFE_API_KEY` in the environment or a `.env` file in the current20-directory or next to the sheet; `TYPESAFE_BASE_URL` and21-`TYPESAFE_DEFAULT_MODEL` are optional. Without a key the functions show22-`#N/A` and say why. `JEV_LIVE_TEST=1 go test ./internal/jev -run TestLive`23-checks the real service.19+Without a key the functions show `#N/A` and say how to add one.20+`JEV_LIVE_TEST=1 go test ./internal/jev -run TestLive` checks the real21+service.22+23+## Setup24+25+The API key never goes in a file 012 reads or on a command line. Store it26+once in your operating system's credential store:27+28+```sh29+012 config set-key # asks for the key without echoing it30+op read op://Private/TypeSafe/credential | 012 config set-key # or pipe it in31+012 config delete-key # remove it32+```33+34+or in the app, File > Settings > JEV API key, which asks on the context35+line with the key masked and turns JEV on at once. `012 config` says where36+the key would come from, without showing it.37+38+012 looks for the key in this order:39+40+1. **`TYPESAFE_API_KEY`** in the environment, for CI, containers and41+ one-off runs.42+2. **The credential store**: the macOS Keychain, the Windows Credential43+ Manager, or the Secret Service on Linux and the BSDs (GNOME Keyring,44+ KWallet, KeePassXC; this needs `secret-tool`, in the `libsecret-tools`45+ package on Debian and Ubuntu). The item is service `012`, account46+ `jev-api-key`. `jev-credential-store = false` in the config skips it.47+3. **`jev-api-key-command`** in the config: a command that prints the key,48+ for password managers:49+50+ ```51+ jev-api-key-command = op read op://Private/TypeSafe/credential52+ jev-api-key-command = pass show typesafe53+ jev-api-key-command = sh -c 'gpg -dq ~/.secrets/typesafe.gpg | head -1'54+ ```55+56+ It runs the first time a sheet asks JEV something, not at every57+ start, for at most 10 seconds, and the first line it prints is the key.58+ It runs without a shell: the value is split into words, with quotes59+ grouping words and a backslash escaping the next character, and60+ nothing is expanded (`$HOME`, `~`, `*`, pipes and `;` are passed on61+ literally). For a pipeline, write `sh -c '...'` yourself.62+63+The service's address and model come from the config file or the64+environment only: `jev-base-url` (or `TYPESAFE_BASE_URL`), which must be65+https unless it's this machine, and `jev-model` (or66+`TYPESAFE_DEFAULT_MODEL`). See [config.md](config.md).67+68+**`.env` files are no longer read.** A `.env` next to a downloaded sheet69+could set `TYPESAFE_BASE_URL` and send your key to someone else's server.70+If the directory 012 starts in has a `.env` with `TYPESAFE_API_KEY`, the71+context line suggests `012 config set-key`; 012 never reads the key from72+it.2473 2574 ## How requests behave2675
@@ -33,7 +82,8 @@ checks the real service.
3382 than two labels, more than the service's 255 labels or 10 score levels)3483 show as errors at once and are never sent.3584 - A failed request shows `#ERROR!`, and the context line says why.36-- 012 never logs or saves the API key; `.env` files are read for37- `TYPESAFE_*` settings only.85+- 012 never logs, shows or saves the API key anywhere but the credential86+ store, and never passes it as a command-line argument: the macOS87+ Keychain and the Secret Service get it on stdin.3888 3989 The client is [typesafe-go](https://github.com/FelineStateMachine/typesafe-go).
M docs/observability.md +4 -0
M docs/testing.md +6 -1
A docs/themes.md +104 -0
@@ -0,0 +1,104 @@
1+# Themes2+3+A theme is a terminal color scheme: the 16 ANSI colors plus a background,4+a foreground and a selection color, the same shape Ghostty, kitty, iTerm2,5+Alacritty and VHS themes have. 012 has no theme format of its own to learn:6+any scheme works, and so does any Ghostty theme file.7+8+```9+# ~/.config/012/config10+theme = terminal # the default11+theme = Dracula # one scheme12+theme = light:Catppuccin Latte,dark:Catppuccin Mocha # follow the terminal13+```14+15+- **`terminal`** (the default) draws with the terminal's own palette, as16+ ANSI color numbers, with a dark and a light variant picked from the17+ terminal's background. It looks the way your terminal is set up.18+- **Any other name** is a color scheme. 012 draws its roles in the19+ scheme's colors (true color), fills the whole screen with the scheme's20+ background, and draws the menu bar, the column headers and the status21+ line as solid bands, in the manner of 1-2-3 and classic terminal22+ programs. The terminal's own palette is never changed.23+- **`light:A,dark:B`** picks A on a light terminal and B on a dark one,24+ and switches when the terminal does (for example with the system's25+ appearance, in terminals that report it).26+27+File > Settings > Theme opens a picker: type to search the names, or28+"dark" or "light"; the highlighted theme is drawn live, Enter keeps it and29+writes `theme = ...` to the config file, and Esc goes back to the theme you30+had. With a `light:`/`dark:` pair, the picker changes the one in use.31+`012 config themes` lists every theme, with a `*` on the current one.32+33+## Built-in schemes34+35+012 embeds the 348 schemes of [VHS](https://github.com/charmbracelet/vhs)36+(Dracula, Nord, Gruvbox, Catppuccin, Solarized, Tokyo Night, One Dark,37+Rose Pine and hundreds more), so names are the ones VHS's `Set Theme`38+takes, plus **1-2-3 Classic**: CGA colors on a blue screen. Names match39+without regard to case, and then without regard to spaces, hyphens and40+underscores, so `tokyo-night` finds `TokyoNight`.41+42+## Your own schemes43+44+Put a file in the `themes` directory next to the config file45+(`~/.config/012/themes/` on Linux; `012 config path` shows where the46+config is) and use its file name as the theme. A file with a built-in's47+name replaces it. Three formats are read:48+49+- **Ghostty** (copy any Ghostty theme unchanged):50+51+ ```52+ palette = 0=#1d1f2153+ palette = 1=#cc666654+ # ... through 1555+ background = #1d1f2156+ foreground = #c5c8c657+ selection-background = #373b4158+ ```59+60+- **kitty**: `color0 #1d1f21` through `color15`, `background`,61+ `foreground` and `selection_background`.62+- **VHS JSON**: one scheme or a list, with `black` ... `brightWhite`,63+ `background`, `foreground`, `selection` and `meta.isDark` (a `.json`64+ file; `purple` and `selectionBackground` are accepted too).65+66+A scheme needs a background and a foreground; ANSI colors it leaves out67+are xterm's. Other keys (fonts, cursor colors) are ignored.68+69+## How roles get their colors70+71+Every role in `internal/ui/theme` (the cell pointer, selection, headers,72+menus, errors, links, chart series and so on) is defined on the ANSI73+colors and the background and foreground: the pointer is black on cyan,74+headers are bright white on bright black (black on white on light75+terminals), errors are red. The terminal theme sends those color numbers;76+a scheme replaces each with its own color. Then, so every scheme reads77+well:78+79+- the selection uses the scheme's selection color, when it has one;80+- the menu bar and status line are the background moved 12% toward the81+ text color; the column header row is filled with the header color;82+- a background role too close to the screen's background is moved apart;83+- text below its contrast minimum against its background (WCAG 4.5:1 for84+ text, 3:1 for hints, lines and chart axes, 2:1 for unavailable items)85+ is moved toward the scheme's text color, or toward black or white, just86+ far enough to reach it.87+88+A test runs every built-in scheme through this and checks those minimums89+for cell text, text on the bars, the selection, the headers and every90+other role (`TestEveryThemeReadable`). Charts, as text and as kitty91+images, use the scheme's colors too.92+93+The roles for the bars are `MenuBarRow`, `FormulaBarRow`, `ContextRow`,94+`ColumnHeaderRow` and `StatusBarRow` (a background and a text color for95+the whole line, out to the terminal's edge, under overlays too), with96+`RowHeader` for the row numbers and `Screen` for everything else. The97+terminal theme leaves them empty, so it looks as it always has.98+99+## Credits100+101+The built-in schemes are VHS's `themes.json` (MIT License, Copyright (c)102+2022-2023 Charmbracelet, Inc.; see [NOTICE](../NOTICE)), which credits103+each scheme's authors in its `meta.credits`. The contrast correction is104+adapted from the theme package of puzzletea, by the same author.
M internal/config/registry.go +1 -1