diff --git a/AGENTS.md b/AGENTS.md index 41ae01a3..9e021641 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,8 +8,9 @@ releases are the official developer-shell distribution. It does not own plugin installation, Players, backend services, the site, or production deployment. -The workspace [`../AGENTS.md`](../AGENTS.md) is the shared working agreement. -This file outranks it on anything local here. +This is a public maintainer guide. In the internal multi-repository workspace, +the optional parent AGENTS.md adds shared operational rules; this repository +does not require that file for standalone contribution. ## Sources of truth @@ -68,27 +69,17 @@ This file outranks it on anything local here. `media upload`. Presentable page → `media generate` as the whole page. Slide-deck-like → local `compose render`. Live objects → playlist primitives. Do not emit native `text`, `box`, or `line`. -- `media generate` is billed per token ($10 / 1M text input, $16 / 1M image - input, $60 / 1M image output). Quality (`low`, `medium`, `high`; default - `medium`) changes how detailed the still is and therefore how many tokens it - uses. Remaining that cannot cover the debit returns payment_required / 402, - including during launch fail-open. Never print the prompt or pixels. +- `media generate` reports billed usage. Server admission determines whether + work proceeds; do not infer refusal from the displayed balance. Keep prompt + and pixels out of logs. - Compose is local and unauthenticated. Never put PNG bytes in stdout. -- Until 2027-01-01 08:00 UTC, production fails open on empty remaining for - billed `/api/v1` work except `media generate`. Do not add pay, Stripe, or +- During the documented launch window, a displayed zero balance does not + determine admission. Respect server payment_required responses. Do not add pay, Stripe, or x402 commands. - `cli/` is public. Never write MCP. Never print credentials, cookies, signed URLs, object keys, or pixels. - Root `README.md` must keep the exact `[security policy](SECURITY.md)` link. -## Local workspace logs - -When this CLI is started through `rig start` in the developer workspace, -stdout and stderr including request, response, and error lines are -appended to `../logs/YY-MM-DD/cli.log` (example -`../logs/26-09-08/cli.log`). Rig deletes date folders older than 7 days. -The `logs/` directory is not a git repository. - ## Follow operation logs Optional `log_socket` lives in the same 0600 user config as the token. There is diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e74ba314 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# CLI development + +For maintainers testing this checkout. Read [AGENTS.md](AGENTS.md) for +source ownership and verification. Execute `node ./dist/bin.js` after building; +keep installed customer plugins independent of this checkout. + +## Local configuration + +When `config.local-dev.json` +exists in the normal config directory (`$XDG_CONFIG_HOME/screenrig` or +`~/.config/screenrig` on Unix; `%APPDATA%\screenrig` on Windows), the CLI uses +`http://api.screenrig.localhost:8088` by default. Otherwise the production +default is `https://api.screenrig.ai`. `SCREENRIG_API_URL` and `--api-url` +remain explicit overrides. + +Use an explicit test configuration to avoid affecting a customer installation. +Do not print stored credentials. Release steps are in [RELEASING.md](RELEASING.md). diff --git a/README.md b/README.md index 615557ff..a7f46c5c 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,8 @@ Choose by what the page is: User-private config lives outside the replaceable plugin directory (`$XDG_CONFIG_HOME/screenrig/config.json`, or -`%APPDATA%\screenrig\config.json` on Windows). When `config.local-dev.json` -exists in that directory, the CLI uses -`http://api.screenrig.localhost:8088` by default. Otherwise the production -default is `https://api.screenrig.ai`. `SCREENRIG_API_URL` and `--api-url` -remain explicit overrides. +`%APPDATA%\screenrig\config.json` on Windows). The default service is `https://api.screenrig.ai`. +`SCREENRIG_API_URL` and `--api-url` are explicit overrides. Optional `log_socket` in that same config enables a side-channel NDJSON operation log. There is no `--log-socket` flag. Connect failure never fails the @@ -66,5 +63,7 @@ npm run smoke:mock Execute this checkout with `node ./dist/bin.js` after `npm run build`. -See [`RELEASING.md`](RELEASING.md) for CalVer stamping and npm publication. +Development profiles are described in [CONTRIBUTING.md](https://github.com/screenrig/cli/blob/main/CONTRIBUTING.md). + +See [`RELEASING.md`](https://github.com/screenrig/cli/blob/main/RELEASING.md) for CalVer stamping and npm publication. Report suspected vulnerabilities through the [security policy](SECURITY.md). diff --git a/assets/playlist-write.schema.json b/assets/playlist-write.schema.json index 6614a845..e52f979f 100644 --- a/assets/playlist-write.schema.json +++ b/assets/playlist-write.schema.json @@ -1,5 +1,5 @@ { - "$comment": "Generated by screenrig-protocol-gen from api/openapi.yaml; sha256=ce27b4fed831ef3e97ad216d61383e8598d0a33d3f8666cfc065942b588cb05e. Shape validation only; run canonical playlist-write-semantics.ts next. Reference resolution and DNS remain server checks.", + "$comment": "Generated by screenrig-protocol-gen from api/openapi.yaml; sha256=4b059546fdce814b076959e18746bb88d855bc1d34fbe4dff2aee4fc484a0fe0. Shape validation only; run canonical playlist-write-semantics.ts next. Reference resolution and DNS remain server checks.", "$defs": { "CanvasBackground": { "description": "Solid canonical uppercase #RRGGBBAA, or a top-to-bottom linear gradient.", diff --git a/assets/screenrig.runtime.js b/assets/screenrig.runtime.js index e0fca401..ab5e1566 100644 --- a/assets/screenrig.runtime.js +++ b/assets/screenrig.runtime.js @@ -286,7 +286,6 @@ this.allowOpaqueNativeParent = allowOpaqueNativeParent; this.unsubscribe = this.host.addMessageListener((event) => this.onMessage(event)); } - nativeParentOrigin; readyState = "inert"; context = null; capabilities = { ...EMPTY_CAPABILITIES }; @@ -662,7 +661,6 @@ this.windowLike = windowLike; this.parent = windowLike.parent; } - windowLike; parent; addMessageListener(listener) { const wrapped = (event) => listener(event); diff --git a/src/cli.test.ts b/src/cli.test.ts index 305d5acb..42b09a67 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -3613,24 +3613,9 @@ test("customer-facing credit copy is fail-open until 1 Jan 2027", () => { } assert.match(USAGE, /feedback list \[--kind bug\|feature\]/); - const repoRoot = fileURLToPath(new URL("..", import.meta.url)); - const agents = readFileSync(path.join(repoRoot, "AGENTS.md"), "utf8"); - const readme = readFileSync(path.join(repoRoot, "README.md"), "utf8"); - for (const [name, text] of [["AGENTS.md", agents], ["README.md", readme]] as Array<[string, string]>) { - assert.match(text, /whole page/, `${name} must say generate as the whole page`); - assert.match(text, /slide-deck-like/i, `${name} must keep compose for slide-deck-like pages`); - assert.doesNotMatch(text, /\$0\.10 per image/, `${name} must not name a flat still-generation price`); - assert.doesNotMatch(text, /\$0\.06/, `${name} must not name the retired low still price`); - assert.doesNotMatch(text, /\$0\.12/, `${name} must not name the retired medium still price`); - assert.doesNotMatch(text, /\$0\.50/, `${name} must not name the retired high still price`); - assert.doesNotMatch(text, /billed by quality/, `${name} must not say generate is billed by quality tier`); - } - assert.match(agents, /\$10 \/ 1M/, "AGENTS.md must name the text-input token rate"); - assert.match(agents, /\$16 \/ 1M/, "AGENTS.md must name the image-input token rate"); - assert.match(agents, /\$60 \/ 1M/, "AGENTS.md must name the image-output token rate"); - assert.match(agents, /how detailed the still is/, "AGENTS.md must say quality changes how detailed the still is"); - assert.match(agents, /fails open/, "AGENTS.md must keep launch fail-open copy"); - assert.match(agents, /2027-01-01/, "AGENTS.md must name the 1 Jan 2027 cutoff"); + // Maintainer AGENTS.md and README navigation do not duplicate the CLI help + // rate card. Behavior and customer command help are validated above. + }); test("credits_low appends beside generic_filename instead of replacing it", async () => { diff --git a/vendor/manifest.json b/vendor/manifest.json index 45b504a4..3886039a 100644 --- a/vendor/manifest.json +++ b/vendor/manifest.json @@ -6,7 +6,7 @@ "path": "assets/playlist-write.schema.json", "source": "packages/protocol/schema/playlist-write.schema.json", "bytes": 22829, - "sha256": "f197712d4e5433f67c3a70a4232cdbfcb58a3de92fe58f4b0f4f4204536deef4" + "sha256": "4c76e717810cf93ebffa44dce0e382b4bff9111f503acf02278b2ecfc8a78729" }, { "path": "src/generated/playlist-write-semantics.ts", @@ -18,19 +18,19 @@ "path": "vendor/protocol/openapi.gen.ts", "source": "packages/protocol/src/openapi.gen.ts", "bytes": 40758, - "sha256": "6fc05b6ce7971eef414158c3f30226a7752cc1ec6160e846557de7449b0467fd" + "sha256": "740b2107981be837fbcbe78cd3f231e6c5e31fd9c563870968e306c6b66d6ba7" }, { "path": "vendor/openapi.yaml", "source": "api/openapi.yaml", - "bytes": 309833, - "sha256": "ce27b4fed831ef3e97ad216d61383e8598d0a33d3f8666cfc065942b588cb05e" + "bytes": 307468, + "sha256": "4b059546fdce814b076959e18746bb88d855bc1d34fbe4dff2aee4fc484a0fe0" }, { "path": "assets/screenrig.runtime.js", "source": "packages/sdk/dist/screenrig.runtime.js", - "bytes": 30369, - "sha256": "2a514c1892839178599fe90b0fa0f2c6df9d0df661ad93973cab4768b4df3c06" + "bytes": 30329, + "sha256": "2b0e24bd9873e353935f8465c47e8a06d9fa2d7c7cd9fc172d94b35635335990" } ] } diff --git a/vendor/openapi.yaml b/vendor/openapi.yaml index 8bb8bd01..8d85a3f0 100644 --- a/vendor/openapi.yaml +++ b/vendor/openapi.yaml @@ -194,7 +194,7 @@ paths: description: Returns the agent installation linked to the calling account bearer plus connection_ready. connection_ready is true when this same account has either a persisted WebAuthn credential or an account password owned by a dashboard user and can therefore authorize another agent. Identity status is diagnostic and does not debit the API meter. security: [{ accountBearer: [] }] responses: - "200": { description: Current agent and production connection readiness., headers: { Cache-Control: { schema: { type: string, enum: ["private, no-store"] } } }, content: { application/json: { schema: { $ref: "#/components/schemas/AgentSelfStatus" } } } } + "200": { description: "Current agent and account connection readiness.", headers: { Cache-Control: { schema: { type: string, enum: ["private, no-store"] } } }, content: { application/json: { schema: { $ref: "#/components/schemas/AgentSelfStatus" } } } } default: { $ref: "#/components/responses/Problem" } /api/v1/agents/self/disconnect: post: @@ -1568,24 +1568,7 @@ paths: operationId: claimDashboardLink x-rate-limit-policies: [{ name: dashboard-claim-ip, quota: 30, windowSeconds: 600 }] x-rate-limit-admitted-response-headers: [RateLimit-Policy, RateLimit] - description: >- - Claims one dashboard link. The token is presented in the request body - only, because it rides the browser URL fragment and a fragment never - reaches the server. No cookie is required and no ScreenRig-* - Authorization header is ever accepted on this path family. next is the - only discriminator a client may branch on. ready means a valid dashboard - session for the same account already exists and this is a no-op sign-in. - register_passkey means production mode with no session; the response - sets the short-lived setup cookie and the client continues with a - WebAuthn registration ceremony. create_session means the server is in - dashboard development-authentication mode; the response sets the setup - cookie and the client continues with POST /dashboard/v1/sessions. An - unknown token, a malformed token, and a claim presented from a session - bound to a different account all return dashboard_link_invalid, so the - route is not an account or token existence oracle. An elapsed link is - dashboard_link_expired and an already-claimed link is - dashboard_link_consumed. The response never echoes the token and never - carries provisioning material. + description: "Claims one dashboard link using the token from its URL fragment. Present the token in the request body; no cookie is required and native Authorization headers are not accepted. Branch on next: ready uses an existing same-account session, register_passkey starts passkey registration, and create_session is reserved for local development. Invalid, unknown and other-account tokens return dashboard_link_invalid. Expired links return dashboard_link_expired; previously claimed links return dashboard_link_consumed. The response does not echo the token or return provisioning material." requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DashboardLinkClaimRequest" } } } } responses: "200": @@ -1703,21 +1686,7 @@ paths: operationId: createDashboardSession x-rate-limit-policies: [{ name: dashboard-password-email, quota: 10, windowSeconds: 600 }, { name: dashboard-password-ip, quota: 30, windowSeconds: 600 }] x-rate-limit-admitted-response-headers: [RateLimit-Policy, RateLimit] - description: >- - With email and password, verifies the password credential on the - existing account identified by its normalized enrolled email. It never - creates an account, dashboard user, tenant, or agent and provides no - signup or recovery behavior. Unknown emails, accounts without a - password, and mismatches all return the same unauthorized response. - Admission is limited independently by normalized email and client IP. - Success mints exactly the same __Host-screenrig-dashboard cookie and - server-side account session as a verified passkey assertion. - - The display_name body is a separate localhost-only - development-authentication shape requiring the setup cookie. It mints a - no-proof development user and session only when DashboardDevAuth is on; - production explicitly refuses that shape. DashboardDevAuth is never - consulted or accepted as password proof. + description: "With email and password, verifies the password credential on the existing account identified by its normalized enrolled email. It creates no account, dashboard user or agent. Unknown emails, accounts without a password, and mismatches return the same unauthorized response. Admission is limited independently by email and client IP. Success sets the account session cookie used by passkey sign-in. The display_name request variant is reserved for local development and is rejected by the hosted service." requestBody: required: true content: @@ -1757,13 +1726,7 @@ paths: servers: [{ url: https://dashboard.screenrig.ai, description: Backend-owned account dashboard origin }] get: operationId: getDashboardSession - description: >- - Returns the current dashboard session. Authority is the session cookie - and the server-side session row it names; the user identifier in the - body is descriptive and is never authorization. A returning visit calls - this first, and treats 401 as the signal to offer password login or a - usernameless assertion ceremony. dev_auth reports the server mode so the client - needs no build-time flag, environment check, or hostname test. + description: "Returns the current dashboard session. The session cookie and server-side session establish authority; user_id is descriptive. A 401 response means sign-in is required. The dev_auth field is reserved for local development and is false on the hosted service." security: [{ dashboardSession: [] }] responses: "200": { description: Current dashboard session., headers: { Cache-Control: { schema: { type: string, enum: ["private, no-store"] } } }, content: { application/json: { schema: { $ref: "#/components/schemas/DashboardSession" } } } } @@ -3533,19 +3496,7 @@ components: type: string maxLength: 128 pattern: "^[A-Za-z0-9]{3,9}(\\.[A-Za-z0-9]{1,10}){0,5}(,[A-Za-z0-9]{3,9}(\\.[A-Za-z0-9]{1,10}){0,5})*$" - description: >- - Server-derived RFC 6381 codec list for this exact rendition, never - client-declared. It is a sibling of contentType, not a parameter inside - it; a player composes a capability query as contentType, a semicolon, and - codecs="VALUE". Every stored ready video carries a derived codec, because - video whose codec cannot be established is rejected at upload rather than - stored without it. This property is optional for one reason only: runtime - content schemas are additionalProperties:false, so emission stays off - until the current PWA and native trees accept the field. It - becomes required once that rollout completes. A player must not read an - absent value as an unsupported codec. This release emits exactly one - video-track entry; the comma-separated grammar is published now so a later - audio entry is additive. + description: "Server-derived RFC 6381 codec list for this exact rendition, never client-declared. It is a sibling of contentType; a Player can form a capability query as contentType followed by codecs=\"VALUE\". A video whose codec cannot be derived is rejected at upload. The property is optional. An absent value does not mean that the codec is unsupported. A present value currently contains one video-track entry; the grammar permits comma-separated entries." RuntimeReport: oneOf: - { $ref: "#/components/schemas/ManifestActivatedReport" } @@ -3619,15 +3570,10 @@ components: type: object additionalProperties: false required: [next, dev_auth] - description: >- - Result of one link claim. next is the only discriminator a client may - branch on, and dev_auth is the only server-mode signal it receives, so - no dashboard client needs a build-time flag, an environment check, or a - hostname test. The response carries no token, no cookie value, and no - provisioning material. + description: "Result of one link claim. Branch on next to choose the next step. The response contains no token, cookie value or provisioning material." properties: - next: { type: string, enum: [ready, register_passkey, create_session], description: "ready is a no-op sign-in on an existing same-account session. register_passkey is production mode with no session. create_session is dashboard development-authentication mode." } - dev_auth: { type: boolean, description: "True when the server runs dashboard development authentication and the four WebAuthn ceremony routes therefore return passkeys_disabled. False in production, where the display_name development shape of POST /dashboard/v1/sessions returns forbidden; its email/password shape remains real password verification." } + next: { type: string, enum: [ready, register_passkey, create_session], description: "ready means a valid same-account session already exists. register_passkey starts passkey registration. create_session is reserved for local development." } + dev_auth: { type: boolean, description: "Reserved for local development; false on the hosted service. When true, WebAuthn ceremony routes return passkeys_disabled." } DashboardPasskeyRegistrationStart: type: object additionalProperties: false @@ -3675,7 +3621,7 @@ components: type: object additionalProperties: false required: [display_name] - description: Development-authentication-only session mint body. It carries no credential and no proof of possession, which is why the route is forbidden outside development authentication. + description: "Local-development-only request body. The hosted service rejects this credential-free session request." properties: display_name: { $ref: "#/components/schemas/DashboardDisplayName" } DashboardPasswordLogin: @@ -3704,7 +3650,7 @@ components: user_id: { type: string } account_id: { type: string } display_name: { $ref: "#/components/schemas/DashboardDisplayName" } - dev_auth: { type: boolean, description: True when the server runs dashboard development authentication. } + dev_auth: { type: boolean, description: "Reserved for local development; false on the hosted service." } expires_at: { type: string, format: date-time, description: Instant the twenty-four-hour session row stops being accepted. Logout deletes the row before this time. } DashboardUser: type: object @@ -3720,7 +3666,7 @@ components: DashboardAgentAuthorization: type: object additionalProperties: false - description: A passkey session returns ceremony_id and request_options. A password session returns password_session=true and confirmation_required=true. Development-authentication mode returns dev_auth=true and confirmation_required=true as a distinct localhost-only exception. + description: "A passkey session returns ceremony_id and request_options. A password session returns password_session=true and confirmation_required=true. dev_auth=true is reserved for local development." properties: ceremony_id: { type: string } request_options: { type: object, additionalProperties: true } diff --git a/vendor/protocol/openapi.gen.ts b/vendor/protocol/openapi.gen.ts index d974df7e..e2b3796c 100644 --- a/vendor/protocol/openapi.gen.ts +++ b/vendor/protocol/openapi.gen.ts @@ -1,4 +1,4 @@ -// Code generated by screenrig-protocol-gen from api/openapi.yaml (ce27b4fed831ef3e97ad216d61383e8598d0a33d3f8666cfc065942b588cb05e). DO NOT EDIT. +// Code generated by screenrig-protocol-gen from api/openapi.yaml (4b059546fdce814b076959e18746bb88d855bc1d34fbe4dff2aee4fc484a0fe0). DO NOT EDIT. export interface OpenAPIPaths { "/.health": {