# VIABLE CLI `viable` is the human- and agent-friendly control plane for VIABLE Lab. It is a thin client: the Website Portal and Developer Platform APIs remain authoritative for authentication, permissions, validation, workflow, storage, and audit logs. Naming is intentionally consistent by role: **VIABLE CLI** is the product, `@viable-lab/cli` is its npm package, `viable` is the installed command, and `viable-cli-.tgz` is the versioned website download. ## For an agent setting up VIABLE When a person asks you to follow this guide, set up the CLI and its bundled Skill in **your execution environment**, then help them sign in. Do not make nontechnical users choose installation paths or copy terminal commands. 1. Check terminal access, outbound HTTPS, and `node --version` (22 or newer). Check `viable --version` if available. Install or update the CLI with the official command below; this workflow requires **0.3.6 or newer**. Never attempt sign-in with an older version. If prerequisites are unavailable, explain the specific limitation. Do not claim installation or sign-in worked. 2. Install the bundled `viable-cli` Skill using your host's supported location. Use the host you actually run in; do not ask the person to identify it. - Codex and hosts using `~/.agents/skills`: `viable agent install`. - Claude Code: `viable agent install --target claude`. - Kimi Code: `viable agent install --target kimi`. - Qwen Code: `viable agent install --target qwen`. For another host, check its current Skill installation instructions. Copy the complete bundled `skill/` folder (or the official source ZIP below) to its supported location as `viable-cli`, preserving references and metadata. Do not guess a location or claim automatic discovery where it is unsupported. If terminal commands work but filesystem Skills are unsupported, read the bundled `SKILL.md` and relevant references directly for this session and explain that persistent Skill installation is unavailable. 3. If the same Skill already exists, read its location first. This setup request permits refreshing **only** that `viable-cli` directory: use the same target with `--confirm viable-cli`. Never remove other Skills, follow a destination symlink, or install duplicate copies into legacy folders. Read the installed `SKILL.md` now and follow it. If the host needs a new session to discover it, explain this after setup; the current session can still read it directly. 4. Once per new operational session, run `viable update check --json` before starting a fresh sign-in. If an update is available, update the installed CLI and Skill with `viable update --with-skill --json`, read the refreshed Skill, then sign in. Reuse a valid login; do not recheck on every command or while resuming a pending device login. If the check fails, report it and continue with the installed supported version. Run `viable help --json`, then `viable auth status --json`. Reuse a valid session. For an agent that pauses between turns (including cloud agents), start sign-in with `viable auth login --device --start-only --json`. Show the returned verification URL and one-time code to the person and wait for them to approve it. After confirmation, run `viable auth login --resume --json`. Keep the pending transaction rather than starting another code. On a local desktop where the person is present, `viable auth login` opens browser sign-in. Never ask for passwords or tokens. 5. Run `viable help --json` again after sign-in. Report the installed CLI version, Skill location (or session-only instructions), and available services. Existing Developer users need no CMS account. Existing active Lab permissions may reveal CMS commands. Sign-in never registers service accounts, claims invitations, or grants roles. If access is unknown during an outage, retry status; do not treat it as absent membership. If registration is needed, explain the service's website path without registering on the person's behalf. Setup itself does not authorize content or account changes. A local installation does not install anything in a separate cloud environment. A chat without terminal access cannot execute this CLI. There is no universal VIABLE plugin to install; use a terminal-capable agent and its supported Skills. Official CLI installation: ```bash npm install --global --prefer-online https://www.viablelab.org/downloads/viable-cli.tgz ``` Official Skill source (for hosts needing manual filesystem installation): https://www.viablelab.org/downloads/viable-cli-skill.zip Before any operation, read the Skill's safety contract. Read the current object, preserve unrelated fields, and pass its `latestVersion` as `--expected-version` when required. Publish, delete, change access, or alter live placement only for an explicit request naming the action and target. Never print credential stores or API-key secret files. Installation is not permission to make business changes. ## Install VIABLE CLI requires Node.js 22 or newer, has no runtime dependencies, and can be installed without cloning a repository. The Portal generates a copyable URL for the environment currently open; the production form is: ```bash npm install --global --prefer-online https://www.viablelab.org/downloads/viable-cli.tgz viable --version ``` The stable distribution command will be `npm install --global @viable-lab/cli` after npm trusted publishing is configured. Maintainers can test the package from this monorepo with `npm install --global ./tools/viable-cli`; end users do not need to clone the website repository. ## Check your setup ```bash viable doctor # or: viable doctor --json ``` `viable doctor` checks the Node version, whether a newer release exists, whether the saved sign-in is valid, which services your account can reach, and whether each installed Skill is current. Every line is `ok`, `warn`, `FAIL`, or `skip`, with the command that fixes it. It only reads local files and makes GET requests, never prints credentials, and being offline is a warning, not a failure. Suggestions exit 0; only real problems exit 1 (Node older than 22, a credential file other users can read, or a saved sign-in that no longer works). With `--json`, `data.healthy` and the exit code say whether any check failed. ## Shell completion ```bash viable completion # tells you what to do for your shell viable completion install # saves the script where your shell loads it viable completion install --update-rc # also adds the needed lines to ~/.zshrc (or ~/.bashrc) ``` Run `viable completion` with no arguments and it detects your shell from `$SHELL` and says exactly what to run. `install` saves the script (zsh: `~/.zfunc/_viable`; bash: `$XDG_DATA_HOME/bash-completion/completions/viable`, usually under `~/.local/share`) and then reports what your shell's startup file still needs. It only changes that file when you add `--update-rc`, which appends one marked block, keeps everything already there, and does nothing if the block exists. For zsh the block is `fpath=(~/.zfunc $fpath)` and `autoload -Uz compinit && compinit`; if the file already runs `compinit`, it adds a `source` line after it instead of running `compinit` twice. Open a new terminal afterwards. Run `install` again after updating the CLI to refresh the saved script; `viable doctor` tells you when it is missing, not loaded, or out of date. A file at the target path that viable did not write is never overwritten. Completion covers commands, nested actions, and each command's flags, and is generated from the same table `help` prints, so it follows the CLI. It does not complete flag values. `viable completion ` prints the script instead of saving it, for people who manage their own shell configuration. ## Help and examples `viable help ` prints that command's options and one or two runnable examples; `viable help --json` returns the same as data. Dates in `--from` and `--to` are checked as described below. ## Sign in ```bash viable auth login viable auth status viable whoami ``` The browser completes VIABLE sign-in and returns the session only to a temporary random localhost listener. The current provider uses the public VIABLE Firebase application. The listener validates the callback origin, anti-forgery state, token issuer/audience, body size, and recent authentication. Credentials are stored in the operating-system config directory with owner-only permissions and never printed. CLI sign-in establishes your identity and discovers **existing** service accounts. It does not register CMS visitors, create Developer accounts or personal projects, claim invitations, or grant roles. First-time Developer users should sign in at https://developers.viablelab.org to register. Invited Lab members should activate their Website Portal identity once before using CMS commands. An external Developer user needs no CMS account. Before sign-in, `viable help` shows ordinary Developer commands and sign-in guidance. After sign-in it queries current permissions and adds the CMS and Developer administrative workflows your accounts allow. Use `viable developer --help` or `viable cms --help` for scoped help; agents should add `--json`. Run help again after changing roles. A registered visitor or suspended CMS account does not enable internal commands. Failed discovery is reported as unknown, never treated as permission or as a reason to register an account. No persistent permission cache is used. Help describes command workflows, not authorization to every action within a group. Every API call rechecks account status, roles, and object access. The CLI checks an existing-account discovery endpoint before business requests and uses `X-Viable-Account-Mode: existing` so account deletion between requests cannot silently register a replacement. Services without the discovery endpoint fail closed; the CLI never falls back to provisioning identity endpoints. Remote and headless environments use a device authorization instead of a localhost callback: ```bash viable auth login --device # --headless and --no-browser are equivalent aliases ``` Plain `viable auth login` selects this flow automatically in SSH sessions and Linux environments without a display. Use `--device` explicitly in agent instructions so the behavior remains deterministic. The CLI prints `https://www.viablelab.org/device` and a short, one-time code. Open that page in any browser, sign in, compare the displayed code and environment, then approve or deny the request. The CLI polls over HTTPS and exchanges a single-use Firebase custom token without putting any credential in the terminal, URL, clipboard, process arguments, or logs. Codes expire after ten minutes; early polling is slowed automatically, and Ctrl+C cancels the pending request. Device logins store a revocable VIABLE session instead of a Firebase refresh token; `viable auth logout` revokes it before removing local credentials. Revocation stops renewal immediately; an already-issued Firebase ID token may remain valid for up to one hour. Cloud agents that cannot keep one network-enabled process alive while a person approves the request should split the same device login across two commands: ```bash viable auth login --device --start-only # approve the displayed code in any browser viable auth login --resume ``` `--start-only` exits after securely saving the pending transaction. Running it again reuses the same unexpired code. `--resume` makes one short polling and exchange attempt from a fresh process; if approval is still pending, approve the request and run the same resume command again. The high-entropy device code is stored in an owner-only local file and is never printed. The file is bound to the configured VIABLE origin, expires with the ten-minute transaction, and is removed after success, denial, expiry, or logout. After approval, the revocable VIABLE session is written before the Firebase identity exchange. If a cloud policy interrupts that final exchange, the next `viable auth login --resume` retries safely from the stored session without another code. Device authorization is for remote sessions with a person present, including ChatGPT Work, Codex Cloud, Claude Code, SSH, and containers. Non-interactive CI should continue to use a separately managed short-lived `VIABLE_FIREBASE_TOKEN`; do not put that token in a prompt, command argument, log, or repository. Maintainers must apply the CMS D1 migrations and configure `FIREBASE_SERVICE_ACCOUNT_JSON` as a Cloudflare Pages secret in both Preview and Production. The key must belong to the `viable-lab` Firebase project; it is never a public runtime variable and must be rotated through Firebase/Google IAM. Migration `0017_cli_identity_sessions.sql` preserves device sessions while removing their dependence on CMS membership. Deploy the Developer discovery API and the website migration/auth routes before distributing CLI 0.3.6. Apply the API WAF allowlist's exact `GET /v1/cli/session` entry and verify an unauthenticated request reaches the Worker's 401 response before release. ## Output for people and agents People can run commands exactly as shown without `--json`; the CLI prints plain summaries or readable indented records. Agents should add `--json` so stdout contains one deterministic result object while diagnostics remain on stderr. ## Version and updates ```bash viable --version viable update check viable update viable update --with-skill viable agent refresh ``` Interactive commands check the website release manifest at most once every 24 hours and print a reminder only when a newer version exists. JSON output, CI, and non-interactive runs never receive an automatic notice. Set `VIABLE_NO_UPDATE_CHECK=1` or pass `--no-update-check` to disable the check. Updates are never installed silently. `viable update` downloads the versioned package over HTTPS, verifies its SHA-256 digest against the website manifest, then asks the existing npm installation to replace the global package. The next command uses the new version. The installed Skill is a copy made at install time, so it can fall behind the CLI. `viable agent install` records the CLI version and a fingerprint of the bundled Skill beside the copy. `viable help --json` lists each installed Skill as `current`, `stale`, `newer`, or `unknown` with the reason and the exact refresh command. A copy is `stale` only when the bundled Skill's content changed since it was installed, so a CLI release that did not touch the Skill never raises a false alarm. `newer` means the copy came from a later CLI than this one: update the CLI, and refreshing leaves it alone. `unknown` means no usable marker, as with copies installed before versions were recorded. Copies written by 0.3.9 and 0.3.10 hold only a version and are judged by it. `viable agent refresh` updates every copy that is already installed, and `viable update --with-skill` updates the CLI and then refreshes them with the new package. Refreshing never creates a copy where none exists, never touches other Skills, and skips anything that is not a plain `viable-cli` directory. The daily reminder names copies that are behind. ## Agent setup Open **CLI & Agent Skills** after signing in at https://developers.viablelab.org/dashboard/cli. Lab members also have setup instructions in the Website Portal. No Lab membership is needed to use the Developer installation page. Versioned downloads remain hosted on the official Lab website; downloading them does not sign in or create a CMS account. The CLI executes requests; its bundled Skill teaches an agent how to use it. Manual terminal users only need the CLI. For agents, install the CLI first, then its Skill in the same environment, and finally sign in: ```bash npm install --global --prefer-online https://www.viablelab.org/downloads/viable-cli.tgz viable agent install viable auth login viable help --json ``` `codex` and `generic` both write to `~/.agents/skills/viable-cli`, the current Codex user Skill location. `claude` writes to `~/.claude/skills/viable-cli`; `kimi` and `qwen` retain their host-specific targets. Existing Skills require `--confirm viable-cli` to refresh. Updating the CLI alone does not refresh an already-installed Skill. Start a new agent session if the Skill is not detected; an agent can also read the installed `SKILL.md` explicitly in the current session. Avoid installing a second copy with the same name in a legacy Skill directory. A Skill installed first can guide CLI setup: its instructions include the official address, Node.js requirement, and minimum CLI version. The standalone source is https://www.viablelab.org/downloads/viable-cli-skill.zip. Extract `skill/` and copy it, including references and metadata, to your host's Skill directory as `viable-cli`. This does not install the executable. Codex and supported ChatGPT desktop workflows can discover local filesystem Skills. A normal ChatGPT conversation needs a terminal-capable environment to run the CLI; copying files locally does not install them in a cloud environment. Cloud setup needs Node.js and outbound HTTPS in that environment, then resumable device sign-in. If filesystem Skill discovery is unsupported but shell commands work, run `viable agent prompt` and provide its instructions directly. A universal VIABLE plugin is not currently distributed. Plugin installation would be a separate distribution path; it would not grant VIABLE permissions. See the [official Skill guide](https://learn.chatgpt.com/docs/build-skills). Maintainers run `npm run package:cli` to create versioned archives, checksums, and guides, plus stable latest-download aliases and `viable-cli.json`. Publish them together on the Lab website. The Developer Portal reads that manifest at runtime; a CLI-only release requires no Developer Portal rebuild. Stable URLs revalidate their cached content; `--prefer-online` also asks npm to revalidate. Versioned URLs remain available for reproducible installations. ## Command model Use `viable help --json` as the current machine-readable catalog. Every success emits one JSON object on stdout with `--json`; progress and errors stay on stderr. Commands accepting `--file` also accept `--file -` for one JSON object on stdin. ### Website content All editable website types share one lifecycle: ```bash viable cms content types --json viable cms content search "learning analytics" --type news --limit 10 --json viable cms content get https://www.viablelab.org/news/example-story --json viable cms content export example-story --output ./story-history.json --json ``` Supported types are member profiles, news, products, grants, partnerships, group photos, and announcements, plus read/update compatibility for legacy blog records. Search ranks titles/slugs first and also checks complete payload text, topics, links, and media metadata. Lists are compact by default; exact reads include the current full body. Use `--full` only for full revision payloads. ```bash viable cms content create --type news --file story.json --json viable cms content update example-story --file story.json \ --expected-version 4 --change-summary "Refresh results" --json viable cms content submit example-story --expected-version 4 --json viable cms content review example-story --action publish \ --expected-version 4 --confirm example-story --json ``` Creating content makes a private draft. Reviews, historical revision restore, draft discard, archive/recovery, and permanent deletion are available under `cms content`. `cms news`, `cms profile`, and `cms members` are convenient specialized entry points. Upload JPEG, PNG, WebP, GIF, or PDF assets first, then use the returned public URL in the content payload: ```bash viable cms assets upload ./product.webp --item product-slug --json viable cms assets list --item product-slug --json ``` ### Sharing content In the Portal, open an item and choose **Share** to add/remove an existing active lab member. Recipients find drafts and publishing progress in **Shared with me**. No email is sent. The grant allows editing that item and reading its single-page analytics where available; it does not grant publishing, resharing, or whole-site Analytics access. Existing role/profile permissions still apply. CLI create/update accepts repeated `--collaborator ` values. On update, these **replace the complete collaborator list**: read the current detail first and preserve everyone who should keep access. Omit the flag for unrelated edits. Only the content owner or editorial administrators can change this list. The Portal's individual add/remove controls are preferable when several people are managing access concurrently. Sharing changes do not need a content revision. ### Private page reading analytics Version 0.3.3 adds the CMS Analytics report to the CLI, including every returned breakdown and revision-specific section metric: ```bash viable cms content analytics https://www.viablelab.org/news/example-story --days 30 --json viable cms content analytics example-story --days 7 --country DE --device mobile --json viable cms content analytics example-story --revision revision-id --json viable cms content analytics example-story --days 90 --format csv --output ./reading.csv --json viable cms content analytics example-story --output ./reading.json --json ``` References accept item IDs (including legacy `seed_` IDs), exact slugs, public URLs, or Portal content URLs. Use `--type news|product|member` to disambiguate slugs. `cms news analytics` and `cms members analytics` are scoped aliases. Grants and other content without an independent public page have no report. The API enforces the same private access as the CMS tab; the CLI does not grant additional access. Filters: `--days` defaults to 30 (1–36600); use `--range 1y|2y|3y|5y|all` or `--from YYYY-MM-DD --to YYYY-MM-DD` for calendar periods (UTC end exclusive); `--country` accepts two-letter country codes, `XX` (unknown), or `T1` (Tor); `--device` accepts desktop/mobile/tablet; `--revision` takes an ID from `report.revisions`. Omit a dimension flag for all values. With `--json`, `data.item` identifies the page and `data.report` contains summary, previous period, daily series, countries, devices, browsers, referrers, sources, campaigns, depths, sections, revisions, coverage, and collection status. No raw visitor records are returned. `activeMs` is milliseconds; sessions are temporary tab groupings, not unique people. Section averages use reached views, and reach uses `sectionViews` for `sectionRevision`. Date boundaries are UTC with an exclusive end; today is incomplete. `previous: null` means unavailable. `--output` saves a complete JSON report by default; `--format csv` exports the same CSV as the CMS, including all returned metrics and groups, coverage/filter metadata, daily dates (uncovered history remains null), and formula-safe cells. Files are created with owner-only permissions (`0600`) and never overwrite an existing path. Referrer/source/browser groups are capped at 250 and campaigns at 50 by the API. The command returns export-path metadata instead of the full report when saving. With the site analytics migration, retained facts remain queryable for at least five years, with automatic expiry disabled. Daily aggregates have no expiry. Already deleted dimensions cannot be recovered; coverage is explicit. Lifetime views are unfiltered. Signed-in readers, site analytics opt-outs, and detected bots are excluded. The current website collects first-party reading statistics even with DNT/GPC; these signals alone do not opt out. Use the website's privacy-policy control to disable collection in a browser. CLI 0.3.4 updates this guidance; collection behavior is determined by the deployed website, not the installed CLI version. Paused collection, unpublished pages, and no matching visits are reported separately. Deploy the matching API and migration before using this command against an environment. After upgrading the CLI, refresh an existing agent skill with `viable agent install --target generic --confirm viable-cli`. ### Whole-site analytics (CLI 0.4.0+) Site reports require the independent `analytics:site:read` capability (editor, admin or owner). Single-page contributor access does not grant whole-site access. These commands each call a server aggregate endpoint; overview does not list or fetch individual content reports. ```bash viable cms analytics overview --range 5y --json viable cms analytics pages --from 2026-10-01 --to 2026-10-11 --country US --json viable cms analytics landing-pages --source uf --range 1y --json viable cms analytics acquisition --range 3y --json viable cms analytics countries --campaign fall-2026 --range 5y --json viable cms analytics campaigns --content uf-story --range 1y --json viable cms analytics actions --action read_paper --page cms:news-id --json viable cms analytics journeys --range 30d --json viable cms analytics legacy-daily --range all --json viable cms analytics export --report pages --range 5y --format csv --output ./site-pages.csv --json ``` Reports also include `referrers`, `devices`, and `daily`. Quick ranges: `7d|30d|90d|1y|2y|3y|5y|all`. `--to` is **exclusive UTC**, unlike the Portal's inclusive Through input. Filters: `--page`, `--page-type`, `--country`, `--device`, `--channel`, `--source`, `--campaign`, `--content`, `--landing-page`, `--link`, `--referrer`, `--action`. Tags are normalized public labels, never private URLs or personal information. Use `--sort`, `--order asc|desc`, `--page-number`, `--limit`. Page/channel rankings support `--sort change` only when comparison is available. `legacy-daily` supports only date/page/type filters and refuses other dimensions. JSON `data` is the API response: schema 2, metric 2.0, UTC range, generated time, filters, coverage, comparison, summary, breakdowns, pagination, units and quality limitations. Sessions are distinct matching short tab sessions, **not people**; never sum page or daily sessions. `sessionStarts` counts matching first openings by UTC start day. `activeMs` is milliseconds; `averageActiveMs` is per view; rates are fractions. Engaged views and engaged sessions are distinct metrics. Actions count repeats; converted sessions count each qualifying session once. The goal filter changes the numerator, keeping matching eligible sessions as the conversion denominator. An outbound click is not a verified registration or use. Unknown old metrics are null/N/A. `status` distinguishes `no_data` and `incomplete_history`; unsupported filters return 400, permission denial 403, and missing API/schema produces `analytics_unavailable`. Do not fill uncovered history with zeroes or calculate growth when comparison is unavailable. Old unsegmented totals have a separate report; they cannot answer country × campaign. Exports fetch **all** aggregate pages, never raw visits or session IDs. A dataset generation change during paging fails the export rather than saving mixed data. CSV neutralizes spreadsheet formulas; files use mode 0600 and create-only writes. The JSON envelope remains on stdout; errors remain on stderr. Tracking link management is explicit mutation, separate from reports: ```bash viable cms analytics links list --json viable cms analytics links create --file ./uf-link.json --confirm uf-story --json viable cms analytics links update uf-story --file ./uf-link-v2.json --confirm uf-story --json viable cms analytics search-console --from 2026-10-01 --to 2026-10-11 --dimension query --json ``` Example `uf-link.json` (public labels only): ```json {"id":"uf-story","name":"UF research story","description":"Autumn research outreach","owner":"Lab editors","targetPage":"route:/research","source":"uf","medium":"referral","campaign":"fall-2026","content":"uf-news-story","placementUrl":"https://news.ufl.edu/story/","status":"active","notes":"Use only on this placement"} ``` Updates require the current `version` and complete metadata. Do not automatically retry a mutation after a timeout; read the link list first. Pausing disables the short redirect, but copied direct UTM URLs still open the public target. Redirect requests include bots/previews and are not readers. Never add UTM to internal links. Search Console is separate: Pacific date boundaries, Google impressions/clicks/ CTR/position, and `query|page|country|device` dimensions. `--start-row` continues Google pagination. A `not_connected` response is an honest configuration state; no live connection is implied. Google may omit anonymized queries and return only top rows. Never combine its clicks with first-party views or sessions. ### Portal operations The CLI exposes the same role-aware workflows as the Portal: - `cms users`, `invitations`, and `access` for workspaces, roles, profile assignment, invitations with welcome emails (CLI 0.5.3+: `invitations send`), the welcome guide for existing accounts (CLI 0.5.4+: `users welcome`), and access requests. `cms users graduate` (CLI 0.5.2+) moves someone to alumni access and their profile to Alumni. - `cms identities` for linking another verified sign-in identity and changing the primary identity. - `cms email` for @viablelab.org forwarding alias availability, provisioning, verification sync, and status. - `cms resources` for team posts, official Lab picks, pins, likes, attachments, and comments. - `cms files` (CLI 0.5.5+) for Lab files: `ls [folder/path]` lists folders or one folder's files and receipt totals, `upload --folder "Billing/OpenAI"` adds a file (with `--vendor`, `--date`, `--amount`, `--currency`, `--note` for receipt folders), and `download ` saves it without overwriting. - `cms meetings` (CLI 0.5.0+) for the lab meeting schedule: list and read meetings, claim a presenter slot (`signup next`), update or move your talk, withdraw, keep meeting notes, suggest and vote on topic ideas, choose email reminders, mark yourself away (`settings --away-until`), and print a private calendar feed URL. Organizers also `schedule` (with an audience), `update` (including `"status": "cancelled"` and `"applyTo": "following"`), `delete`, mark members `away`, and manage `organizers`. - `cms announcements` and `cms products` for explicit public placement. - `cms reviews`, `activity`, and `handbook` for queues, audit history, and role-aware guidance. Publishing an announcement does not display it automatically. A separate, confirmed placement command selects the `floating` or `product_alert` slot. ### Developer Platform For API requests, application code, model behavior, and building published Agents, read the maintained Developer API guide: https://developers.viablelab.org/agents/viablelab-api.md Use that guide for request shapes, authentication, scopes, streaming, and Agent graph contracts; check the current model catalog rather than inventing fields. CLI sign-in manages service accounts; application API requests use a scoped VIABLE API key. Reading documentation or writing integration code does not require CLI installation or sign-in. Never put API keys in client-side code, chat, or a repository. CLI agent administration remains permission-checked. ```bash viable developer me --json viable developer usage --from 2026-09-01 --to 2026-09-30 --json viable developer logs list --limit 25 --json viable developer logs list --key key_... --project proj_... --json viable developer usage --from 2026-09-01 --to 2026-09-30 --key key_... --json viable help developer logs export --json # every flag, type, default, meaning viable developer logs stats --key key_... --group-by model,day --json viable developer logs facets --status-class 4xx,5xx --json # operations, platforms, models, keys that failed viable developer logs list --client-request-id order-1234 --json viable developer logs list --status-class 5xx --from 2026-10-09T13:00:00Z --to 2026-10-09T14:00:00Z --json viable developer usage --group-by model,day --tz America/New_York --from 2026-09-01 --to 2026-09-30 --json viable developer logs export --no-bodies --columns timestamp_utc,model,status,tokens_used --format csv --output ./usage.csv viable developer agents schema --json # node types, availability, required config, limits, rules viable developer projects list --json viable developer projects members suggest proj_... --query "ada lovelace" --json viable developer projects transfer get proj_... --json viable developer credits --json ``` Dates in `--from` and `--to` are UTC, inclusive, and written `YYYY-MM-DD`. Log commands (`logs list`, `export`, `stats`, `facets`, and project logs) also take an exact time with a zone, such as `2026-10-09T13:00:00Z` or `2026-10-09T06:00:00-07:00`, for a window shorter than a day; a time without a zone is refused because the API would read it as the whole day. A date that is not a real day, or a `--from` later than `--to`, is refused before anything is sent. When only `--to` is given, the range starts 30 days before it, so `--to 2026-08-01` reads the month ending then; with no dates, lists and usage keep the server's default window and exports and summaries use the 30 days ending today. `--all-time` cannot be combined with dates. Summarize before you export. `developer logs stats --group-by key,model,day` groups by one to three of `key`, `model`, `day`, `status`, `path`, `provider`, `operation`, and `platform`. It streams the matching logs in bounded batches, never requests request or response bodies, keeps only the groups, and reports requests, errors (HTTP 400 and above), tokens, cost, and average seconds per group plus totals. It stops with an error rather than reporting a partial answer when more than `--max-rows` rows match. Its token and cost totals agree with `developer usage` for the same key and range. Its request and error counts can be higher, because the usage totals leave out requests whose log was never completed, such as some calls the client cancelled; the `groups` that `developer usage` returns are counted from the logs and show the same difference. `developer logs facets` returns, for the same filters, the operations, platform IDs, models, endpoints, statuses, and keys that occur, with counts (at most 50 values each; each list ignores its own filter), so you can see which exact values to filter or group by. When you do need rows, add `--no-bodies` to leave bodies out and `--columns` to keep only the columns you name; a typical four-column export is a fraction of a percent of the size of a full one. List rows leave bodies out unless `--include-payload` is set. `developer logs list`, `export`, `stats`, and `facets` filter by `--key`, `--project`, `--path`, `--model`, `--provider`, `--operation`, `--status`, `--status-class` (such as `4xx,5xx` for every error), `--platform`, `--client-request-id`, `--request-id`, `--agent-id`, `--agent-run-id`, and `--agent-only`; `developer projects logs` takes the same flags except `--project`. `--platform`, `--operation`, and `--client-request-id` match the `X-Platform-ID`, `X-Operation-Type`, and `X-Request-ID` headers an application sends; the bundled Skill's Developer reference lists what each records. An unknown status class is refused locally, since the API would ignore it and match every row. `developer usage` and `developer projects usage ` take `--group-by` with one or two of `day`, `key`, `model`, and `endpoint` (at most 366 days), and return `groups` with requests, errors, input and output tokens, and cost beside the unchanged totals and daily rows. `--tz America/New_York` reads `--from` and `--to` as local days and groups by local day; `--model` and `--endpoint` narrow only the groups and therefore need `--group-by`. `--key` narrows the totals and daily rows locally and the groups on the server. `developer credits` includes `spend`, a 30-day daily cost series with `burn_rate_usd_per_day`. `developer agents runs get ` returns the full trace, including per-step `duration_ms` and `started_offset_ms`, the entry point, the originating `request_id`, the key name and prefix, the version, recorded branch and loop decisions, and the background job link. Project roles are **Owner** (manages secrets, members, ownership, and the project lifecycle), **Editor** (edits project details, key limits, and agents; member management depends on the owner's sharing policy), and **Viewer** (inspects resources, activity, and members; secrets and changes are restricted). Ownership transfers use `viable developer projects transfer `. The recipient must be an active project member and has 7 days to accept; on acceptance the project's API keys and future billing move to them and the previous owner becomes an editor. `offer`, `accept`, and `cancel` first show these consequences and change nothing until re-run with the printed `--confirm` value (`:` to offer, the transfer ID otherwise). Agent test runs bill an API key you name: `viable developer agents test-run --key --message "..." --confirm `. Add `--dry-run` to validate and check the key for free. The key must be active, have `agents:execute`, belong to the agent's project or be granted access, and allow the models the agent uses. Knowledge bases and hosted chat pages (CLI 0.6.0+) take an agent from your materials to a chat students or patrons can use. `viable developer knowledge create|add-site|add-files|add-text|get|search` builds and checks a knowledge base (`search` is free). `viable developer agents chat-page set --key --confirm ` turns on a published agent's chat page, run and paid for by the key you name, and prints its link, a script tag for any website, LibGuides, or LibAnswers, and an inline frame for Canvas; `set` changes only the flags given (including `--avatar`, `--tagline`, `--theme`, `--teaser`, and per-visitor limits since 0.6.1), and `off` takes the link and every embed offline. `viable developer agents memories list|forget` reviews or deletes what an agent remembers about one end user. Project members, project usage/logs/keys, agent definitions/runs/key access, Developer users, grants, administrative projects, audit history, and tier policies are available under their corresponding groups. API key secrets never appear in stdout. Creation and reveal require a new output path; the file is created with mode `0600` and is never overwritten: ```bash viable developer keys create --file key-policy.json \ --output ./viable-api-key.txt --confirm create-key --json ``` Use `viable developer keys open` when a human should manage keys in the web UI. Request-log exports follow the selected filters and fetch every matching row in batches of 1,000 by default (up to 5,000 per request). The final CSV or JSONL is written to a new owner-only file; `--limit` controls batch size, not the total number exported. Payload-heavy batches may contain fewer rows; the CLI follows the server cursor until completion. The date window still defaults to the last 30 days: use explicit `--from` and `--to` dates for historical data. The window is fixed when the command starts. Log exports require the companion batch-aware API; failures discard the temporary file and never report a partial export as complete. Counts are from live data, not a transactional snapshot. ## Safety and exit codes - Read immediately before an update and send the returned `latestVersion`. - Preserve fields the user did not request changing. - Draft before publishing; require the exact confirmation target for sensitive writes. - Never retry an uncertain mutation. Read authoritative state first. - Output files are create-only so existing local data is not overwritten. - The APIs recheck all permissions; CLI confirmations are an additional guard, not an authorization mechanism. Exit codes: `2` usage, `3` authentication, `4` permission, `5` validation/local input, `6` conflict, `7` network. API errors include a request ID when present. ## Development ```bash npm test --prefix tools/viable-cli npm pack --prefix tools/viable-cli --dry-run VIABLE_CMS_URL=http://localhost:3000 \ VIABLE_DEVELOPER_API_URL=http://localhost:8787 \ viable whoami --json ``` Firebase Web configuration in the package is public application metadata, not a secret. Protected APIs still require a valid signed identity and enforce their own role and object-level rules.