From 00307021c66d309bbf510400b3aae3fade94eede Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Wed, 7 Oct 2026 09:17:43 +0000 Subject: [PATCH] refactor: rename the commands after what they do (#26) --- README.md | 32 +++++++++++++------------- package.json | 17 ++++++-------- src/__tests__/startup.test.ts | 2 +- src/{sessions.tsx => chat-history.tsx} | 2 +- src/chat.tsx | 2 +- src/lib/constants.ts | 2 +- src/lib/hooks/useChat.ts | 2 +- src/lib/hooks/usePreferences.ts | 2 +- src/lib/preferences.ts | 6 ++--- src/lib/startup.ts | 2 +- src/lib/vision.ts | 2 +- src/{models.tsx => manage-models.tsx} | 4 ++-- src/setup.tsx | 6 ++--- src/snip.tsx | 4 ++-- 14 files changed, 41 insertions(+), 44 deletions(-) rename src/{sessions.tsx => chat-history.tsx} (99%) rename src/{models.tsx => manage-models.tsx} (99%) diff --git a/README.md b/README.md index bbe5a20..607abc0 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,12 @@ npm run build Setup is two prompts on first launch, then one command you can revisit any time. 1. **The API key.** Vicinae prompts for required preferences before a command runs, so the very first launch asks for your Ollama API key. -2. **The model.** The next prompt asks which model to use. Type it exactly, e.g. `deepseek-v4.1-flash:cloud` or `gpt-oss:120b`. Typing it by hand is the fragile part, so run **Ollama Models** afterwards and pick the real one from the list. -3. **`Set Up Lam Agent`** verifies the connection, lists every model your key can actually reach, and reports which one is in use and where that came from. +2. **The model.** The next prompt asks which model to use. Type it exactly, e.g. `deepseek-v4.1-flash:cloud` or `gpt-oss:120b`. Typing it by hand is the fragile part, so run **Manage Models** afterwards and pick the real one from the list. +3. **`Setup & Status`** verifies the connection, lists every model your key can actually reach, and reports which one is in use and where that came from. -To change the model later, run **Ollama Models** and pick one, or edit the **Model** preference directly. +To change the model later, run **Manage Models** and pick one, or edit the **Model** preference directly. -> An extension can read preferences but not write them, which is why a pick made in **Ollama Models** is stored by the extension and layered over the preference rather than saved into it. See [Models](#models) for how the two are reconciled, and for what a reset does. +> An extension can read preferences but not write them, which is why a pick made in **Manage Models** is stored by the extension and layered over the preference rather than saved into it. See [Models](#models) for how the two are reconciled, and for what a reset does. ## Keyboard shortcuts @@ -83,7 +83,7 @@ To change the model later, run **Ollama Models** and pick one, or edit the **Mod | `↩` with an empty bar | Open the highlighted conversation | | `⌘ N` | New chat | | `⌘ ⇧ S` | Snip a region and attach it to this chat (only with a vision model) | -| `⌘ ⇧ ⌫` | Delete the highlighted conversation (also in **Chat Sessions**) | +| `⌘ ⇧ ⌫` | Delete the highlighted conversation (also in **Chat History**) | Prompts are single line. An earlier `⌘ ↩` multi-line composer was removed. See [Architecture](#architecture) for why a `Form.TextArea` cannot be laid out cleanly in a launcher. The `Tab` shortcut went with it: the left column *is* the conversation switcher now, so an accessory dropdown would only duplicate it. @@ -118,16 +118,16 @@ vicinae 'vicinae://launch/lam-agent/chat?fallbackText=summarise%20this' | --- | --- | | **Chat with Lam** | The chat surface. Bind this to your global shortcut. | | **Snip to Lam** | Capture a region and start a new chat about it. A `no-view` command, so the launcher window closes before the selector appears. Bind this to a second shortcut for one-key "snip and ask". | -| **Chat Sessions** | Browse, reopen, and delete past conversations. | -| **Ollama Models** | Browse the models the configured host can reach, pick the one Lam uses, and pull or delete models on a server you run. See [Models](#models). | -| **Set Up Lam Agent** | Verify the connection, list available models, check whether the selected model can see images, and clear out chats or unattached photos. | +| **Chat History** | Browse, reopen, and delete past conversations. | +| **Manage Models** | Browse the models the configured host can reach, pick the one Lam uses, and pull or delete models on a server you run. See [Models](#models). | +| **Setup & Status** | Verify the connection, list available models, check whether the selected model can see images, and clear out chats or unattached photos. | ## Configuration | Preference | Default | Notes | | --- | --- | --- | | **Ollama API Key** | None | Required. Stored by Vicinae as a password preference. | -| **Model** | None | Required. Any model your key can reach. **Ollama Models** can pick one for you, and a pick takes precedence over this value until you reset it from the setup screen. | +| **Model** | None | Required. Any model your key can reach. **Manage Models** can pick one for you, and a pick takes precedence over this value until you reset it from the setup screen. | | **Ollama Host** | `https://ollama.com` | Set to `http://localhost:11434` for a local server. | | **Location** | None | The place Lam should treat as yours, for example `Cebu City, Philippines`. Optional. The only exact location available, and the only one that stops anything being inferred. | | **IP Geolocation** | off | Off by default. When on, and only when **Location** is empty, your IP is sent to ipwho.is to resolve a rough area. | @@ -145,7 +145,7 @@ vicinae 'vicinae://launch/lam-agent/chat?fallbackText=summarise%20this' ## Models -**Ollama Models** lists everything the configured host can reach, filtered by the search bar. Each row shows the parameter count and how much disk it takes, with tags for the model in use and for anything that reports image input. Picking one writes it to LocalStorage and `loadPreferences` layers it over the `Model` preference, so it takes effect on the next question with no restart. +**Manage Models** lists everything the configured host can reach, filtered by the search bar. Each row shows the parameter count and how much disk it takes, with tags for the model in use and for anything that reports image input. Picking one writes it to LocalStorage and `loadPreferences` layers it over the `Model` preference, so it takes effect on the next question with no restart. | Action | Notes | | --- | --- | @@ -211,7 +211,7 @@ If you turn on **IP Geolocation**, and only then, `get_location` resolves a roug **It is coarser than "city-level" suggests, and that is worth knowing before trusting it.** An IP address identifies the point where the traffic left the ISP, not the person. Measured from one machine on Globe Telecom, an IPv4 lookup resolved to Cebu City and an IPv6 lookup moments later resolved to Talisay in Calabarzon, roughly 500km apart. If you want a place you can rely on, set **Location** and turn this off. -The answer is cached for 6 hours rather than looked up per question, because an area does not change between questions and every lookup hands over another copy of your address. `Set Up Lam Agent` clears that cache, and the capability cache, when you re-test. +The answer is cached for 6 hours rather than looked up per question, because an area does not change between questions and every lookup hands over another copy of your address. `Setup & Status` clears that cache, and the capability cache, when you re-test. This extension does not use GPS. That would mean CoreLocation on macOS, which has no command-line interface, or GeoClue on Linux, whose usual provider resolves nearby Wi-Fi networks through a Mozilla service anyway. Neither is worth it for "is it late where you are". @@ -287,7 +287,7 @@ The index entry for a conversation carries its image paths (`SessionSummary.atta ### Doing it by hand -**Set Up Lam Agent** has a Maintenance section with the same two jobs as explicit actions: +**Setup & Status** has a Maintenance section with the same two jobs as explicit actions: | Action | What it deletes | | --- | --- | @@ -352,8 +352,8 @@ The list shows the sources the answer cited, falling back to everything the turn src/ chat.tsx # "chat" command entrypoint, thin wrapper snip.tsx # "snip" command entrypoint, no-view: captures then hands over - models.tsx # "models" command: browse, pick, pull, delete - sessions.tsx # browse, reopen, delete conversations + manage-models.tsx # "manage-models" command: browse, pick, pull, delete + chat-history.tsx # "chat-history" command: browse, reopen, delete conversations setup.tsx # connection check + live model list lib/ ollama.ts # the only file that talks HTTP @@ -423,7 +423,7 @@ The rendered height stays capped at 200px whatever is asked for (`Math.min(_rawH **The logic layer has no Vicinae imports.** `agent.ts`, `ollama.ts`, `tools.ts` and friends are plain TypeScript, which is why they unit test without a runtime. A test enforces this. -**Preferences have exactly one reader.** `loadPreferences` applies the model chosen in **Ollama Models** on top of what Vicinae holds, and it is asynchronous because that pick lives in LocalStorage. `getLamPreferences` is deliberately not exported, because a second, synchronous reader would look correct and silently ignore the pick. Views take preferences through `hooks/usePreferences`, and the chat view is split in two so that `useChat` is only ever created once the model is known, rather than being pinned to whatever the preference said a moment earlier. +**Preferences have exactly one reader.** `loadPreferences` applies the model chosen in **Manage Models** on top of what Vicinae holds, and it is asynchronous because that pick lives in LocalStorage. `getLamPreferences` is deliberately not exported, because a second, synchronous reader would look correct and silently ignore the pick. Views take preferences through `hooks/usePreferences`, and the chat view is split in two so that `useChat` is only ever created once the model is known, rather than being pinned to whatever the preference said a moment earlier. **Sessions are capped twice over**, once per turn and once per session, because `LocalStorage` has no documented ceiling. @@ -441,7 +441,7 @@ The hand-off goes through `snip` specifically rather than straight to `chat` on **An attached image is sent once and never replayed.** It is the largest thing a request can carry, so `buildMessages` attaches it to the question it belongs to and drops it afterwards, exactly as it already drops tool output. The cost is that a follow-up about a screenshot needs the image attached again; the alternative is re-sending every screenshot in the conversation on every turn. Only the path and mime type go into storage: `MAX_STORED_CHARS_PER_TURN` exists so one turn cannot dominate `LocalStorage`, and base64 would blow past it on a single 1080p capture. -**Whether the model can see is a tri-state, and the prompt gets the whole answer.** `canSeeImages` stays fail-closed, because offering the snip action to a model that cannot read would waste the selection gesture. But the system prompt needs `"unknown"` kept apart from `"no"`: telling a model it cannot see when the check merely failed is a false claim about the user's setup, and telling it nothing is what made it reason about its own capability instead of describing the image it had been sent. A model name that does not exist on the server is the common way to end up at `"unknown"`, which is one more reason the Models command picks names from the list rather than taking them typed. +**Whether the model can see is a tri-state, and the prompt gets the whole answer.** `canSeeImages` stays fail-closed, because offering the snip action to a model that cannot read would waste the selection gesture. But the system prompt needs `"unknown"` kept apart from `"no"`: telling a model it cannot see when the check merely failed is a false claim about the user's setup, and telling it nothing is what made it reason about its own capability instead of describing the image it had been sent. A model name that does not exist on the server is the common way to end up at `"unknown"`, which is one more reason the Manage Models command picks names from the list rather than taking them typed. ## Development diff --git a/package.json b/package.json index 990cda7..1e4b05f 100644 --- a/package.json +++ b/package.json @@ -11,34 +11,31 @@ { "name": "chat", "title": "Chat with Lam", - "subtitle": "Lam Agent", "description": "Ask Lam anything. Answers stream in as they are generated, your session history is kept, and Lam can search the web when it needs current information.", "mode": "view" }, { "name": "snip", "title": "Snip to Lam", - "subtitle": "Lam Agent", "description": "Capture a region of the screen and start a new chat about it. The launcher window closes first so it cannot end up in the shot. Offered only when the model in preferences supports image input.", "mode": "no-view" }, { - "name": "sessions", - "title": "Chat Sessions", + "name": "chat-history", + "title": "Chat History", "description": "Browse, reopen, and clean up your past conversations with Lam.", "mode": "view" }, { - "name": "models", - "title": "Ollama Models", - "subtitle": "Lam Agent", + "name": "manage-models", + "title": "Manage Models", "description": "Browse the models your Ollama host can reach, pick the one Lam should use, unload one that is holding memory, and pull or delete models on a server you run. A pick takes effect immediately; a pull keeps downloading until it finishes, so leave the launcher open.", "mode": "view" }, { "name": "setup", - "title": "Set Up Lam Agent", - "description": "Check your Ollama connection, pick the model Lam should use, and tune how Lam answers.", + "title": "Setup & Status", + "description": "Check your Ollama connection, pick the model Lam should use, tune how Lam answers, and clear out old chats or screenshots you never sent.", "mode": "view" } ], @@ -53,7 +50,7 @@ { "name": "model", "title": "Model", - "description": "The model Lam should use, for example deepseek-v4.1-flash:cloud or gpt-oss:120b. Run \"Set Up Lam Agent\" to pick from the models your key can access.", + "description": "The model Lam should use, for example deepseek-v4.1-flash:cloud or gpt-oss:120b. Run \"Setup & Status\" to pick from the models your key can access.", "type": "textfield", "required": true }, diff --git a/src/__tests__/startup.test.ts b/src/__tests__/startup.test.ts index 2caba86..147ffee 100644 --- a/src/__tests__/startup.test.ts +++ b/src/__tests__/startup.test.ts @@ -191,7 +191,7 @@ describe("resolveStartup", () => { }); it("does not resume when a session was pushed in by hand", async () => { - // The sessions command already knows which conversation to show. + // The Chat History command already knows which conversation to show. const p = ports(); await resolveStartup(input({ hasInitialSession: true }), p); diff --git a/src/sessions.tsx b/src/chat-history.tsx similarity index 99% rename from src/sessions.tsx rename to src/chat-history.tsx index f8b092f..66a57cb 100644 --- a/src/sessions.tsx +++ b/src/chat-history.tsx @@ -78,7 +78,7 @@ export default function SessionsCommand() { return ( {loaded && items.length === 0 ? ( diff --git a/src/chat.tsx b/src/chat.tsx index 848f8a2..15f2363 100644 --- a/src/chat.tsx +++ b/src/chat.tsx @@ -6,7 +6,7 @@ import { readSnipContext } from "./lib/snip"; * Entrypoint for the "chat" command. * * Deliberately thin: everything lives in {@link ChatView} so the same view can - * also be pushed onto the navigation stack from the sessions command. + * also be pushed onto the navigation stack from the Chat History command. * * Assign this command a global shortcut in Vicinae's settings to get the * launcher-and-start-typing behaviour. `fallbackText` is populated when the diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 738548a..8ae6bdc 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -16,7 +16,7 @@ export const STORAGE_INDEX_KEY = "lam.sessions.index"; export const STORAGE_SESSION_PREFIX = "lam.session."; /** - * The model picked in the Models command. + * The model picked in the Manage Models command. * * Separate from the `model` preference because an extension cannot write its own * preferences: Vicinae reads them once at startup and holds them in memory, so diff --git a/src/lib/hooks/useChat.ts b/src/lib/hooks/useChat.ts index 6873233..0a9be5f 100644 --- a/src/lib/hooks/useChat.ts +++ b/src/lib/hooks/useChat.ts @@ -36,7 +36,7 @@ const FLUSH_INTERVAL_MS = 60; export interface UseChatOptions { preferences: ResolvedPreferences; - /** Restores an existing conversation, e.g. when reopening from the sessions command. */ + /** Restores an existing conversation, e.g. when reopening from the Chat History command. */ initialSession?: Session; /** * Whether the model can read images, for the system prompt. diff --git a/src/lib/hooks/usePreferences.ts b/src/lib/hooks/usePreferences.ts index f3c3dc0..a3923fb 100644 --- a/src/lib/hooks/usePreferences.ts +++ b/src/lib/hooks/usePreferences.ts @@ -5,7 +5,7 @@ import { loadPreferences, type ResolvedPreferences } from "../preferences"; * The preferences a view should use, or `undefined` until they have loaded. * * Every reader goes through `loadPreferences`, which layers the model chosen in - * the Models command on top of what Vicinae holds, and that read is asynchronous + * the Manage Models command on top of what Vicinae holds, and that read is asynchronous * because the selection lives in LocalStorage. A hook rather than a synchronous * call so that the one reader is also the only way to get preferences into a * component, and a view that forgets to handle the loading case fails to compile diff --git a/src/lib/preferences.ts b/src/lib/preferences.ts index 44a7f90..2531d33 100644 --- a/src/lib/preferences.ts +++ b/src/lib/preferences.ts @@ -39,7 +39,7 @@ export interface ResolvedPreferences { model: string; /** * Whether {@link ResolvedPreferences.model} came from the preference or from a - * pick in the Models command. + * pick in the Manage Models command. * * Carried rather than inferred, because the two disagree silently otherwise: * the setup screen would show one model while the chat used another, and the @@ -244,14 +244,14 @@ export function resolvePreferences(raw: LamPreferences): ResolvedPreferences { /** * Vicinae's own preferences, resolved and normalised. Not exported: everything * outside this module should go through {@link loadPreferences}, which also - * applies the model chosen in the Models command. + * applies the model chosen in the Manage Models command. */ function getLamPreferences(): ResolvedPreferences { return resolvePreferences(getPreferenceValues()); } /** - * The model chosen in the Models command, if there is one. + * The model chosen in the Manage Models command, if there is one. * * Never throws. Storage that cannot be read means no selection rather than a * broken command, because the fallback (the preference) is always a valid diff --git a/src/lib/startup.ts b/src/lib/startup.ts index dd11776..92fbd9e 100644 --- a/src/lib/startup.ts +++ b/src/lib/startup.ts @@ -18,7 +18,7 @@ import { errorMessage } from "./utils"; */ export interface StartupInput { - /** A conversation already in hand, which is how the sessions command pushes this view. */ + /** A conversation already in hand, which is how the Chat History command pushes this view. */ hasInitialSession: boolean; /** A conversation to reopen by id, handed over by the snip command. */ initialSessionId?: string; diff --git a/src/lib/vision.ts b/src/lib/vision.ts index d785d06..97db189 100644 --- a/src/lib/vision.ts +++ b/src/lib/vision.ts @@ -66,7 +66,7 @@ export async function visionSupport(check: VisionCheck): Promise * Drops every cached answer. * * A model can gain vision without its name changing (a re-pull, a different - * tag on the same account), so `Set Up Lam Agent` calls this rather than + * tag on the same account), so `Setup & Status` calls this rather than * leaving the user pinned to a stale "no". */ export async function forgetVisionSupport(): Promise { diff --git a/src/models.tsx b/src/manage-models.tsx similarity index 99% rename from src/models.tsx rename to src/manage-models.tsx index e2d8cf5..69d41d4 100644 --- a/src/models.tsx +++ b/src/manage-models.tsx @@ -67,7 +67,7 @@ export default function ModelsCommand() { // marked up relative to it: which row is current, and whether picking one // would change anything. if (!preferences) { - return ; + return ; } return ; @@ -256,7 +256,7 @@ function ModelsScreen({ preferences, reloadPreferences }: ModelsScreenProps) { return ( diff --git a/src/setup.tsx b/src/setup.tsx index 32d948a..6418ef8 100644 --- a/src/setup.tsx +++ b/src/setup.tsx @@ -56,11 +56,11 @@ export default function SetupCommand() { // The screen does nothing until the model is known, because half of what it // reports is about that model: whether it can see images, and whether it is the - // one from preferences or from the Models command. Rendering it with the + // one from preferences or from the Manage Models command. Rendering it with the // preference value and correcting it a moment later would flash a wrong answer // at the user. if (!preferences) { - return ; + return ; } return ; @@ -207,7 +207,7 @@ function SetupScreen({ preferences, reloadPreferences }: SetupScreenProps) { return ( diff --git a/src/snip.tsx b/src/snip.tsx index 9756ffe..0889668 100644 --- a/src/snip.tsx +++ b/src/snip.tsx @@ -34,7 +34,7 @@ function settleWindow(): Promise { * Bind it to a global shortcut for one-key "snip and ask". */ export default async function SnipCommand(props: LaunchProps) { - // Awaited because the model can come from a pick in the Models command, which + // Awaited because the model can come from a pick in the Manage Models command, which // lives in storage. The vision check below is about whichever model is in use, // so it has to be the resolved one. const preferences = await loadPreferences(); @@ -59,7 +59,7 @@ export default async function SnipCommand(props: LaunchProps) { await showHUD( vision === "no" ? `${preferences.model} cannot see images. Pick a vision model in preferences.` - : `Could not check whether ${preferences.model} can see images. Run Set Up Lam Agent.`, + : `Could not check whether ${preferences.model} can see images. Run Setup & Status.`, ); return; }