basic extension to mimic claude code behaviour
  • TypeScript 63.3%
  • JavaScript 36.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
outage.sh 9db31a38b4 feat: count input + output tokens; add e2e test harness
Token count now sums input_tokens + output_tokens from the provider
usage payload (cache read / write excluded -- they're internal
accounting, not real spend). Streaming char estimate and high-water
mark fallbacks preserved for ollama / minimax-m3 and last-resort.

Add e2e-test.mjs / e2e-harness.ts: a tsx-driven harness that loads
the real extension source via a mock ExtensionAPI, fires synthetic
events (session_start, input, before_provider_request, message_update
chunks, after_provider_response, agent_end, tick), and asserts the
rendered row at each phase. Three scenarios: --provider (Anthropic /
OpenAI / Google style), --ollama (streaming only), --hybrid (both).
14 assertions, all green.
2026-08-17 16:23:47 +02:00
src feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
.gitignore feat: initial omp extension — time + tokens per turn 2026-08-17 03:21:07 +02:00
e2e-harness.ts feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
e2e-test.mjs feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
LICENSE feat: initial omp extension — time + tokens per turn 2026-08-17 03:21:07 +02:00
package-lock.json feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
package.json feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
README.md feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
smoke.mjs feat: count input + output tokens; add e2e test harness 2026-08-17 16:23:47 +02:00
tsconfig.json feat: initial omp extension — time + tokens per turn 2026-08-17 03:21:07 +02:00

omp-time-token-spent

Oh My Pi extension that renders a single status line below the editor input box, Claude Code style: elapsed time on the left, tokens spent close beside it. Refreshes every second on a managed tick. Tokens are scoped per prompt -- they reset every time you press Enter on a new prompt, never accumulate across the session.

The widget sits between the input box and the agent's last reply, so it reads as part of the live UI rather than as a chat line in the transcript scrollback. The spinner label that omp renders alongside the working circle is left untouched. After the AI finishes responding, the row stays visible showing the final time + tokens until you press Enter on the next prompt.

Install

From a clone of this repo:

omp plugin install /absolute/path/to/omp-time-token-spent

omp will link the directory as a plugin and load src/index.ts automatically on next start (or after /reload).

Remote install (requires bun)

If you have Bun installed and your omp build accepts the github: scheme:

omp plugin install github:git.pandem.fr/outage.sh/omp-time-token-spent

Manual install (last resort)

Add the extension path to ~/.omp/agent/config.yml:

extensions:
  - /absolute/path/to/omp-time-token-spent/src/index.ts

Then omp --reload.

What you'll see

A single line below the editor input box, updated every second. Example after 7 seconds of work on the current prompt and 2,459 tokens consumed by it:

7s    2.46k tokens
  • Ns -- wall time since you pressed Enter on the current prompt.
  • N tokens -- tokens consumed by this prompt, summing input + output (Anthropic / OpenAI / Google provider-reported deltas, or streaming char estimate for ollama, or context-growth delta as last resort). Cache read / write tokens are excluded (internal accounting detail, not real spend). Formatted N, N.NNk (under 10k), Nk (under 1M), N.NNM (over 1M).
  • (N calls) -- only shown when the turn made more than one provider request.

When agent_end fires, the timer freezes at the final value and the row stays on screen with the frozen tally. The next input event resets every counter and the row jumps to 0s 0 tokens immediately, then ticks up again as the new turn runs.

Why a widget, not a spinner label

omp renders working text alongside the spinner via setWorkingMessage, and footer slots via setStatus. Both are reserved surfaces that change the model's own UI text. We use setWidget(..., { placement: "belowEditor" }) so the time/tokens row sits in its own dedicated band between the input box and the last AI reply -- not in the transcript scrollback, and never leaking into the model's prompt.

Events used

omp event What we do
input Snapshot ctx.getContextUsage().tokens so subsequent tick reads show only tokens spent by the current prompt. Resets all per-turn counters.
before_provider_request Increment callCount for the (N calls) suffix.
after_provider_response Sum provider-reported usage deltas (input_tokens / output_tokens / cache_read / cache_write).
session_start Register a 1-second tick via ctx.setInterval that updates the widget.

Testing

Pure-logic smoke tests (no runtime needed):

node smoke.mjs

13 cases cover: idle, in+out summed, ollama streaming, provider-trumps-streaming, hwm fallback, multi-call, minutes, input-reset, freeze-on-agent-end, streaming floor, frozen streaming, pure input counting, cache tokens ignored.

End-to-end tests with a mock ExtensionAPI driving the real extension source via tsx:

node e2e-test.mjs             # default: provider scenario (Anthropic / OpenAI / Google)
node e2e-test.mjs --provider  # usage deltas -- expects input + output summed
node e2e-test.mjs --ollama    # streaming only -- expects char-based estimate
node e2e-test.mjs --hybrid    # both signals -- provider deltas should win

Each scenario drives the full event sequence (session_start -> input -> before_provider_request -> message_update chunks -> after_provider_response with usage -> tick -> agent_end -> tick) and asserts the rendered row at each phase.

Privacy

  • Reads no environment variables.
  • Reads no API keys, tokens, hostnames, or file paths beyond what omp already passes in its own event payloads.
  • Writes nothing to disk.
  • Sends nothing off-host.

License

MIT -- see LICENSE.