Oh My OpenCode Dashboard

repository·main·Indexed 43 days ago

https://github.com/williamjudge94/oh-my-opencode-dashboard

A local-only, read-only dashboard for visualizing the progress of OpenCode and OhMyOpenCode agents. Version 0.4.4 provides real-time signals including plan progress, tool-call metadata, and token usage by consuming data from SQLite or legacy file-based storage without exposing sensitive prompt or tool data.

Tokens
9.5K
Snippets
33
Records
40
Agent score
55%

What's inside Oh My OpenCode Dashboard

  1. How the dashboard reads OpenCode data

    main

    The dashboard is a read-only tool that consumes data from two primary sources:

    1. OhMyOpenCode Plan Tracking (Optional)

    It looks for files in the .sisyphus/ directory of your project:

    • .sisyphus/boulder.json: Used for plan progress.
    • boulder.active_plan: The active plan markdown file.

    2. OpenCode Session Storage

    The dashboard auto-detects storage in the following order:

    • SQLite (Preferred): Located at ${XDG_DATA_HOME ?? ~/.local/share}/opencode/opencode.db. It also monitors WAL side files (opencode.db-wal, opencode.db-shm).
    • Legacy File-based (Fallback): If SQLite is unavailable, it falls back to files in ${XDG_DATA_HOME ?? ~/.local/share}/opencode/storage/{session,message,part}.

    Note on Session Selection:

    • If .sisyphus/boulder.json exists, the dashboard prefers the most recent session_ids[] entry found on disk.
    • Otherwise, it selects the most recently updated OpenCode session where the meta.directory matches your --project path (after realpath normalization).
  2. Run the dashboard from source

    main

    If you are developing or running from a cloned repository, use the following commands:

    Development Mode

    Starts both the API and the UI development server:

    bun run dev -- --project /absolute/path/to/your/project

    Production Mode

    Builds and starts a single server serving both the UI and the API:

    bun run build
    bun run start -- --project /absolute/path/to/your/project

    Network Binding

    By default, the server binds to 127.0.0.1. To expose the server to other machines (e.g., from a container), bind to 0.0.0.0. Use this only on trusted networks.

    Using environment variables:

    OMO_DASHBOARD_HOST=0.0.0.0 bun run start -- --project /absolute/path/to/your/project

    Using CLI flags:

    bun run start -- --project /absolute/path/to/your/project --host 0.0.0.0
    bun run start -- --project /absolute/path/to/your/project
  3. Install and run Oh My OpenCode Dashboard via npm

    main

    You can run the dashboard without a permanent installation using bunx.

    Important: If the dashboard appears empty after an OpenCode SQLite update, you must register your project as a source first using the add command.

    1. Register your project

    Run this from your project directory to register it as a source:

    bunx oh-my-opencode-dashboard@latest add --name "My Project"

    Alternatively, specify a custom name or an absolute path:

    bunx oh-my-opencode-dashboard@latest add --name "My Project" --project /absolute/path/to/your/project

    2. Start the dashboard

    Once registered, start the dashboard using:

    bunx oh-my-opencode-dashboard@latest

    Global Installation

    To install it globally via Bun:

    bun add -g oh-my-opencode-dashboard
    oh-my-opencode-dashboard
    bunx oh-my-opencode-dashboard@latest add --name "My Project"
    bunx oh-my-opencode-dashboard@latest
  4. Troubleshoot session detection and data visibility

    main

    If you encounter issues with the dashboard, check the following:

    • Sessions not detected:
      • Ensure you have run OpenCode at least once in the project directory.
      • Verify that --project matches the real, resolved path of the directory stored in the session metadata. Symlinks can cause mismatches.
      • Check if OpenCode storage exists under ${XDG_DATA_HOME ?? ~/.local/share}/opencode/storage.
    • Plan progress is empty:
      • This is expected in vanilla OpenCode. For plan tracking, ensure .sisyphus/boulder.json is present.
    • Dashboard shows 'Disconnected' in dev mode:
      • Ensure the API server is running and the UI is correctly using the Vite proxy.
    • Data is stale in SQLite mode:
      • OpenCode may be writing via WAL. While the dashboard uses best-effort file watching, polling is the fallback. If data is stuck, try closing OpenCode to release database locks and then reload the dashboard.
    • SQLite mode fails:
      • The dashboard should automatically fall back to legacy file-based storage if SQLite is unreadable or corrupt.
  5. Configure the dashboard via CLI options

    main

    When running the dashboard, you can customize its behavior using the following flags:

    • --project <path>: The project root used for plan lookup and session filtering. Defaults to the current working directory.
    • --host <hostname>: The server bind host. Defaults to 127.0.0.1. Can also be set via the OMO_DASHBOARD_HOST environment variable.
    • --port <number>: The server port. Defaults to 51234.

    Example of specifying a project path explicitly:

    bunx -p oh-my-opencode-dashboard oh-my-opencode-dashboard -- --project /absolute/path/to/your/project
  6. Parse plan progress from Markdown content

    main

    The getPlanProgressFromMarkdown function extracts progress statistics from a Markdown string by counting task list items (e.g., - [ ] or - [x]).

    Returns: An object containing:

    • total: Total number of task items found.
    • completed: Number of items marked as checked ([x] or [X]).
    • isComplete: Boolean indicating if all tasks are completed (or if no tasks exist).
    import { getPlanProgressFromMarkdown } from './src/ingest/boulder';
    
    const markdown = `- [x] Task 1\n- [ ] Task 2`;
    const progress = getPlanProgressFromMarkdown(markdown);
    // Returns: { total: 2, completed: 1, isComplete: false }
  7. Create a reactive dashboard store with createDashboardStore

    main

    The createDashboardStore function provides a managed way to access dashboard snapshots. It handles caching, polling, and optional file system watching to ensure that data is only re-computed when necessary.

    • Polling: If pollIntervalMs is set, the store will re-compute the payload if the requested snapshot is older than the interval.
    • Watching: If watch: true (default), the store sets up file watchers on critical files (like boulder.json, plan files, or the SQLite database/WAL files) to mark the cache as dirty immediately upon changes.
    • Snapshotting: Call .getSnapshot() to retrieve the current DashboardPayload or LegacyDashboardPayload.
    import { createDashboardStore } from './src/server/dashboard';
    
    const store = createDashboardStore({
      projectRoot: '/path/to/project',
      storageRoot: '/path/to/storage',
      storageBackend: { kind: 'sqlite', sqlitePath: '/path/to/db.sqlite' },
      pollIntervalMs: 2000,
      watch: true
    });
    
    // Periodically get the latest data
    const data = store.getSnapshot();
  8. Get the main session view status

    main

    The getMainSessionView function provides a high-level summary of a session's current state, useful for dashboarding. It returns a MainSessionView object containing:

    • agent: The name of the last active agent.
    • currentTool: The name of the tool currently being executed (if any).
    • currentModel: The model currently in use.
    • lastUpdated: Timestamp of the last activity.
    • sessionLabel: The session title or ID.
    • status: One of "busy", "idle", "unknown", "running_tool", or "thinking".

    Status Logic:

    • "running_tool": An active tool part is found with status "pending" or "running".
    • "thinking": The last message was from the assistant and has no completed timestamp.
    • "busy": The last activity occurred within the last 15 seconds.
    • "idle": The last activity was more than 15 seconds ago.
    • "unknown": Default state.
    import { getMainSessionView, getStorageRoots } from './ingest/session';
    
    const view = getMainSessionView({
      projectRoot: '/path/to/project',
      sessionId: 'some-session-id',
      storage: getStorageRoots('/path/to/storage')
    });
    
    console.log(view.status); // e.g., 'running_tool' or 'thinking'
  9. Generate dashboard data with buildDashboardPayload

    main

    Use buildDashboardPayload to construct a complete DashboardPayload object representing the current state of the OpenCode session. This function automatically detects whether to use the legacy file-based storage or the modern SQLite backend based on the provided storageBackend option.

    If a storageBackend with kind: 'sqlite' is provided, the function attempts to derive data (tasks, time series, token usage, etc.) from the SQLite database. If the backend is missing or not SQLite, it falls back to reading from the file system using the provided storage roots.

    import { buildDashboardPayload } from './src/server/dashboard';
    
    const payload = buildDashboardPayload({
      projectRoot: '/path/to/project',
      storage: { /* OpenCodeStorageRoots */ },
      storageBackend: { kind: 'sqlite', sqlitePath: '/path/to/database.sqlite' }
    });
  10. Locate the OpenCode storage directory

    main

    The dashboard uses specific directory structures to find OpenCode data. You can determine the storage location using getDataDir or getOpenCodeStorageDir.

    By default, the dashboard follows XDG standards:

    1. It checks the XDG_DATA_HOME environment variable.
    2. If XDG_DATA_HOME is not set, it defaults to ~/.local/share (or the platform equivalent).
    3. The OpenCode storage directory is located at [dataDir]/opencode/storage.

    You can override the default behavior by providing a custom Env object containing a different XDG_DATA_HOME value.

    import { getOpenCodeStorageDir } from 'oh-my-opencode-dashboard/src/ingest/paths';
    
    // Uses default XDG_DATA_HOME or ~/.local/share
    const storageDir = getOpenCodeStorageDir();
    console.log(`Storage directory: ${storageDir}`);
  11. Read Boulder state from JSON

    main

    Use readBoulderState to retrieve the current session and plan metadata from the .sisyphus/boulder.json file located within a project root. If the file does not exist or is invalid, the function returns null.

    import { readBoulderState } from './src/ingest/boulder';
    
    const boulderState = readBoulderState('/path/to/project-root');
    if (boulderState) {
      console.log(boulderState.plan_name);
      console.log(boulderState.session_ids);
    }
  12. Define OpenCode storage roots

    main

    OpenCode uses a specific directory structure to separate session metadata, messages, and tool parts. You can define these roots using getStorageRoots(storageRoot: string), which returns an OpenCodeStorageRoots object containing paths for session, message, and part subdirectories.

    import { getStorageRoots } from './ingest/session';
    
    const roots = getStorageRoots('/path/to/opencode/data');
    // roots.session -> '/path/to/opencode/data/session'
    // roots.message -> '/path/to/opencode/data/message'
    // roots.part    -> '/path/to/opencode/data/part'