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
16 changes: 12 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ installation, players, backend services, the site, or production deployment.
`passkeys_disabled` belong to that origin; the CLI cannot receive them and must
not pretend to map them. The mint call returns `invalid_request`,
`unauthorized`, `payment_required`, `rate_limited`, or `not_ready`.
Production does not send `payment_required` (HTTP 402) until LaunchCreditEnforcementAt
(2027-01-01 08:00 UTC / midnight PT).
- Writes carry `Idempotency-Key`. An exact retry returns the original link and
expiry for twenty-four hours, so a retry is safe and does not mint a second
live link.
Expand Down Expand Up @@ -423,12 +425,18 @@ installation, players, backend services, the site, or production deployment.
simply causes the server to derive `hvc1.*` instead.
- The default plan has no product storage cap (`account_content_bytes` and
`content_limit_bytes` are 0). A custom storage ceiling, when present, is
checked first and rejected with `quota_exceeded`. Remaining prepaid credit of
below 1 whole credit rejects billed `/api/v1` work with `payment_required`.
Keep the 1 GiB local check as a plan-independent transport bound. Keep
`quota_exceeded` and `payment_required` guidance pointing at `account show`.
checked first and rejected with `quota_exceeded`. Remaining prepaid credit is
a nonnegative whole integer; empty remaining displays `0`, never negative.
`account show` prints integer `credit_remaining` and does not debit.
Remaining below 1000 credits adds `credits_low` to envelope `warnings[]`.
Until LaunchCreditEnforcementAt (2027-01-01 08:00 UTC / midnight PT),
production fails open: billed `/api/v1` work is not rejected for empty
remaining and does not return HTTP 402. Empty remaining does not stop or
shut off screens in this window. After that instant, remaining below 1
whole credit rejects billed `/api/v1` work with `payment_required`. Keep
mapping 402 to `payment_required` in the client; do not weaken handling of
a real 402. Keep the 1 GiB local check as a plan-independent transport bound.
Keep `quota_exceeded` and `payment_required` guidance pointing at `account show`.
Screen toast and screenshot are exempt from the 1-credit API meter. Do not
add pay, Stripe, or x402 commands.
- Screenshotting is in v1. `screen screenshot <id>` blocks on a still WebP and
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,18 @@ days first. Filter with `--screen-id`, `--media-id`, and `--day YYYY-MM-DD`.
Those identifiers select the caller's own rows and are never a cross-account
lookup.

Authenticated responses may carry remaining prepaid credits as an integer.
Authenticated responses may carry remaining prepaid credits as a nonnegative
whole integer. Remaining never displays negative; empty remaining is `0`.
Remaining below 1000 credits adds a `credits_low` warning to the envelope
`warnings[]` array; the message includes the integer remaining. A 402
`payment_required` problem means remaining is below 1 credit and billed
control-plane commands are rejected. Both signals can appear together on a
402 envelope. `account show`, `agent status`, `agent disconnect --yes`,
`warnings[]` array; the message includes the integer remaining. Until
1 January 2027 08:00 UTC (midnight Pacific Time), production fails open:
billed commands are not rejected for empty remaining, and the server does
not return HTTP 402. Empty remaining does not stop or shut off screens in
this window. After that instant, remaining below 1 credit rejects billed
control-plane commands with `payment_required` (HTTP 402). The CLI still
maps a 402 to `payment_required` if the server sends one. Both
`credits_low` and `payment_required` can appear together on a 402 envelope.
`account show`, `agent status`, `agent disconnect --yes`,
`auth status`, `auth revoke --yes [--allow-lockout]`, `screen toast`,
`screen screenshot`, `compose catalog`, and `compose render` do not debit
that meter. Opening `events follow` costs 1 credit. There is no pay command.
Expand Down Expand Up @@ -307,7 +313,8 @@ dashboard origin, so the CLI never presents a link token and never sees
`dashboard_link_expired` or `dashboard_link_consumed`. What the mint call can
return is `invalid_request`, `unauthorized`, `payment_required`,
`rate_limited`, and `not_ready`; the CLI renders each with the server's own
detail and guidance.
detail and guidance. Production does not send `payment_required` (HTTP 402)
until 1 January 2027 08:00 UTC.

Production links require HTTPS. Local development has one exact exception:
`http://api.screenrig.localhost:8088` may return and open
Expand Down
23 changes: 23 additions & 0 deletions src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import assert from "node:assert/strict";
import { createCipheriv, createHash, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync } from "node:crypto";
import { PassThrough } from "node:stream";
import { readFileSync } from "node:fs";
import { mkdir, open, readFile, rename, chmod, stat, writeFile, rm } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "node:test";
import { EVENT_STREAM_BACKOFF_CAP_MS, EVENT_STREAM_BACKOFF_MS, USAGE, formatEventLine } from "./commands.js";
import { run, type CliRuntime } from "./main.js";
Expand Down Expand Up @@ -3123,6 +3125,27 @@ test("media upload warns on a low-information filename without blocking the uplo
}
});

test("customer-facing credit copy is fail-open until 1 Jan 2027", () => {
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
const documents: Array<[string, string]> = [
["USAGE", USAGE],
["README.md", readFileSync(path.join(repoRoot, "README.md"), "utf8")],
["AGENTS.md", readFileSync(path.join(repoRoot, "AGENTS.md"), "utf8")],
];
for (const [name, text] of documents) {
assert.match(text, /nonnegative/, `${name} must say remaining is nonnegative`);
assert.match(text, /credits_low/, `${name} must name the live credits_low warning`);
assert.match(text, /1000 credits/, `${name} must name the live 1000-credit warning threshold`);
assert.match(text, /1 Jan(?:uary)? 2027|2027-01-01/, `${name} must name the 1 Jan 2027 cutoff`);
assert.match(text, /fails open/, `${name} must say production fails open until the cutoff`);
assert.match(text, /not rejected for empty\s+remaining/, `${name} must say billed work is not rejected yet`);
assert.match(text, /payment_required/, `${name} must keep payment_required as the after-cutoff code`);
assert.match(text, /402/, `${name} must name HTTP 402 as after-cutoff, not current`);
assert.match(text, /does not stop or\s+shut off screens/, `${name} must not claim screens stop for empty remaining`);
}
assert.match(USAGE, /feedback list \[--kind bug\|feature\]/);
});

test("credits_low appends beside generic_filename instead of replacing it", async () => {
const transport = memoryBackend();
transport.extraResponseHeaders = { "ScreenRig-Credits-Remaining": "999" };
Expand Down
8 changes: 8 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ Commands:
feedback list [--kind bug|feature]
doctor [--repair-config]
version

Credits:
Remaining is a nonnegative whole number (never negative; empty remaining
displays 0). Below 1000 credits, authenticated responses may warn
credits_low. Until 1 Jan 2027 08:00 UTC (midnight PT), production fails open:
billed commands are not rejected for empty remaining and do not return
HTTP 402. After that instant, remaining below 1 credit is payment_required.
Empty remaining does not stop or shut off screens in this window.
`;

export interface CommandResult {
Expand Down
Loading