james.maesandClaude Fable 5.1 8ed694cd2f cli: noun-verb grammar on the kernel, output formats, JMESPath, CRUD, api escape hatch
qqq <noun> <verb> for table (list, describe, query, count, get, create, update, delete, export),
process (list, describe, run) and job (list, get, logs, cancel, retry), with the 0.1 flat
commands kept as hidden aliases. Global --output json|table|text|csv|yaml, --json, --query
(JMESPath), --fields, --yes and --trace work before or after the subcommand. Filters take
--where, --group (AND/OR with parentheses), --or, --order, --filter-json, --print-filter and
--count; queries page with --limit, --skip and --all. qqq api <operationId> calls any
operation from the generated index (--path, --param, --body-json, --form, --bytes, --out).
Exit codes: 2 usage and bad filters, 3 auth, 4 unreachable or rate limited, 5 not found,
6 validation, 70 server error. The realtime stream can carry a session cookie for tail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 17:03:18 -05:00
2026-10-15 11:47:21 -05:00
2026-10-15 10:56:15 -05:00

qqq-cli

The reference command-line client for QQQ applications, and the kit for writing your own CLI, TUI or native client. Everything here talks to a QQQ server only through the UI API (/qqq/v1), the API the web UI uses too.

Package What it is
@qrun-io/qqq-cli qqq, the reference CLI: device-code sign-in, apps, tables, filtered queries, records, processes, jobs, and a live event stream
@qrun-io/qqq-ui-api-client A typed TypeScript client, generated from the merged UI API document (core plus extensions). It also does OAuth2 device-flow sign-in, token refresh, and reads the realtime (SSE) stream
@qrun-io/qqq-ui-api-conformance A black-box conformance suite you can run against any server, plus qqq-dev-idp, a local OIDC provider for testing native clients

The packages are published to GitHub Packages:

echo "@qrun-io:registry=https://npm.pkg.github.com" >> ~/.npmrc
npm install -g @qrun-io/qqq-cli@rc

Requires Node.js 20 or later and a QQQ server with UI API 1.6.0 or later (QQQ 4.1.0-RC.15 or later).

The CLI

$ qqq login --server https://admin.example.com
To sign in to https://admin.example.com, open:

  https://auth.example.com/device?code=ED84-CC03

and confirm the code ED84-CC03 (it expires in 10 minutes).
Waiting for approval...
Signed in to https://admin.example.com as darin@example.com.

$ qqq tables --app peopleApp
name     label     insert  edit  delete
-------  --------  ------  ----  ------
person   Person    yes     yes   yes
pet      Pet       yes     yes   yes

$ qqq query person --where "firstName ~ a" --where "id < 100" --order -id --limit 5
Id  First Name  Last Name  Annual Salary  ...
5   Morgan      Sample     $1,500,000.00
3   Casey       Sample     $300,000.00
(2 records)

$ qqq get person 1
$ qqq run greetInteractive --records 1,2 --value greetingPrefix=Hi
$ qqq jobs --all
$ qqq tail --channel table:person
Command UI API operations
login getAuthenticationMetaData, then the provider's device-authorization grant with values.nativeClientId
whoami getVersion, getMetaData
apps, tables [--app] getMetaData
query <table> [--where "field OP value"]... [--or] [--order [-]field] [--limit n] [--fields a,b] getTableMetaData, queryTable
get <table> <pk> getTableMetaData, getRecord
run <process> [--value k=v]... [--records ids] getProcessMetaData, initProcess, stepProcess, getProcessStatus
jobs [--all] listJobs
tail [--channel ...] openRealtimeStream, updateRealtimeSubscriptions (user:me, plus your channels)

Add --json for machine-readable output. Errors print the server's message, code and traceId.

Where things are kept:

  • Tokens go in ~/.config/qqq-cli/credentials.json (mode 0600), one entry per server. The CLI refreshes the access token at the provider before it expires, and once more on a 401.
  • QQQ_TOKEN overrides the stored token with any bearer token, such as a QQQ access token exchanged for a JWT, or a session UUID.
  • QQQ_SERVER sets the default server.

Every request carries X-QQQ-Client: cli/qqq-cli <version>, so the server's audits attribute it to the CLI.

Writing a client

import { createUiApiClient, unwrap, getNativeSignInConfig, deviceSignIn } from '@qrun-io/qqq-ui-api-client'

const config = await getNativeSignInConfig('https://admin.example.com')
const tokens = await deviceSignIn(config, (p) => console.log(`Open ${p.verificationUriComplete}`))
const api = createUiApiClient({ baseUrl: 'https://admin.example.com', token: () => tokens.accessToken, clientChannel: 'tui/my-tui 1.0' })

const page = await unwrap(api.POST('/qqq/v1/table/{tableName}/query', {
  params: { path: { tableName: 'person' } },
  body: { filter: { criteria: [{ fieldName: 'lastName', operator: 'STARTS_WITH', values: ['K'] }], limit: 20 } },
}))

Paths, parameters, bodies and responses are typed from the document. Read the QQQ docs before you write a client:

Mock server

npm run mock serves the merged document with Prism on http://127.0.0.1:4010, so you can build a client without a server. Responses come from the document's examples and schemas.

Conformance suite

qqq-ui-api-conformance --server https://admin.example.com              # public checks only
qqq-ui-api-conformance --server https://admin.example.com --token "$T"  # all read-only checks

The suite only reads. It never writes. It checks that:

  • the version and the served document agree;
  • every operation has a unique operationId and every $ref resolves;
  • every extension the server lists has operations;
  • requests without credentials or with forged tokens (alg: none, garbage) get a 401 with the error schema;
  • metadata, table metadata (with clientCapabilities), query, count, record, jobs and realtime responses validate against the document;
  • a 404 has the error schema, and X-Request-Id comes back as traceId.

Testing a client locally

qqq-dev-idp is a small OIDC provider for local development only: device grant, refresh and RS256 JWTs, with one issuer per client, as Authentik does it. Run it with the QQQ sample app:

npx qqq-dev-idp --port 9400 --client sample-app --client sample-cli --user you@example.com --groups admins --auto-approve

# in a qqq checkout (QQQ 4.1.0-RC.15 or later)
export OAUTH2_BASE_URL=http://localhost:9400/application/o/sample-app/ OAUTH2_CLIENT_ID=sample-app OAUTH2_CLIENT_SECRET=dev \
       OAUTH2_SCOPES="openid profile email offline_access" \
       OAUTH2_NATIVE_CLIENT_ID=sample-cli OAUTH2_NATIVE_CLIENT_BASE_URL=http://localhost:9400/application/o/sample-cli/
./mvnw -f qqq-sample-project/pom.xml compile exec:java -Dexec.mainClass=com.kingsrook.sampleapp.SampleJavalinServer -Dqqq.sample.port=8077

qqq login --server http://localhost:8077

Development

npm ci
npm run spec:merge -- <core openapi.yaml> <extension yaml>...   # or a server's /qqq/v1/openapi.json
npm run generate        # regenerate packages/ui-api-client/src/schema.ts
npm run build && npm test

Releases: push a tag v<client version>. The publish workflow publishes the three packages to GitHub Packages.

License

Apache-2.0

S
Description
Reference CLI for the QQQ UI API (/qqq/v1), with the generated TypeScript client, a mock server and a conformance suite for client authors.
Readme Apache-2.0
8.4 MiB
0 Stars 2 Watchers 0 Forks
Languages
TypeScript 93.3%
Shell 2.5%
JavaScript 2.1%
Python 2%
PowerShell 0.1%