Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ Upgrade with `brew upgrade --cask mm7894215/tokentracker/tokentracker`. The tap
| **GitHub Copilot App / CLI** | ✅ Auto | Unified per-request SQLite usage (`~/.copilot/session-store.db`); App DB legacy baseline |
| **GitHub Copilot Chat extension / legacy CLI** | ✅ Auto | OpenTelemetry file exporter (`COPILOT_OTEL_FILE_EXPORTER_PATH`) |
| **Kimi Code** | ✅ Auto | Passive `wire.jsonl` reader (`~/.kimi/sessions/**/wire.jsonl`) |
| **oh-my-pi (Pi Coding Agent)** | ✅ Auto | Passive reader (`~/.omp/agent/sessions/**/*.jsonl`) |
| **oh-my-pi (Pi Coding Agent)** | ✅ Auto | Passive reader (`~/.omp/agent/sessions/**/*.jsonl`) + optional notify extension (`~/.omp/agent/extensions/tokentracker-notify.ts`) for near-real-time sync |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
| **CodeBuddy** (Tencent) | ✅ Auto | SessionEnd hook in `~/.codebuddy/settings.json` (Claude-Code fork) |
| **WorkBuddy** (Tencent) | ✅ Auto | SessionEnd hook in `~/.workbuddy/settings.json` (Claude-Code fork) + passive `projects/**/*.jsonl` scan |
| **Grok Build** (xAI) | ✅ Auto | SessionEnd hook + passive `updates.jsonl` / `signals.json` scan (`~/.grok/sessions/**/`) |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ brew install mm7894215/tokentracker/tokentracker
| **GitHub Copilot App / CLI** | ✅ 自动 | 统一逐请求 SQLite 用量(`~/.copilot/session-store.db`);App DB 作为旧数据基线 |
| **GitHub Copilot Chat 扩展 / 旧版 CLI** | ✅ 自动 | OpenTelemetry 文件导出(`COPILOT_OTEL_FILE_EXPORTER_PATH`) |
| **Kimi Code** | ✅ 自动 | 被动读取 `wire.jsonl`(`~/.kimi/sessions/**/wire.jsonl`) |
| **oh-my-pi (Pi Coding Agent)** | ✅ 自动 | 被动读取(`~/.omp/agent/sessions/**/*.jsonl`) |
| **oh-my-pi (Pi Coding Agent)** | ✅ 自动 | 被动读取(`~/.omp/agent/sessions/**/*.jsonl`)+ 可选 notify 扩展(`~/.omp/agent/extensions/tokentracker-notify.ts`)近实时同步 |
| **CodeBuddy** (腾讯) | ✅ 自动 | 写入 `~/.codebuddy/settings.json` 的 SessionEnd hook(Claude-Code fork) |
| **WorkBuddy** (腾讯) | ✅ 自动 | 写入 `~/.workbuddy/settings.json` 的 SessionEnd hook(Claude-Code fork)+ 被动扫描 `projects/**/*.jsonl` |
| **Grok Build** (xAI) | ✅ 自动 | SessionEnd hook + 被动扫描 `updates.jsonl` / `signals.json`(`~/.grok/sessions/**/`) |
Expand Down
33 changes: 33 additions & 0 deletions dashboard/public/brand-logos/omp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions dashboard/public/brand-logos/pi.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions dashboard/src/lib/provider-display.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const SPECIAL_PROVIDER_NAMES = {
anythingllm: "AnythingLLM",
claudescience: "Claude Science",
omp: "oh-my-pi",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move the new display label into the dashboard copy source.

"oh-my-pi" is user-facing text and is hardcoded in dashboard/src/lib/provider-display.js. Add it to dashboard/src/content/copy.csv and use the existing content lookup instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/lib/provider-display.js` at line 4, Move the user-facing
"oh-my-pi" label from the provider mapping in provider-display.js into copy.csv,
then replace the hardcoded value with the existing content lookup mechanism.
Preserve the provider mapping behavior while ensuring the label is sourced
through dashboard copy content.

Source: Path instructions

pianthropic: "Pi · Anthropic",
pigithubcopilot: "Pi · GitHub Copilot",
picopilot: "Pi · Copilot",
Expand Down
5 changes: 5 additions & 0 deletions dashboard/src/lib/provider-display.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ describe("formatProviderDisplayName", () => {
expect(formatProviderDisplayName("pi-anthropic")).toBe("Pi · Anthropic");
expect(formatProviderDisplayName("PI-GITHUB-COPILOT")).toBe("Pi · GitHub Copilot");
});

it("formats omp as oh-my-pi", () => {
expect(formatProviderDisplayName("omp")).toBe("oh-my-pi");
expect(formatProviderDisplayName("OMP")).toBe("oh-my-pi");
});
});
6 changes: 6 additions & 0 deletions dashboard/src/ui/dashboard/components/ProviderIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,14 @@ const PROVIDER_LOGO_MAP = {
"KILO-CLI": "/brand-logos/kilo.svg",
"KILO-CODE": "/brand-logos/kilo.svg",
MIMO: "/brand-logos/mimo.svg",
// oh-my-pi / pi: multi-color brand marks (pi letterform + plugin connector).
OMP: "/brand-logos/omp.svg",
OPENCLAW: "/brand-logos/openclaw.svg",
OPENCODE: "/brand-logos/opencode.svg",
PI: "/brand-logos/pi.svg",
"PI-ANTHROPIC": "/brand-logos/pi.svg",
"PI-GITHUB-COPILOT": "/brand-logos/pi.svg",
"PI-COPILOT": "/brand-logos/pi.svg",
QODER: "/brand-logos/qoder.svg",
};

Expand Down
15 changes: 15 additions & 0 deletions dashboard/src/ui/dashboard/components/ProviderIcon.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,19 @@ describe("ProviderIcon", () => {
expect(icon?.querySelector("path")).not.toBeNull();
expect(icon?.querySelector("circle")).toBeNull();
});

it("renders the multi-color oh-my-pi brand logo", () => {
const { container } = render(<ProviderIcon provider="omp" size={20} />);
const icon = container.querySelector('img[src="/brand-logos/omp.svg"]');
expect(icon).not.toBeNull();
expect(icon).toHaveAttribute("width", "20");
expect(icon).toHaveAttribute("height", "20");
});

it("renders the multi-color Pi brand logo", () => {
const { container } = render(<ProviderIcon provider="pi" size={18} />);
const icon = container.querySelector('img[src="/brand-logos/pi.svg"]');
expect(icon).not.toBeNull();
expect(icon).toHaveAttribute("width", "18");
});
});
57 changes: 49 additions & 8 deletions src/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const {
removeGrokHook,
GROK_HOOK_FILENAME
} = require("../lib/grok-hook");
const {
upsertOmpHook,
probeOmpHookState,
} = require("../lib/omp-hook");
const { resolveTrackerPaths } = require("../lib/tracker-paths");
const {
resolveOmpAgentDir,
Expand Down Expand Up @@ -361,6 +365,7 @@ async function runSetup({
trackerDir,
notifyPath,
notifyOriginalPath,
dryRun: Boolean(opts.dryRun),
});

return {
Expand Down Expand Up @@ -551,7 +556,13 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
};
}

async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOriginalPath }) {
async function applyIntegrationSetup({
home,
trackerDir,
notifyPath,
notifyOriginalPath,
dryRun = false,
}) {
const context = buildIntegrationTargets({ home, trackerDir, notifyPath });
context.notifyOriginalPath = notifyOriginalPath;

Expand Down Expand Up @@ -642,15 +653,44 @@ async function applyIntegrationSetup({ home, trackerDir, notifyPath, notifyOrigi
}
}

// oh-my-pi: passive reader — no hook installation needed.
// TokenTracker reads ~/.omp/agent/sessions/**/*.jsonl directly.
// oh-my-pi: passive session scan always works; also install an optional
// notify extension so turn_end triggers sync --source omp near-real-time.
{
// resolveOmpAgentDir returns null on Windows when ~/.omp doesn't exist (the
// win32 path resolver only yields a dir it can see) — null means "not
// installed", so skip rather than path.join(null, …) and crash.
const fssyncLocal = require("node:fs");
const ompAgentDir = resolveOmpAgentDir(process.env);
if (ompAgentDir && fssync.existsSync(path.join(ompAgentDir, "sessions"))) {
summary.push({ label: "oh-my-pi", status: "detected", detail: "Passive reader (no hook needed)" });
const ompSessions = ompAgentDir && fssyncLocal.existsSync(path.join(ompAgentDir, "sessions"));
if (ompAgentDir && (ompSessions || fssyncLocal.existsSync(ompAgentDir))) {
if (dryRun) {
const probe = await probeOmpHookState({ home, trackerDir, env: process.env });
summary.push({
label: "oh-my-pi",
status: "detected",
detail: probe.configured
? "Notify extension already installed (passive scan still runs)"
: "Will install notify extension + keep passive scan",
});
} else {
const result = await upsertOmpHook({ home, trackerDir, env: process.env });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (result.written) {
summary.push({
label: "oh-my-pi",
status: "installed",
detail: `Notify extension → ${result.extensionPath} (passive scan still runs)`,
});
} else if (result.skippedReason === "unmanaged-extension-present") {
summary.push({
label: "oh-my-pi",
status: "skipped",
detail: "Existing unmanaged tokentracker-notify extension left untouched",
});
} else {
summary.push({
label: "oh-my-pi",
status: "detected",
detail: "Passive reader (notify extension not written)",
});
}
}
}
}

Expand Down Expand Up @@ -1904,6 +1944,7 @@ module.exports = {
installLocalTrackerApp,
repairCodexNotifyIntegration,
repairRuntimeIntegrations,
applyIntegrationSetup,
};

async function probeFile(p) {
Expand Down
17 changes: 12 additions & 5 deletions src/commands/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const wsl = require("../lib/wsl-probe");
const { getWslMode, isInvalidWslMode, shouldProbeWsl, discoverWslHome } = wsl;
const { resolveInstallPaths } = require("../lib/install-resolver");
const { probeGrokHookState, resolveGrokHome } = require("../lib/grok-hook");
const { probeOmpHookState } = require("../lib/omp-hook");

function formatResolvedPaths(paths, filename) {
const active = [];
Expand Down Expand Up @@ -359,10 +360,11 @@ async function cmdStatus(argv = []) {
? fssync.existsSync(path.join(workbuddyHome, "workbuddy.db"))
: false;

// oh-my-pi — passive scan only (no hooks).
// oh-my-pi — passive scan + optional notify extension.
const ompAgentDir = resolveOmpAgentDir(process.env);
const ompInstalled = Boolean(ompAgentDir) && fssync.existsSync(path.join(ompAgentDir, "sessions"));
const ompFiles = ompInstalled ? resolveOmpSessionFiles(process.env) : [];
const ompHookState = await probeOmpHookState({ home, trackerDir, env: process.env });

// pi (@mariozechner/pi-coding-agent) — passive scan only (no hooks).
// Skip when its agent dir collides with omp's; sync would dedupe anyway.
Expand Down Expand Up @@ -771,8 +773,13 @@ async function cmdStatus(argv = []) {
workbuddy: workbuddyInstalled
? { installed: true, files: workbuddyFiles.length }
: { installed: false },
omp: ompInstalled
? { installed: true, files: ompFiles.length }
omp: ompInstalled || ompHookState.ompPresent
? {
installed: true,
files: ompFiles.length,
notify_extension: Boolean(ompHookState.configured),
notify_extension_path: ompHookState.extensionPath || null,
}
: { installed: false },
pi: piInstalled
? { installed: true, files: piFiles.length }
Expand Down Expand Up @@ -895,8 +902,8 @@ async function cmdStatus(argv = []) {
workbuddyInstalled
? `- WorkBuddy hooks: ${workbuddyHookConfigured ? "set" : "unset"} (${workbuddyFiles.length} session jsonl file${workbuddyFiles.length !== 1 ? "s" : ""} found, SQLite DB ${workbuddyDbExists ? "found" : "not found"})`
: null,
ompInstalled
? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found)`
ompInstalled || ompHookState.ompPresent
? `- oh-my-pi: passive reader (${ompFiles.length} session jsonl file${ompFiles.length !== 1 ? "s" : ""} found${ompHookState.configured ? ", notify extension: yes" : ", notify extension: no"})`
: null,
piInstalled
? `- pi: passive reader (${piFiles.length} session jsonl file${piFiles.length !== 1 ? "s" : ""} found)`
Expand Down
7 changes: 7 additions & 0 deletions src/commands/uninstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const { resolveOpencodeConfigDir, removeOpencodePlugin } = require("../lib/openc
const { removeOpenclawHookConfig } = require("../lib/openclaw-hook");
const { removeOpenclawSessionPluginConfig } = require("../lib/openclaw-session-plugin");
const { removeGrokHook } = require("../lib/grok-hook");
const { removeOmpHook } = require("../lib/omp-hook");
const { resolveTrackerPaths } = require("../lib/tracker-paths");

async function cmdUninstall(argv) {
Expand Down Expand Up @@ -104,6 +105,7 @@ async function cmdUninstall(argv) {
});
const openclawHookRemove = await removeOpenclawHookConfig({ home, trackerDir, env: process.env });
const grokHookRemove = await removeGrokHook({ home, trackerDir, env: process.env });
const ompHookRemove = await removeOmpHook({ home, trackerDir, env: process.env });

// Remove installed notify handler.
await fs.unlink(notifyPath).catch(() => {});
Expand Down Expand Up @@ -195,6 +197,11 @@ async function cmdUninstall(argv) {
grokHookRemove?.removed
? `- Grok Build hook removed: ${grokHookRemove.hookPath}`
: "- Grok Build hook: no change",
ompHookRemove?.removed
? `- oh-my-pi notify extension removed: ${ompHookRemove.extensionPath}`
: ompHookRemove?.skippedReason === "unmanaged"
? "- oh-my-pi notify extension: skipped (unmanaged file)"
: "- oh-my-pi notify extension: no change",
opts.purge ? `- Purged: ${path.join(home, ".tokentracker")}` : "- Purge: skipped (use --purge)",
...(machineIdSeedKept
? [`- Kept: ${machineIdSeedPath} (cloud device identity — a reinstall reuses the same device; delete it to fully reset)`]
Expand Down
Loading
Loading