Skip to content

feat: add external integration (JSON export file + show --json/--threshold)#105

Open
vmvarela wants to merge 10 commits into
slkiser:mainfrom
vmvarela:feat/external-integration
Open

feat: add external integration (JSON export file + show --json/--threshold)#105
vmvarela wants to merge 10 commits into
slkiser:mainfrom
vmvarela:feat/external-integration

Conversation

@vmvarela
Copy link
Copy Markdown
Contributor

@vmvarela vmvarela commented May 28, 2026

Closes #102

Summary

Adds two complementary surfaces for consuming quota data from external tools — no live network fetches, all reads from the existing per-provider disk cache.

What changed

New: show --json / --threshold / --provider
opencode-quota show gains three new flags:

  • --json — emits a QuotaExport JSON document to stdout instead of human-readable text. Reads exclusively from disk cache, never triggers a network fetch.
  • --threshold <pct> — exit 1 if any enabled provider is below <pct>% remaining; exit 2 when no provider returned an ok status. Requires --json.
  • --provider <id> — filters the output to a single provider.
    New: TUI background export writer
    When export.enabled is true, the TUI writes a QuotaExport file to disk after each 60 s background refresh. The file is written atomically (writeJsonAtomic). Write errors are fire-and-forgotten and never affect TUI rendering.
    New config fields
    | Field | Default | Meaning |
    |---|---|---|
    | export.enabled | false | Write export file after each background refresh |
    | export.path | "" | Export path; empty = $XDG_CACHE_HOME/opencode/quota-export.json; ~/ expanded |
    New types
    QuotaExport, QuotaExportEntry, QuotaExportProvider in src/lib/quota-export-types.ts — the shared JSON schema consumed by both surfaces.

Files

Area Files
Schema & builder quota-export-types.ts, quota-export.ts
Cache read-through quota-state.ts (readCachedProviderResult, ignoreExpiry)
Config types.ts, config.ts
CLI cli-show.ts, opencode-quota.ts
TUI tui-runtime.ts, tui.tsx
Docs README.md (External integration section + JSON schema + examples)
Tests lib.quota-export-types.test.ts, lib.quota-export.test.ts, quota-state.test.ts, tui-runtime.test.ts, lib.cli-show.test.ts

Tests

All new and existing tests pass.

@vmvarela vmvarela marked this pull request as draft May 28, 2026 12:19
@vmvarela vmvarela marked this pull request as ready for review May 29, 2026 09:06
- Drop --cached entirely (it only aliased --json, which is already
  cache-only by design); remove it from code, usage text, and README.
- Merge the duplicated --threshold / --threshold= parse branches.
- Remove the two empty 'Warn but accept' blocks for --threshold > 100.
- Fix README: --json is cache-only (no network); export write errors
  are silently ignored, not logged.
- Sync SHOW_USAGE with --json/--threshold options.
@vmvarela vmvarela changed the title feat: add external integration (JSON export file + show --json/--cached/--threshold) feat: add external integration (JSON export file + show --json/--threshold) May 29, 2026
vmvarela added 8 commits May 29, 2026 11:26
Satisfies issue slkiser#102 acceptance criterion: a failed export write must log
a warning and never affect TUI rendering. The fire-and-forget catch now
emits console.warn instead of swallowing the error silently. Docs updated.
A user with onlyCurrentModel:true would compute a different
quota-state cache key than the TUI background writer (which
always uses onlyCurrentModel:false, no session). The CLI --json
path was reading under a key nobody wrote under, producing
'unavailable' for every provider despite fresh cache data.

Now the CLI normalizes its provider context the same way the
TUI export writer does, so it reads the entries the TUI wrote.
This fixes the core external-integration use case for anyone
with onlyCurrentModel:true in their config.
- Remove lib.quota-export-types.test.ts (119 lines): tested JSON.stringify/parse
  behavior, which is runtime-standard and already validated by TypeScript
- Consolidate resolveExportPath tests: 4 separate tests → 1 with multiple assertions
- Remove trivial/redundant tests in lib.quota-export.test.ts:
  * 'omits resetAt when entry has no resetTimeIso' (covered by test 1)
  * 'sets cacheAgeSeconds to 0 when no ok/error providers exist' (edge case)
  * 'propagates fromCache flag' (already verified in test 1)
  * 'handles multiple providers with mixed statuses' (redundant with tests 1, 4, 5)
- Remove duplicate test in tui-runtime.test.ts:
  * 'does not call buildQuotaExport when export is disabled' (identical to test 1)
- Consolidate lib.cli-show.test.ts:
  * Remove redundant '--json output includes all expected QuotaExport schema fields'
  * Consolidate 4 --threshold validation tests into 1 with multiple assertions
  * Remove trivial '--threshold > 100 is accepted' test

Total: 40 insertions, 283 deletions + 119 (deleted file) = ~362 lines removed.
All 1040 tests still pass (3 pre-existing pricing failures unrelated to this PR).
…threshold exit 2

- Remove 'stale' from CachedProviderRead: computed in two branches of
  readCachedProviderResult but never read by any production consumer
  (buildQuotaExport only uses .hit/.result/.timestamp). The field was
  introduced by this PR and has no external users.
- Collapse the two now-redundant 'stale: false'/'stale: true' tests in
  quota-state.test.ts into a single cache-hit test; disk-read coverage
  is retained by 'populates inMemoryCache from disk'.
- Strip leftover 'stale:' keys from quota-export mock setups.
- Document the existing exit-code 2 ('no cached quota to compare') in
  SHOW_USAGE and at the return site, distinguishing it from exit 1
  ('below threshold') per issue slkiser#102.

Net: 4 insertions, 70 deletions. All 1039 non-pricing tests pass.
…visible home content; reduce tests

- Fix: register homeBottom in resolveTuiSurfaceRegistration when
  export.enabled is true, even if compact status and announcements
  are both disabled. Otherwise the export file is never written.
- Guard: writeTuiQuotaExportIfEnabled now respects config.enabled === false.
- Docs: update bin usage to show --json/--threshold flags and exit-2 docs.
- Tests: consolidate 3 buildQuotaExport mapping tests into one (percent
  entry + value-kind + window omission); add test for export-only
  homeBottom registration.

Net: 49 insertions, 68 deletions. 1038 tests pass (3 pre-existing
pricing failures unrelated).
- Stop deriving machine-readable 'window' from entry.name (single-window providers with window-like names like 'Monthly Premium Requests' would emit a spurious window that consumers branch on). Derive from entry.label only.

- Add sanitizeSingleLineDisplaySnippet to provider error messages before writing to disk, matching the redaction guarantee the non-JSON CLI path already provides.

- Add test regression entry proving a name-matches-but-label-doesn't entry gets no window.

- Update error test to assert ANSI escapes and control characters are stripped.
Both the CLI show --json and TUI periodic writer need the same cache-key-aligning normalization (onlyCurrentModel: false, showSessionTokens: false, session: {}). Duplicating this in two places is a latent footgun — if one drifts, exports silently become all-unavailable. Extract into createExportProviderContext(runtime) so the contract lives in one documented place.
- Collapse two near-identical --threshold exit-0/exit-1 tests into an it.each table (~35 lines saved)

- Drop writeQuotaExport re-throw test from unit file (error propagation asserted at integration layer in tui-runtime)

- Trim wiring-only data-shape assertions from tui-runtime export test (mapping owned by unit file); keep TUI-specific path-resolution assertion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature]: optional quota state export to JSON file for external tool integration

1 participant