### Install and Start oh-my-opencode-dashboard Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Install the dashboard globally and start it with specified project and port. Alternatively, use `bunx` for direct execution. Ensure project sources are registered if using SQLite. ```bash bun add -g oh-my-opencode-dashboard oh-my-opencode-dashboard --project /path/to/your/project --port 51234 ``` ```bash bunx oh-my-opencode-dashboard@latest --project /path/to/your/project ``` ```bash bunx oh-my-opencode-dashboard@latest add --name "My Project" --project /path/to/your/project ``` ```bash OMO_DASHBOARD_HOST=0.0.0.0 bunx oh-my-opencode-dashboard@latest ``` -------------------------------- ### Start Oh My OpenCode Dashboard Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Starts the dashboard application. Ensure you have registered your project as a source first. ```bash bunx oh-my-opencode-dashboard@latest ``` -------------------------------- ### Start Production Server Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Starts the production server, serving the built UI and API from a single port. Requires specifying the project path. ```bash # Start production server (serves built UI + API from a single port) bun run start -- --project /absolute/path/to/your/project ``` -------------------------------- ### Install Dependencies Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Installs project dependencies using Bun. ```bash # Install dependencies bun install ``` -------------------------------- ### Build and Run Production Server Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Builds the production-ready application and then starts the production server. Use the `--project` flag to specify the project root. ```bash bun run build bun run start -- --project /absolute/path/to/your/project ``` -------------------------------- ### Install from Source with Bun Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Installs project dependencies from source using Bun. This is typically done when developing or contributing to the project. ```bash bun install ``` -------------------------------- ### Run Globally Installed Dashboard Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Executes the globally installed dashboard. This command assumes you have already installed it using `bun add -g`. ```bash oh-my-opencode-dashboard ``` -------------------------------- ### Run Development Server Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Starts the development server for both the API and the UI. Use the `--project` flag to specify the project root for plan lookup and session filtering. ```bash bun run dev -- --project /absolute/path/to/your/project ``` -------------------------------- ### Run Development Mode Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Starts the development server with hot reloading for both the API and Vite UI. Requires specifying the project path. ```bash # Run in development mode (API + Vite UI dev server with hot reload) bun run dev -- --project /absolute/path/to/your/project ``` -------------------------------- ### Run Production Server with Host Option Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Starts the production server and exposes it on all network interfaces using the `--host 0.0.0.0` CLI option. Use `0.0.0.0` only on trusted networks. ```bash bun run start -- --project /absolute/path/to/your/project --host 0.0.0.0 ``` -------------------------------- ### Install Oh My OpenCode Dashboard Globally Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Installs the dashboard globally using npm, allowing you to run the `oh-my-opencode-dashboard` command directly from any directory. ```bash bun add -g oh-my-opencode-dashboard ``` -------------------------------- ### Run Production Server Exposing to All Interfaces Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Starts the production server and binds it to `0.0.0.0` using the `OMO_DASHBOARD_HOST` environment variable. This exposes the server on all network interfaces, useful for containers or remote access on trusted networks. ```bash OMO_DASHBOARD_HOST=0.0.0.0 bun run start -- --project /absolute/path/to/your/project ``` -------------------------------- ### Run Production Server Binding to Localhost Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Starts the production server and explicitly binds it to `localhost` using the `OMO_DASHBOARD_HOST` environment variable. This keeps the server local-only. ```bash OMO_DASHBOARD_HOST=localhost bun run start -- --project /absolute/path/to/your/project ``` -------------------------------- ### GET /api/sources Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Lists all registered project sources from the sources registry, sorted by most-recently-updated. ```APIDOC ### `GET /api/sources` Lists all registered project sources from the sources registry, sorted by most-recently-updated first. ```bash curl http://127.0.0.1:51234/api/sources # → { # "ok": true, # "defaultSourceId": "a3f2...c91", # "sources": [ # { "id": "a3f2...c91", "label": "My Project", "updatedAt": 1720000000000 } # ] # } ``` ``` -------------------------------- ### Get Tool Call Summaries by Session ID Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Retrieve metadata-only tool call summaries for a given session. Capped at 200 messages and 300 tool calls. Returns a 404 if the session is not found. ```bash curl http://127.0.0.1:51234/api/tool-calls/sess_abc123 # → { # "ok": true, # "sessionId": "sess_abc123", # "toolCalls": [ # { "sessionId": "sess_abc123", "messageId": "msg_001", "callId": "call_001", # "tool": "Bash", "status": "completed", "createdAtMs": 1720000010000 }, # { "sessionId": "sess_abc123", "messageId": "msg_002", "callId": "call_002", # "tool": "Read", "status": "completed", "createdAtMs": 1720000020000 } # ], # "caps": { "maxMessages": 200, "maxToolCalls": 300 }, # "truncated": false # } # Session not found → 404 curl http://127.0.0.1:51234/api/tool-calls/nonexistent_session # → { "ok": false, "sessionId": "nonexistent_session", "toolCalls": [] } ``` -------------------------------- ### GET /api/dashboard Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Returns the full `DashboardPayload` snapshot. It can be filtered by `sourceId` to retrieve data for a specific project source. ```APIDOC ### `GET /api/dashboard[?sourceId=]` Returns the full `DashboardPayload` snapshot. Without `sourceId`, uses the default source (first in the registry) or the store passed at construction time. With `sourceId`, resolves the matching `DashboardStore` for that project root. ```bash # Default project curl http://127.0.0.1:51234/api/dashboard # Specific registered source curl "http://127.0.0.1:51234/api/dashboard?sourceId=a3f2c91" # → { # "mainSession": { "agent": "sisyphus", "currentModel": "claude-opus-4-5", "currentTool": "Bash", # "lastUpdatedLabel": "2024-07-04T12:00:00.000Z", "session": "My session", # "sessionId": "sess_abc123", "statusPill": "running tool" }, # "planProgress": { "name": "Implement feature X", "completed": 3, "total": 5, # "path": "/home/user/.sisyphus/plans/plan.md", "statusPill": "in progress", # "steps": [{ "checked": true, "text": "Step 1" }, { "checked": false, "text": "Step 2" }] }, # "backgroundTasks": [{ "id": "call_xyz", "description": "Run tests", "agent": "general-purpose", # "lastModel": "claude-haiku", "status": "running", "toolCalls": 4, # "lastTool": "Bash", "timeline": "2024-07-04T12:00:05Z: 1m30s", "sessionId": "sess_bg1" }], # "timeSeries": { "windowMs": 300000, "bucketMs": 2000, "buckets": 150, ... }, # "tokenUsage": { "rows": [{ "model": "anthropic/claude-opus-4-5", "input": 12000, "output": 3500, # "reasoning": 0, "cacheRead": 800, "cacheWrite": 200, "total": 16500 }], # "totals": { "input": 12000, "output": 3500, ... } }, # "todos": [{ "content": "Fix bug", "status": "pending", "priority": "high", "position": 0 }] # } ``` ``` -------------------------------- ### GET /api/health Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt A simple liveness probe endpoint to check if the API is running. ```APIDOC ### `GET /api/health` Simple liveness probe. ```bash curl http://127.0.0.1:51234/api/health # → {"ok":true} ``` ``` -------------------------------- ### Pick Active Session ID and Get Main Session View Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Use `pickActiveSessionId` to find the best active OpenCode session for a project. `getMainSessionView` then reads session files to determine the current agent, tool, model, and status. Ensure storage roots are correctly configured. ```typescript import { getStorageRoots, pickActiveSessionId, getMainSessionView } from "./src/ingest/session" const storage = getStorageRoots("/home/user/.local/share/opencode/storage") // → { session: "...storage/session", message: "...storage/message", part: "...storage/part" } const sessionId = pickActiveSessionId({ projectRoot: "/home/user/my-project", storage, boulderSessionIds: ["sess_abc123"], // optional, from boulder.json }) // → "sess_abc123" (or null if no session found) const view = getMainSessionView({ projectRoot: "/home/user/my-project", sessionId: "sess_abc123", storage, nowMs: Date.now(), }) // → { // agent: "sisyphus", // currentTool: "Bash", // null if no active tool // currentModel: "claude-opus-4-5", // lastUpdated: 1720000030000, // sessionLabel: "My session title", // status: "running_tool" // | "thinking" | "busy" | "idle" | "unknown" // } ``` -------------------------------- ### Get Dashboard Payload Snapshot Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Fetches the complete `DashboardPayload` snapshot. Use the `sourceId` query parameter to specify a particular project; otherwise, it defaults to the store provided during API construction or the first registered source. ```bash # Default project curl http://127.0.0.1:51234/api/dashboard # Specific registered source curl "http://127.0.0.1:51234/api/dashboard?sourceId=a3f2c91" # → { # "mainSession": { "agent": "sisyphus", "currentModel": "claude-opus-4-5", "currentTool": "Bash", # "lastUpdatedLabel": "2024-07-04T12:00:00.000Z", "session": "My session", # "sessionId": "sess_abc123", "statusPill": "running tool" }, # "planProgress": { "name": "Implement feature X", "completed": 3, "total": 5, # "path": "/home/user/.sisyphus/plans/plan.md", "statusPill": "in progress", # "steps": [{ "checked": true, "text": "Step 1" }, { "checked": false, "text": "Step 2" }] }, # "backgroundTasks": [{ "id": "call_xyz", "description": "Run tests", "agent": "general-purpose", # "lastModel": "claude-haiku", "status": "running", "toolCalls": 4, # "lastTool": "Bash", "timeline": "2024-07-04T12:00:05Z: 1m30s", "sessionId": "sess_bg1" }], # "timeSeries": { "windowMs": 300000, "bucketMs": 2000, "buckets": 150, ... }, # "tokenUsage": { "rows": [{ "model": "anthropic/claude-opus-4-5", "input": 12000, "output": 3500, # "reasoning": 0, "cacheRead": 800, "cacheWrite": 200, "total": 16500 }], # "totals": { "input": 12000, "output": 3500, ... } }, # "todos": [{ "content": "Fix bug", "status": "pending", "priority": "high", "position": 0 }] # } ``` -------------------------------- ### GET /api/tool-calls/:sessionId Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Retrieves metadata-only tool call summaries for a given session, including tool name, status, and timestamps. The results are capped at 200 messages and 300 tool calls. ```APIDOC ## GET /api/tool-calls/:sessionId ### Description Returns metadata-only tool call summaries (tool name, status, timestamps) for a session. Capped at 200 messages / 300 tool calls. Works for both SQLite and file-based backends. ### Method GET ### Endpoint /api/tool-calls/:sessionId ### Parameters #### Path Parameters - **sessionId** (string) - Required - The ID of the session to retrieve tool calls for. ### Request Example ```bash curl http://127.0.0.1:51234/api/tool-calls/sess_abc123 ``` ### Response #### Success Response (200) - **ok** (boolean) - Indicates if the request was successful. - **sessionId** (string) - The ID of the session. - **toolCalls** (array) - An array of tool call objects, each containing sessionId, messageId, callId, tool, status, and createdAtMs. - **caps** (object) - An object containing the maximum number of messages and tool calls allowed. - **truncated** (boolean) - Indicates if the results were truncated due to exceeding caps. #### Response Example ```json { "ok": true, "sessionId": "sess_abc123", "toolCalls": [ { "sessionId": "sess_abc123", "messageId": "msg_001", "callId": "call_001", "tool": "Bash", "status": "completed", "createdAtMs": 1720000010000 }, { "sessionId": "sess_abc123", "messageId": "msg_002", "callId": "call_002", "tool": "Read", "status": "completed", "createdAtMs": 1720000020000 } ], "caps": { "maxMessages": 200, "maxToolCalls": 300 }, "truncated": false } ``` #### Error Response (404) - **ok** (boolean) - Indicates if the request was successful. - **sessionId** (string) - The ID of the session. - **toolCalls** (array) - An empty array as no tool calls were found for the non-existent session. #### Error Response Example ```json { "ok": false, "sessionId": "nonexistent_session", "toolCalls": [] } ``` ``` -------------------------------- ### Build for Production Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Builds the project for production, creating bundled API and Vite UI assets. ```bash # Build for production (Vite UI + API bundle) bun run build ``` -------------------------------- ### Run Dashboard with Explicit Project Path Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Execute the dashboard using `bunx` with the `-p` flag to specify the package, and then use `--project` to explicitly set the project path. ```bash bunx -p oh-my-opencode-dashboard oh-my-opencode-dashboard -- --project /absolute/path/to/your/project ``` -------------------------------- ### Add Project Source with Bunx Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Register your project as a source for the dashboard. This is necessary if the dashboard appears empty after an OpenCode SQLite update. Run this from your project directory. ```bash bunx oh-my-opencode-dashboard@latest add --name "My Project" ``` -------------------------------- ### Run Tests Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Executes the project's test suite. ```bash # Run tests bun test ``` -------------------------------- ### Create Dashboard Store with State Caching Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Use `createDashboardStore` to initialize a reactive state cache for dashboard data. It supports file watching and periodic polling to ensure data freshness. Configure `projectRoot`, `storageRoot`, `storageBackend`, `watch`, and `pollIntervalMs` as needed. ```typescript import { createDashboardStore } from "./src/server/dashboard" import { selectStorageBackend, getLegacyStorageRootForBackend } from "./src/ingest/storage-backend" const backend = selectStorageBackend() const storageRoot = getLegacyStorageRootForBackend(backend) const store = createDashboardStore({ projectRoot: "/home/user/my-project", // used for session filtering + plan lookup storageRoot, storageBackend: backend, watch: true, // enable fs.watch on relevant paths (default: true) pollIntervalMs: 2000, // re-compute at most once per 2 s (default: 2000) }) // Get the current snapshot (computed on first call, then cached until dirty) const snapshot = store.getSnapshot() // Returns DashboardPayload: // { // mainSession: { agent, currentModel, currentTool, lastUpdatedLabel, session, sessionId, statusPill }, // planProgress: { name, completed, total, path, statusPill, steps: [{ checked, text }] }, // backgroundTasks: [{ id, description, agent, lastModel, status, toolCalls, lastTool, timeline, sessionId }], // mainSessionTasks: [{ id, description, subline, agent, lastModel, status, toolCalls, lastTool, timeline, sessionId }], // timeSeries: { windowMs, bucketMs, buckets, anchorMs, serverNowMs, series: [...] }, // tokenUsage: { rows: [{ model, input, output, reasoning, cacheRead, cacheWrite, total }], totals: {...} }, // todos: [{ content, status, priority, position }], // raw: { ... } // } ``` -------------------------------- ### Add Project Source with Specific Path Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/README.md Register a project source, specifying an absolute path to the project directory. This is useful when the dashboard is not run from the project's root directory. ```bash bunx oh-my-opencode-dashboard@latest add --name "My Project" --project /absolute/path/to/your/project ``` -------------------------------- ### Ingest Plan State from Boulder and Markdown Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Read OhMyOpenCode plan state from `.sisyphus/boulder.json` and parse Markdown files for progress and steps. All file accesses are validated. Can also parse Markdown directly. ```typescript import { readBoulderState, readPlanProgress, readPlanSteps, getPlanProgressFromMarkdown } from "./src/ingest/boulder" const projectRoot = "/home/user/my-project" // Read boulder state (returns null if .sisyphus/boulder.json missing) const boulder = readBoulderState(projectRoot) // → { active_plan: "/home/user/my-project/.sisyphus/plans/plan.md", // started_at: "2024-07-04T10:00:00Z", // session_ids: ["sess_abc123"], // plan_name: "Implement feature X" } // Read plan checkbox progress const progress = readPlanProgress(projectRoot, boulder!.active_plan) // → { total: 5, completed: 3, isComplete: false, missing: false } // Read individual plan steps const { steps } = readPlanSteps(projectRoot, boulder!.active_plan) // → [ // { checked: true, text: "Set up project structure" }, // { checked: true, text: "Write unit tests" }, // { checked: true, text: "Implement core logic" }, // { checked: false, text: "Add documentation" }, // { checked: false, text: "Final review" } // ] // Parse markdown directly (without file I/O) const md = "- [x] Done\n- [ ] Pending\n- [X] Also done" const p = getPlanProgressFromMarkdown(md) // → { total: 3, completed: 2, isComplete: false } ``` -------------------------------- ### Manage Project Sources Registry Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Register, update, list, and retrieve project sources using their stable SHA-256 IDs. Writes are atomic. Requires a storage root path. ```typescript import { addOrUpdateSource, listSources, getSourceById, getDefaultSourceId } from "./src/ingest/sources-registry" const storageRoot = "/home/user/.local/share/opencode/storage" // Register or update a project source const id = addOrUpdateSource(storageRoot, { projectRoot: "/home/user/my-project", label: "My Project", }) console.log(id) // → "a3f2c91..." (SHA-256 of realpath) // List all sources sorted by updatedAt DESC const sources = listSources(storageRoot) // → [{ id: "a3f2c91...", label: "My Project", updatedAt: 1720000000000 }] // Look up a specific source by ID const source = getSourceById(storageRoot, id) // → { id: "...", projectRoot: "/home/user/my-project", label: "My Project", createdAt: ..., updatedAt: ... } // Get the default source (most-recently updated) const defaultId = getDefaultSourceId(storageRoot) ``` -------------------------------- ### createDashboardStore Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Creates a reactive state cache for dashboard data. It lazily computes and caches snapshots, re-computing when underlying files or the SQLite database change. File watchers and polling are used to ensure data freshness. ```APIDOC ## `createDashboardStore` — Reactive State Cache Creates a `DashboardStore` that lazily computes a full `DashboardPayload` snapshot on demand, caches it, and re-computes when the underlying files/SQLite change. File watchers are set up on `.sisyphus/boulder.json`, plan files, and the storage backend. Polling every `pollIntervalMs` milliseconds is used as a correctness baseline regardless of file-watch events. ```typescript import { createDashboardStore } from "./src/server/dashboard" import { selectStorageBackend, getLegacyStorageRootForBackend } from "./src/ingest/storage-backend" const backend = selectStorageBackend() const storageRoot = getLegacyStorageRootForBackend(backend) const store = createDashboardStore({ projectRoot: "/home/user/my-project", // used for session filtering + plan lookup storageRoot, storageBackend: backend, watch: true, // enable fs.watch on relevant paths (default: true) pollIntervalMs: 2000, // re-compute at most once per 2 s (default: 2000) }) // Get the current snapshot (computed on first call, then cached until dirty) const snapshot = store.getSnapshot() // Returns DashboardPayload: // { // mainSession: { agent, currentModel, currentTool, lastUpdatedLabel, session, sessionId, statusPill }, // planProgress: { name, completed, total, path, statusPill, steps: [{ checked, text }] }, // backgroundTasks: [{ id, description, agent, lastModel, status, toolCalls, lastTool, timeline, sessionId }], // mainSessionTasks: [{ id, description, subline, agent, lastModel, status, toolCalls, lastTool, timeline, sessionId }], // timeSeries: { windowMs, bucketMs, buckets, anchorMs, serverNowMs, series: [...] }, // tokenUsage: { rows: [{ model, input, output, reasoning, cacheRead, cacheWrite, total }], totals: {...} }, // todos: [{ content, status, priority, position }], // raw: { ... } // } ``` ``` -------------------------------- ### readBoulderState / readPlanProgress / readPlanSteps Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Functions for reading and parsing plan ingestion state from `.sisyphus/boulder.json` and associated Markdown files. Includes reading overall state, progress, and individual steps. ```APIDOC ## Plan Ingestion Reads the OhMyOpenCode plan state from `.sisyphus/boulder.json` in the project root. Parses the linked plan Markdown file to count checkbox progress and extract individual steps. All file accesses are validated through `assertAllowedPath`. ### `readBoulderState(projectRoot: string) => object | null` Reads the boulder state from `.sisyphus/boulder.json`. #### Parameters - **projectRoot** (string) - The root directory of the project. #### Returns - (object | null) - An object containing the boulder state if the file exists, otherwise null. The object includes `active_plan`, `started_at`, `session_ids`, and `plan_name`. ### `readPlanProgress(projectRoot: string, active_plan: string) => { total: number, completed: number, isComplete: boolean, missing: boolean }` Reads the plan checkbox progress from the specified Markdown plan file. #### Parameters - **projectRoot** (string) - The root directory of the project. - **active_plan** (string) - The path to the active plan Markdown file. #### Returns - (object) - An object containing `total` checkboxes, `completed` checkboxes, `isComplete` status, and `missing` status. ### `readPlanSteps(projectRoot: string, active_plan: string) => { steps: Array<{ checked: boolean, text: string }> }` Reads the individual steps from the specified Markdown plan file. #### Parameters - **projectRoot** (string) - The root directory of the project. - **active_plan** (string) - The path to the active plan Markdown file. #### Returns - (object) - An object containing an array of `steps`, where each step has a `checked` status and `text`. ### `getPlanProgressFromMarkdown(markdown: string) => { total: number, completed: number, isComplete: boolean }` Parses a Markdown string directly to calculate plan progress without file I/O. #### Parameters - **markdown** (string) - The Markdown content of the plan. #### Returns - (object) - An object containing `total` checkboxes, `completed` checkboxes, and `isComplete` status. ### Example Usage ```typescript import { readBoulderState, readPlanProgress, readPlanSteps, getPlanProgressFromMarkdown } from "./src/ingest/boulder" const projectRoot = "/home/user/my-project" // Read boulder state const boulder = readBoulderState(projectRoot) // → { active_plan: "/home/user/my-project/.sisyphus/plans/plan.md", // started_at: "2024-07-04T10:00:00Z", // session_ids: ["sess_abc123"], // plan_name: "Implement feature X" } // Read plan checkbox progress const progress = readPlanProgress(projectRoot, boulder!.active_plan) // → { total: 5, completed: 3, isComplete: false, missing: false } // Read individual plan steps const { steps } = readPlanSteps(projectRoot, boulder!.active_plan) // → [ // { checked: true, text: "Set up project structure" }, // { checked: true, text: "Write unit tests" }, // { checked: true, text: "Implement core logic" }, // { checked: false, text: "Add documentation" }, // { checked: false, text: "Final review" } // ] // Parse markdown directly const md = "- [x] Done\n- [ ] Pending\n- [X] Also done" const p = getPlanProgressFromMarkdown(md) // → { total: 3, completed: 2, isComplete: false } ``` ``` -------------------------------- ### List Project Sources Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Retrieves a list of all registered project sources, sorted by their last update time. The response includes the default source ID and an array of source objects, each with an ID, label, and update timestamp. ```bash curl http://127.0.0.1:51234/api/sources # → { # "ok": true, # "defaultSourceId": "a3f2...c91", # "sources": [ # { "id": "a3f2...c91", "label": "My Project", "updatedAt": 1720000000000 } # ] # } ``` -------------------------------- ### Run Type Check with Bun Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Execute the type checking script using Bun. This is a common command for verifying code integrity. ```bash bun run typecheck ``` -------------------------------- ### resolveServerHost / getPublicHost Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Resolves the bind hostname for the HTTP server from CLI flag, environment variable, or default (`127.0.0.1`). `getPublicHost` converts wildcard bind addresses (`0.0.0.0`, `::`) to `localhost` for display in log output. ```APIDOC ## `resolveServerHost` / `getPublicHost` — Host Resolution Resolves the bind hostname for the HTTP server from CLI flag, environment variable, or default (`127.0.0.1`). `getPublicHost` converts wildcard bind addresses (`0.0.0.0`, `::`) to `localhost` for display in log output. ### `resolveServerHost` #### Parameters ##### Request Body - **cliHost** (string) - Optional - Hostname provided via CLI flag. - **envHost** (string) - Optional - Hostname provided via environment variable. #### Response ##### Success Response (200) - **host** (string) - The resolved hostname for the server. ### `getPublicHost` #### Parameters ##### Path Parameters - **host** (string) - Required - The hostname to convert. #### Response ##### Success Response (200) - **publicHost** (string) - The public-facing hostname (e.g., 'localhost' for wildcards). ### Request Example ```typescript import { resolveServerHost, getPublicHost } from "./src/server/host" resolveServerHost({ cliHost: "0.0.0.0", envHost: "localhost" }) // → "0.0.0.0" (CLI wins) resolveServerHost({ envHost: "127.0.0.1" }) // → "127.0.0.1" resolveServerHost({}) // → "127.0.0.1" (default) getPublicHost("0.0.0.0") // → "localhost" getPublicHost("::") // → "localhost" getPublicHost("127.0.0.1") // → "127.0.0.1" ``` ``` -------------------------------- ### Set Theme Based on Local Storage or System Preference Source: https://github.com/williamjudge94/oh-my-opencode-dashboard/blob/main/index.html This script sets the theme for the dashboard. It first checks local storage for a saved theme, then checks the system's preferred color scheme, and defaults to light if neither is found. The theme is applied by setting a data attribute on the document element. ```javascript (function() { var saved = null; try { saved = localStorage.getItem('omoDashboardTheme'); } catch (e) {} var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; var theme = saved || (prefersDark ? 'dark' : 'light'); document.documentElement.setAttribute('data-theme', theme); })(); ``` -------------------------------- ### Create Hono REST API with Path Enforcement Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Use `createApi` to build a Hono sub-application for RESTful services. It enforces path access using `assertAllowedPath` and validates session IDs. Provide the `store`, `storageRoot`, `projectRoot`, `storageBackend`, and optionally `getStoreForSource`. ```typescript import { createApi } from "./src/server/api" const api = createApi({ store, storageRoot, projectRoot, storageBackend, getStoreForSource: ({ sourceId, projectRoot }) => { /* return per-source DashboardStore */ }, }) // Mount under /api in the main Hono app app.route("/api", api) ``` -------------------------------- ### createApi Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Factory function to build a Hono sub-application with specific API routes. It enforces path access and validates session IDs. ```APIDOC ## `createApi` — Hono REST API Factory Builds and returns a Hono sub-application with three routes. Path access is enforced via `assertAllowedPath` so the server can never be tricked into reading files outside the declared roots. Session IDs are validated against a strict alphanumeric pattern. ```typescript import { createApi } from "./src/server/api" const api = createApi({ store, // DashboardStore for the default project storageRoot, projectRoot, storageBackend, getStoreForSource: ({ sourceId, projectRoot }) => { /* return per-source DashboardStore */ }, }) // Mount under /api in the main Hono app app.route("/api", api) ``` ``` -------------------------------- ### Resolve Storage Paths Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Resolves XDG data directory and OpenCode storage paths, respecting XDG_DATA_HOME and falling back to default locations. Supports tilde expansion for home directories. ```typescript import { getDataDir, getOpenCodeStorageDir, getOpenCodeStorageDirFromDataDir } from "./src/ingest/paths" // With default env getDataDir() // → "/home/user/.local/share" (or $XDG_DATA_HOME if set) getOpenCodeStorageDir() // → "/home/user/.local/share/opencode/storage" // With XDG_DATA_HOME set getDataDir({ XDG_DATA_HOME: "/custom/data" }) // → "/custom/data" // Tilde expansion getDataDir({ XDG_DATA_HOME: "~/mydata" }, "/home/user") // → "/home/user/mydata" getOpenCodeStorageDirFromDataDir("/custom/data") // → "/custom/data/opencode/storage" ``` -------------------------------- ### addOrUpdateSource / listSources Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Manages a JSON registry of project roots. `addOrUpdateSource` registers or updates a project source, returning its stable SHA-256 ID. `listSources` retrieves all registered sources, sorted by update time. ```APIDOC ## Sources Registry Manages a JSON registry of project roots at `/dashboard/sources.json`. IDs are SHA-256 hashes of the canonicalized (realpath-normalized) project root, making them stable across renames if symlinks are used. Writes are atomic (write-to-tmp + rename). ### `addOrUpdateSource(storageRoot: string, source: { projectRoot: string, label: string }) => string` Registers or updates a project source. #### Parameters - **storageRoot** (string) - The root directory for storing the sources registry. - **source** (object) - An object containing: - **projectRoot** (string) - The absolute path to the project root. - **label** (string) - A human-readable label for the project. #### Returns - (string) - The stable SHA-256 hash ID of the registered source. ### `listSources(storageRoot: string) => Array<{ id: string, label: string, updatedAt: number }>` Lists all registered sources, sorted by `updatedAt` in descending order. #### Parameters - **storageRoot** (string) - The root directory for storing the sources registry. #### Returns - (Array) - An array of source objects, each containing `id`, `label`, and `updatedAt`. ### Example Usage ```typescript import { addOrUpdateSource, listSources } from "./src/ingest/sources-registry" const storageRoot = "/home/user/.local/share/opencode/storage" // Register or update a project source const id = addOrUpdateSource(storageRoot, { projectRoot: "/home/user/my-project", label: "My Project", }) console.log(id) // → "a3f2c91..." // List all sources sorted by updatedAt DESC const sources = listSources(storageRoot) // → [{ id: "a3f2c91...", label: "My Project", updatedAt: 1720000000000 }] ``` ``` -------------------------------- ### Auto-detect OpenCode Storage Backend Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt The `selectStorageBackend` function automatically detects if OpenCode is using SQLite or legacy file-based storage. It returns a `StorageBackend` union type. Use `getLegacyStorageRootForBackend` to resolve the legacy storage root, which is necessary for the sources registry. The `dataDir` option can be used to override the default data directory, useful for testing. ```typescript import { selectStorageBackend, getLegacyStorageRootForBackend } from "./src/ingest/storage-backend" const backend = selectStorageBackend() // backend.kind === "sqlite" → { kind: "sqlite", dataDir: "/home/user/.local/share", sqlitePath: "...opencode.db" } // backend.kind === "files" → { kind: "files", dataDir: "...", storageRoot: "...opencode/storage" } // Resolve the legacy storage root regardless of backend (needed for sources registry) const storageRoot = getLegacyStorageRootForBackend(backend) // → "/home/user/.local/share/opencode/storage" // Override data directory (useful for testing) const testBackend = selectStorageBackend({ dataDir: "/tmp/test-opencode" }) // Uses /tmp/test-opencode/opencode/opencode.db if present ``` -------------------------------- ### getDataDir / getOpenCodeStorageDir Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Resolves the XDG data directory and OpenCode storage paths, matching OpenCode's own path logic. Respects `XDG_DATA_HOME` (including `~/` expansion) and falls back to `~/.local/share`. ```APIDOC ## `getDataDir` / `getOpenCodeStorageDir` — Storage Path Resolution Resolves the XDG data directory and OpenCode storage paths, matching OpenCode's own path logic. Respects `XDG_DATA_HOME` (including `~/` expansion) and falls back to `~/.local/share`. ### `getDataDir` #### Parameters ##### Request Body - **env** (object) - Optional - An object containing environment variables to override. - **XDG_DATA_HOME** (string) - Optional - The XDG data home directory. - **userHome** (string) - Optional - The user's home directory path (used for tilde expansion). #### Response ##### Success Response (200) - **dataDir** (string) - The resolved data directory path. ### `getOpenCodeStorageDir` #### Parameters None #### Response ##### Success Response (200) - **storageDir** (string) - The resolved OpenCode storage directory path. ### `getOpenCodeStorageDirFromDataDir` #### Parameters ##### Path Parameters - **dataDir** (string) - Required - The base data directory path. #### Response ##### Success Response (200) - **storageDir** (string) - The resolved OpenCode storage directory path relative to the provided data directory. ### Request Example ```typescript import { getDataDir, getOpenCodeStorageDir, getOpenCodeStorageDirFromDataDir } from "./src/ingest/paths" // With default env getDataDir() // → "/home/user/.local/share" (or $XDG_DATA_HOME if set) getOpenCodeStorageDir() // → "/home/user/.local/share/opencode/storage" // With XDG_DATA_HOME set getDataDir({ XDG_DATA_HOME: "/custom/data" }) // → "/custom/data" // Tilde expansion getDataDir({ XDG_DATA_HOME: "~/mydata" }, "/home/user") // → "/home/user/mydata" getOpenCodeStorageDirFromDataDir("/custom/data") // → "/custom/data/opencode/storage" ``` ``` -------------------------------- ### Derive Background Tasks from Session Files Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Use `deriveBackgroundTasks` to scan session files for task-dispatch tools and infer child sessions. This function returns up to 50 `BackgroundTaskRow` entries, including tool-call counts and status. Requires storage roots and a main session ID. ```typescript import { deriveBackgroundTasks } from "./src/ingest/background-tasks" import { getStorageRoots } from "./src/ingest/session" const storage = getStorageRoots("/home/user/.local/share/opencode/storage") const tasks = deriveBackgroundTasks({ storage, mainSessionId: "sess_abc123", nowMs: Date.now(), }) // → [ // { // id: "call_xyz789", // description: "Run integration tests", // agent: "general-purpose", // status: "completed", // "queued" | "running" | "completed" | "error" | "unknown" // toolCalls: 12, // lastTool: "Bash", // lastModel: "claude-haiku", // timeline: "2024-07-04T12:05:00Z: 3m20s", // sessionId: "sess_bg1" // } // ] ``` -------------------------------- ### computeWaitingDing Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Pure function that decides whether a notification sound should play when the agent transitions into a "waiting for user" state. Suppresses repeated dings within a configurable window (default 20 seconds) to avoid noise when the agent rapidly toggles in/out of waiting. ```APIDOC ## `computeWaitingDing` — Sound Notification Policy Pure function that decides whether a notification sound should play when the agent transitions into a "waiting for user" state. Suppresses repeated dings within a configurable window (default 20 seconds) to avoid noise when the agent rapidly toggles in/out of waiting. ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **prev** (WaitingDingState) - Required - The previous state of the ding policy. - **waiting** (boolean) - Required - Indicates if the agent is currently in a waiting state. - **nowMs** (number) - Required - The current timestamp in milliseconds. - **suppressMs** (number) - Optional - The duration in milliseconds to suppress repeated dings. ### Response #### Success Response (200) - **play** (boolean) - Indicates whether a ding sound should be played. - **next** (WaitingDingState) - The next state of the ding policy. ### Request Example ```typescript import { computeWaitingDing, type WaitingDingState } from "./src/ding-policy" let dingState: WaitingDingState = { prevWaiting: null, lastLeftWaitingAtMs: null } // First time we see waiting=true → play const r1 = computeWaitingDing({ prev: dingState, waiting: true, nowMs: 1000 }) console.log(r1.play) // true dingState = r1.next // Still waiting → no repeat play const r2 = computeWaitingDing({ prev: dingState, waiting: true, nowMs: 2000 }) console.log(r2.play) // false dingState = r2.next // Stopped waiting dingState = computeWaitingDing({ prev: dingState, waiting: false, nowMs: 3000 }).next // Waiting again within suppress window (20 s) → no play const r4 = computeWaitingDing({ prev: dingState, waiting: true, nowMs: 5000, suppressMs: 20_000 }) console.log(r4.play) // false // Waiting again after suppress window → play const r5 = computeWaitingDing({ prev: dingState, waiting: true, nowMs: 25000, suppressMs: 20_000 }) console.log(r5.play) // true ``` ``` -------------------------------- ### Derive and Aggregate Token Usage Source: https://context7.com/williamjudge94/oh-my-opencode-dashboard/llms.txt Use `deriveTokenUsage` to collect assistant message metadata from main and background sessions, then delegate to `aggregateTokenUsage` for de-duplication and aggregation of token counts per model. Ensure necessary imports and storage roots are configured. ```typescript import { deriveTokenUsage } from "./src/ingest/token-usage" import { aggregateTokenUsage } from "./src/ingest/token-usage-core" import { getStorageRoots } from "./src/ingest/session" const storage = getStorageRoots("/home/user/.local/share/opencode/storage") const usage = deriveTokenUsage({ storage, mainSessionId: "sess_abc123", backgroundSessionIds: ["sess_bg1", "sess_bg2", null], // nulls are skipped }) // → { // rows: [ // { model: "anthropic/claude-opus-4-5", input: 15000, output: 4200, reasoning: 0, // cacheRead: 1200, cacheWrite: 600, total: 21000 }, // { model: "anthropic/claude-haiku-3-5", input: 3000, output: 800, reasoning: 0, // cacheRead: 0, cacheWrite: 0, total: 3800 } // ], // totals: { input: 18000, output: 5000, reasoning: 0, cacheRead: 1200, cacheWrite: 600, total: 24800 } // } // Aggregate from raw message-meta objects directly const rawMetas = [ { id: "msg1", role: "assistant", model: "anthropic/claude-opus-4-5", tokens: { input: 1000, output: 300, cache: { read: 100, write: 50 } } }, { id: "msg1", role: "assistant", model: "anthropic/claude-opus-4-5", tokens: { input: 500, output: 150 } }, // duplicate msg1 — skipped ] const result = aggregateTokenUsage(rawMetas) // → { rows: [{ model: "anthropic/claude-opus-4-5", input: 1000, output: 300, ... }], totals: {...} } ```