# qqq-cli The reference command-line client for [QQQ](https://github.com/QRun-IO/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`](packages/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`](packages/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`](packages/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: ```sh 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 ```console $ 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 [--where "field OP value"]... [--or] [--order [-]field] [--limit n] [--fields a,b]` | `getTableMetaData`, `queryTable` | | `get
` | `getTableMetaData`, `getRecord` | | `run [--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 `, so the server's audits attribute it to the CLI. ## Writing a client ```ts 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: - [`UI-API-CLIENTS.md`](https://github.com/QRun-IO/qqq/blob/develop/docs/UI-API-CLIENTS.md) covers authentication, errors, widgets, processes, capabilities and validation. - [`UI-API-CHANGELOG.md`](https://github.com/QRun-IO/qqq/blob/develop/docs/UI-API-CHANGELOG.md) covers versioning. ## Mock server `npm run mock` serves the merged document with [Prism](https://github.com/stoplightio/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 ```sh 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: ```sh 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 ```sh npm ci npm run spec:merge -- ... # 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`. The `publish` workflow publishes the three packages to GitHub Packages. ## License Apache-2.0