Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -7,7 +7,7 @@ Browse and install curated extensions from **Campus → Settings → Extensions*
## Available extensions

- [Usage Limits](extensions/io.flutterflow.campus.usage-limits/) — view Claude
Code and Codex usage in one Campus dashboard.
Code and Codex usage in independent compact Campus cards.

## Contributing

Expand Down
17 changes: 17 additions & 0 deletions extensions/io.flutterflow.campus.usage-limits/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## 2.0.0

- Replaced the combined dashboard with the locally developed independent
Claude and Codex usage cards.
- Replaced the Codex process and Claude external-action collectors with
origin-exact, prefix-bounded Anthropic and ChatGPT connector requests backed
by Campus-owned local credentials.
- Rendered update labels with the owner's current local offset instead of UTC;
reset labels remain relative so daylight-saving transitions stay correct.
- Added a visible in-flight refresh state that replaces stale metrics and bars
with fixed low-contrast skeleton rows.
- Bounded provider labels and usage rows and kept raw account/credential/error
material out of shared state.
- This release replaces the persisted `usage.dashboard` surface. Existing 1.0
dashboard tiles must be removed and recreated as individual Claude and Codex
cards after updating.

## 1.0.0

- Added a shared Usage Limits dashboard for normalized Claude Code and Codex
Expand Down
168 changes: 80 additions & 88 deletions extensions/io.flutterflow.campus.usage-limits/README.md
Original file line number Diff line number Diff line change
@@ -1,133 +1,125 @@
# Usage Limits

`io.flutterflow.campus.usage-limits` is a schema-v3 authoring example for one
shareable Campus dashboard containing normalized Claude Code and Codex usage
metrics. It deliberately has no HTTP connector, OAuth flow, bearer token, API
key, or undocumented network call.
`io.flutterflow.campus.usage-limits` is a schema-v3 Campus extension with the
same two-card experience as the locally developed Usage Limits package. It
contributes independent **Claude Usage Limits** and **Codex Usage Limits**
launchers, each rendered as a compact 420-pixel canvas object.

The package uses the current schema-v3 generic-resource and external-action
contracts. Keep `schemaVersion: 3`; do not replace the local host seams with a
web request.
The extension has no process, filesystem, native-helper, external-action, or
arbitrary-network authority. It declares two origin-exact, prefix-bounded
read-only HTTP connectors and publishes only bounded display values into shared
state.

## Data flow

### Codex
### Claude

The `codex` resource is a generic process grant scoped to the exact executable
name `codex`. Its coordinator:
The extension requests:

1. asks the host to spawn `codex app-server` without a shell;
2. performs the app-server `initialize` handshake;
3. sends `account/rateLimits/read` and `account/usage/read` JSON-RPC requests;
4. bounds stdout to 256 KiB and every JSON-RPC line/read to 64 KiB;
5. projects only displayable counters into shared state; and
6. terminates and waits for the child on every path.
```text
GET https://api.anthropic.com/api/oauth/usage
anthropic-beta: oauth-2025-04-20
```

The app-server reuses the owner's existing local Codex account session. The
extension never receives an OAuth token or calls an OpenAI HTTP endpoint.
Campus resolves the existing local Claude Code credential for that exact
origin and attaches it inside the host broker. Extension JavaScript never sees
the bearer value. The projection keeps at most twelve usage windows: a bounded
label, rounded used percentage, active state, and reset label.

### Claude
The manifest authorizes `GET` beneath the `/api/oauth/usage` path prefix,
including descendants and bounded query parameters; the shipped guest requests
only the path shown above with no query.

`externalActions.claudeStatus` is an owner-only, 256 KiB-bounded ingress mapped
to the ordinary `ingestClaudeStatus` action. Claude Code writes its status-line
JSON to the configured command's stdin, so the adapter is the generic Campus
invocation command itself:
### Codex

Do not replace a working status-line renderer with the quiet Campus ingestion
command: `--quiet` deliberately emits nothing on success, so using it as the
sole `statusLine` would leave the line blank. Instead, point Claude at a small
owner-managed wrapper which reads stdin once, replays the exact bytes to Campus
as a best-effort side effect, and then runs the existing renderer:
The extension requests:

```sh
#!/bin/sh
umask 077
payload_file=$(mktemp "${TMPDIR:-/tmp}/campus-claude-status.XXXXXX") || exit 1
trap 'rm -f "$payload_file"' EXIT HUP INT TERM
cat >"$payload_file" || exit 1
campus extension invoke io.flutterflow.campus.usage-limits claudeStatus \
--quiet <"$payload_file" >/dev/null 2>&1 || true
/absolute/path/to/existing-status-line-renderer <"$payload_file"
```text
GET https://chatgpt.com/backend-api/wham/usage
```

Then preserve the existing `statusLine` object and change only its command to
the wrapper's absolute path. If there is no existing renderer, choose one
first; do not configure quiet ingestion alone. A Campus failure never replaces
or suppresses renderer output. Invocation requires exactly one active Usage
Limits runtime; zero or multiple instances fail closed. The action keeps only
the model display name, bounded context-window counters/percentages, and
bounded session cost/duration metrics.
Campus resolves the existing local Codex login for the exact `chatgpt.com`
origin. The projection keeps the bounded plan label, limit-reached flag, credit
summary, and at most twelve primary/secondary usage windows.

The manifest authorizes `GET` beneath the `/backend-api/wham/usage` path prefix,
including descendants and bounded query parameters; the shipped guest requests
only the path shown above with no query.

Origin matching is exact. A manifest cannot request or influence the local
credential source, and a different host receives no local-tool credential.

Campus integration owns invocation of the external action. This package does
not install or rewrite Claude Code configuration and does not read a transcript
or working directory.
## Updating from 1.0

Version 2.0 replaces the combined `usage.dashboard` surface with independent
`claude.card` and `codex.card` surfaces. Remove an existing 1.0 dashboard tile
and recreate the desired cards after approving the update.

## Local time and refresh state

Connector receipts carry the host-authored `receivedAt` instant and the
owner's east-positive `utcOffsetMinutes`. The guest uses those values to render
the update label in the owner's current local time. Reset labels stay relative,
which remains correct across daylight-saving transitions without pretending a
single receipt-time offset is a full time-zone database. It never uses the
sandbox's pinned `Date.now()` or claims a viewer-local timezone; an older host
that omits the offset shows `Updated just now` rather than falling back to UTC.

Every refresh first publishes `status: "loading"`. While the network effect is
in flight, the renderer replaces stale percentages, progress fills, credits,
and warning copy with low-contrast skeleton rows and changes the action label
to **Refreshing…**. `scene.v1` intentionally has no guest timer or animation
lane, so the skeleton uses a fixed sheen rather than pretending to provide an
animated shimmer.

## Shared projection

Only the following normalized visible values enter `dashboard`, a 64 KiB
`shared.document` pool:
Only these normalized display values enter the 128 KiB `usage` shared-document
pool:

| Source | Shared values |
| --- | --- |
| Codex | plan label, limited flag, at most eight named usage windows, used percentage, reset time, window duration, credit summary, and bounded aggregate token/streak statistics |
| Claude | model display label, context used/remaining percentage, bounded context/input/output counters, session cost, and session/API duration |
| Claude | bounded window label, integer used percentage, active state, relative reset label, local update label |
| Codex | bounded plan label, limit-reached flag, bounded credit summary, integer used percentage, window label, relative reset label, local update label |

Account ids, email, auth state, tokens, raw RPC errors, arbitrary response
fields, Claude session ids, transcript paths, cwd/workspace data, and the raw
status-line payload are dropped before a state write. Teammates can view and
interact with the rendered tile, but both collection actions are owner-only.
Account IDs, email, credentials, tokens, raw connector bodies/errors, paths,
and arbitrary provider fields are not copied into state. Collection actions
are owner-only; room participants may see the normalized shared card.

## Source layout

- `src/read_codex_usage.js` is the only process-effect module.
- `src/codex_protocol.js` is pure bounded JSON-RPC framing.
- `src/codex_usage.js` and `src/claude_status.js` are allowlist-only parsers.
- `src/model.js` owns the canonical shared projection.
- `src/render.js` is a finite pure `scene.v1` renderer.
- `src/main.js` only coordinates resource binding, actions, state, and surface
activation.
- `src/main.js` coordinates state, actions, fixed connector requests, and both
surfaces.
- `src/model.js` bounds and normalizes provider receipts and formats host-owned
clock data.
- `src/card_ui.js` contains shared finite `scene.v1` card/skeleton builders.
- `src/render_claude.js` and `src/render_codex.js` are pure card renderers.

## Test

Run the dependency-free pure tests:
Run the dependency-free focused tests from the registry root:

```sh
node --test \
extensions/io.flutterflow.campus.usage-limits/test/usage_limits.test.mjs
```

The tests exercise hostile extra fields, bounds, JSON-RPC correlation, process
cleanup, unavailable-source behavior, shared-state redaction, coordinator
wiring, and finite rendering.
The suite covers the authority ceiling, exact origins and bounded path prefixes, local-time
formatting, provider projections, row/label bounds, redaction, failure states,
loading skeletons, finite unique scene keys, and coordinator wiring. It never
contacts a real account.

## Generate and validate

The source-version Campus tooling supports this schema-v3 manifest. Regenerate
the inert bindings after manifest identifier changes, then validate a clean
staging package.

The registry source directory is an authoring tree: `README.md`, `CHANGELOG.md`,
and `test/` are not included in the production `.campusext`. The publisher
captures only:

```text
campus.extension.json
src/*.js
src/*.mjs
src/campus.generated.d.mts
```

The registry's normative source, archive, digest, authority, and catalog check
is:
The registry source is an authoring tree: `README.md`, `CHANGELOG.md`, and
`test/` are excluded from the production archive. The registry's normative
source, archive, digest, authority, and catalog check is:

```sh
python3 tool/registry.py validate --root . --json
```

When changing manifest identifiers, regenerate the inert bindings with the
source-version Campus CLI in a `work_canvas` checkout before submitting the
source pull request.

Validation does not grant the process resource, enable the package, install a
Claude status-line adapter, or execute extension code. Publication is a
separate generated catalog/artifact change.
source pull request. Validation does not grant connector access, install or
enable the package, execute code, or publish a release.
Loading
Loading