Something went wrong. Try again.
my dotfiles as mostly literate conf from emacs (Org-mode) to nix-darwin
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107/** * Worklog heartbeat plugin for OpenCode. * * Records a heartbeat to ~/.claude/worklog.db on every user message, * mirroring the Claude Code UserPromptSubmit hook. Uses source='opencode' * to distinguish from Claude Code heartbeats. * * Auto-loads from ~/.config/opencode/plugins/ (symlinked from dotfiles). */
import { Database } from "bun:sqlite";import { execSync } from "child_process";import { existsSync, readFileSync } from "fs";import { join, basename } from "path";import { homedir } from "os";
const DB_PATH = join(homedir(), ".claude", "worklog.db");
const CREATE_TABLE = ` CREATE TABLE IF NOT EXISTS heartbeats ( ts TEXT NOT NULL, project TEXT NOT NULL, cwd TEXT NOT NULL, source TEXT DEFAULT 'claude-code', flushed INTEGER DEFAULT 0 )`;
const CREATE_INDEX = "CREATE INDEX IF NOT EXISTS idx_hb ON heartbeats(project, flushed)";
const INSERT = "INSERT INTO heartbeats(ts, project, cwd, source) VALUES(?, ?, ?, 'opencode')";
function resolveProject(cwd: string): string | null { // Check for .worklog-project file (explicit project override) try { const root = execSync("git rev-parse --show-toplevel", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); const wpf = join(root, ".worklog-project"); if (existsSync(wpf)) { return readFileSync(wpf, "utf-8").trim(); } } catch { // Not a git repo — fall through to heuristic }
// Heuristic: first segment of repo name from git remote try { const remoteUrl = execSync("git remote get-url origin", { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); const repoName = basename(remoteUrl, ".git"); const project = repoName.split("-")[0].toLowerCase(); return project || null; } catch { // No git remote — use directory name const project = basename(cwd).split("-")[0].toLowerCase(); return project || null; }}
function localTimestamp(): string { const now = new Date(); const pad = (n: number) => String(n).padStart(2, "0"); return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;}
export default () => { let db: Database | null = null;
function getDb(): Database { if (!db) { db = new Database(DB_PATH, { create: true }); db.run(CREATE_TABLE); db.run(CREATE_INDEX); } return db; }
return { "chat.message": async () => { try { const cwd = process.cwd(); const project = resolveProject(cwd); if (!project) return;
const database = getDb(); database.run(INSERT, [localTimestamp(), project, cwd]); } catch { // Silent — worklog must never disrupt the session } },
dispose: async () => { if (db) { db.close(); db = null; } }, };};