voroa-cli 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -2
  3. package/package.json +41 -4
  4. package/src/agent.js +95 -0
  5. package/src/api.js +552 -0
  6. package/src/commands/db.js +99 -0
  7. package/src/commands/deploy.js +130 -0
  8. package/src/commands/domains.js +122 -0
  9. package/src/commands/env.js +143 -0
  10. package/src/commands/lifecycle.js +40 -0
  11. package/src/commands/link.js +92 -0
  12. package/src/commands/login.js +104 -0
  13. package/src/commands/logs.js +304 -0
  14. package/src/commands/services.js +72 -0
  15. package/src/commands/ssh.js +46 -0
  16. package/src/commands/status.js +50 -0
  17. package/src/commands/tokens.js +81 -0
  18. package/src/commands/workspace.js +63 -0
  19. package/src/config.js +108 -0
  20. package/src/errors.js +53 -0
  21. package/src/flags.js +32 -0
  22. package/src/index.js +212 -0
  23. package/src/interactive.js +34 -0
  24. package/src/link.js +72 -0
  25. package/src/mcp/build.js +72 -0
  26. package/src/mcp/kernel/index.js +59 -0
  27. package/src/mcp/kernel/logger.js +28 -0
  28. package/src/mcp/lib/config.js +72 -0
  29. package/src/mcp/lib/credentials.js +51 -0
  30. package/src/mcp/lib/http.js +136 -0
  31. package/src/mcp/lib/response.js +14 -0
  32. package/src/mcp/lib/reversibility.js +33 -0
  33. package/src/mcp/reversibilityMap.json +77 -0
  34. package/src/mcp/tools/_action.js +49 -0
  35. package/src/mcp/tools/_ids.js +42 -0
  36. package/src/mcp/tools/_shape.js +318 -0
  37. package/src/mcp/tools/actions.js +94 -0
  38. package/src/mcp/tools/getBuildLog.js +39 -0
  39. package/src/mcp/tools/getDeployHistory.js +42 -0
  40. package/src/mcp/tools/getRuntimeLogs.js +44 -0
  41. package/src/mcp/tools/getServiceStatus.js +41 -0
  42. package/src/mcp/tools/getUsage.js +34 -0
  43. package/src/mcp/tools/listDatabases.js +25 -0
  44. package/src/mcp/tools/listDomains.js +33 -0
  45. package/src/mcp/tools/listServices.js +20 -0
  46. package/src/mcp/tools/propose.js +124 -0
  47. package/src/mcp/tools/reads.js +756 -0
  48. package/src/mcp/tools/writes.js +632 -0
  49. package/src/mcpTools.js +292 -0
  50. package/src/output.js +145 -0
  51. package/src/prompt.js +229 -0
  52. package/src/region.js +5 -0
  53. package/src/resolve.js +64 -0
  54. package/src/session.js +270 -0
  55. package/src/status.js +199 -0
  56. package/src/stepup.js +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Web Tech Digital Work
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,248 @@
1
- # Temporary Holding Version
1
+ # voroa-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <!--
4
+ ZERO RUNTIME DEPENDENCIES - and that is a decision, not an oversight.
5
+
6
+ This package installs onto machines we do not control: a laptop, a CI image, a colleague's
7
+ Windows box. Two entries in the project's known-bugs log are exactly that failure mode:
8
+
9
+ bug #2 - `npm install` died building a native module (node-gyp / "could not find Visual
10
+ Studio") because the installed Node had no prebuilt binary for it.
11
+ bug #11 - the same package fell back to compiling from source inside a slim Docker image
12
+ that had no Python and no build tools, so the image would not build at all.
13
+
14
+ A CLI that cannot be installed is worse than a CLI that is missing a nicety. So: no native
15
+ modules, no node-gyp, no prebuild-install, and no transitive supply chain to audit. HTTP is
16
+ the built-in `fetch`, prompts are `node:readline`, tests are `node --test`. Node >=20 gives
17
+ us all three in the standard library.
18
+
19
+ If a future change wants a dependency, the bar is that question again: what happens on the
20
+ machine where it does not compile?
21
+
22
+ PUBLISHING: released through .github/workflows/publish-cli.yml (manual trigger, dry-run by default, npm
23
+ trusted publishing - no stored token). The docs pages `docs-site/content/cli.md` and `cli-reference.md`
24
+ flip from `status: coming-soon` only AFTER `npm view voroa-cli` resolves, never before (coming-soon
25
+ honesty: we do not describe something a reader cannot get).
26
+ -->
27
+
28
+ Deploy and inspect Voroa services from the terminal.
29
+
30
+ ## Install
31
+
32
+ Needs Node 20 or newer.
33
+
34
+ ```
35
+ npm install -g voroa-cli
36
+ voroa --help
37
+ ```
38
+
39
+ Or without installing: `npx voroa-cli --help`.
40
+
41
+ ## Sign in
42
+
43
+ ```
44
+ voroa login # emails you a 6-digit code
45
+ voroa login --password # email + password instead
46
+ ```
47
+
48
+ The session token is written to `~/.voroa/config.json` with owner-only permissions (`0600`
49
+ on Linux and macOS; on Windows the file inherits your user profile's ACL, which the Node
50
+ runtime cannot narrow further). The token is never printed, never passed on a command line
51
+ and never included in an error message.
52
+
53
+ **A stored session is bound to the endpoint that issued it.** If you point the CLI somewhere
54
+ else with `--api-url` or `VOROA_API_URL`, the stored token is deliberately *not* sent there
55
+ and the command stops with a message naming the endpoint your session belongs to. That flag
56
+ is one word in a command you might paste from anywhere, and a live session token is not
57
+ something to hand to whatever host it names. To use a different endpoint, sign in to it
58
+ (`voroa login --api-url <url>`) or pass `VOROA_TOKEN` explicitly.
59
+
60
+ For CI, set `VOROA_TOKEN` instead of signing in — it is an explicit choice by whoever runs
61
+ the command, so it works against any endpoint. Use an **API token** created in the dashboard
62
+ (it starts with `voroa_cat_`): it is scoped, revocable, expires, and is pinned to one workspace,
63
+ so `--workspace` does not apply to it. A login session in a CI secret is a full-power login with
64
+ none of those limits. A token reaches only what its scopes allow; a command outside them fails
65
+ with "not available to an API token". Live build streaming is session-only, so
66
+ `deploy --wait` with a token polls instead. `VOROA_API_URL` overrides the API endpoint
67
+ (so does `--api-url`, which is mainly useful against a local backend).
68
+
69
+ Signing in as a different account clears the workspace you had selected, so a workspace id
70
+ from one account is never sent on another's behalf.
71
+
72
+ ## Commands
73
+
74
+ ```
75
+ voroa services list the services in the active workspace
76
+ voroa status <service> one service's live state
77
+ voroa logs <service> tail the running container's output
78
+ voroa logs <service> --follow keep tailing
79
+ voroa logs <service> --build the latest build's log (add a deploy id for an older one)
80
+ voroa deploy <service> trigger a deploy
81
+ voroa deploy <service> --wait trigger a deploy and wait for the outcome
82
+ voroa deploy <service> --follow also stream the build log while it waits
83
+ voroa restart|stop|start <service> restart, stop or start the running service
84
+ voroa rollback <service> [--to <id>] serve the previous version, or a chosen deploy, again
85
+ voroa env list [service] variable names
86
+ voroa env set <KEY> [service] value from stdin or a hidden prompt; --overwrite to replace
87
+ voroa env unset <KEY> [service] remove a variable
88
+ voroa db list databases in the active workspace
89
+ voroa db connect <database> open psql as the postgres user (asks you to confirm it is you)
90
+ voroa tokens list API tokens
91
+ voroa tokens create --name <n> --scope <a,b> the value is shown once
92
+ voroa tokens revoke <id> stop a token working immediately
93
+ voroa domains list [service] custom domains and their state
94
+ voroa domains add <domain> add one and print the DNS record to create
95
+ voroa domains verify <domain> check it (exit 2 until live; --fix re-applies a stuck one)
96
+ voroa ssh [service] [--print] a shell in the running service (uses your account's SSH keys)
97
+ voroa link <service> bind this folder to a service
98
+ voroa unlink remove that binding
99
+ voroa open [service] [--print] the service's dashboard page
100
+ voroa workspace list your workspaces
101
+ voroa workspace set <id> choose the workspace later commands act on
102
+ ```
103
+
104
+ `<service>` is a service name or its id. Names are resolved only within the active
105
+ workspace; if two services share a name you will be asked for the id.
106
+
107
+ `voroa link` writes `.voroa.json` (a service id, its name and the workspace id - no secret, safe
108
+ to commit). In that folder and any subfolder, `<service>` becomes optional. The id is checked
109
+ against your workspace on every command, so a link copied from someone else's repository does
110
+ nothing. An explicit `--workspace` still wins over the one the link remembers.
111
+
112
+ ## Interactive session
113
+
114
+ Run `voroa` with nothing after it and you get a session instead of a single command:
115
+
116
+ ```
117
+ $ voroa
118
+
119
+ | ^ voroa CLI 0.3.0
120
+ |
121
+ | Account you@example.com
122
+ | Services * 2 running o 1 idle x 0 failed
123
+
124
+ /help commands - Tab complete - Ctrl+C stop - /exit
125
+
126
+ voroa > services
127
+ voroa > logs web --follow
128
+ voroa > deploy web --wait
129
+ voroa > /exit
130
+ ```
131
+
132
+ Every command on this page works there, with or without the leading `/`. **Tab** completes a command
133
+ name, then a service name. **Up arrow** recalls earlier commands - only commands: what you type into a
134
+ password, code or value prompt is never kept. **Ctrl+C** stops a running log tail and returns to the
135
+ prompt; at an empty prompt, pressing it twice leaves, as does `/exit` or Ctrl+D. A command that fails
136
+ prints its message and the session carries on.
137
+
138
+ It is not a shell: quotes group words (`--command "node job.js"`), and nothing else on the line is
139
+ expanded or executed. In a pipe or in CI, `voroa` with no arguments prints the usage text exactly as
140
+ before; `voroa session` opens a session by name.
141
+
142
+ On a real terminal the header, the status marks (green running, grey idle, amber changing, red failed)
143
+ and the last line of a deploy are coloured. That is decoration on top of the words: set `NO_COLOR`, or
144
+ pipe the output anywhere, and you get the same text with no colour and no escape codes.
145
+
146
+ ## For scripts and AI agents
147
+
148
+ ```
149
+ voroa agent the rules, written for a model to read
150
+ voroa agent --json the same as data
151
+ ```
152
+
153
+ - `--json` on any command: the result is JSON on stdout, and a failure is one object on stderr,
154
+ `{"error": "...", "code": "NOT_FOUND"}`. The codes are a short fixed list - `voroa agent` prints them.
155
+ - `--no-input` or `VOROA_NO_INPUT=1`: a step that would ask a question (a sign-in code, a password)
156
+ fails at once with `NEEDS_HUMAN` instead of waiting for an answer nobody will type.
157
+ - `deploy --wait --json` prints one object when it ends, with `outcome` (`live`, `failed`,
158
+ `failed_to_start`, `timeout`, ...) and the same `exitCode` the process returns.
159
+ - `logs --json` prints `{"service", "lines"}`; with `--follow`, one `{"line"}` object per line.
160
+ - `status --all --json` gives every service with its `verdict` in one call.
161
+ - `voroa tools <word>` narrows the list (`voroa tools bucket`); each command is marked as changing
162
+ something or not.
163
+ - A flag a command does not have is refused (`BAD_INPUT`), never ignored.
164
+ - Authenticate an agent with `VOROA_TOKEN`, never with a sign-in.
165
+
166
+ ## Everything else: `voroa tools`
167
+
168
+ The commands above are the everyday ones. Everything else the platform lets a token do is a command
169
+ too - projects and environments, scheduled tasks, database backups and users, storage buckets,
170
+ environment groups, previews, edge cache, maintenance mode, usage, invoices, support tickets:
171
+
172
+ ```
173
+ voroa tools the full list, one line each
174
+ voroa list-buckets run one
175
+ voroa create-scheduled-task --help what it takes
176
+ voroa create-scheduled-task web --name nightly --command "node job.js" --frequency daily
177
+ voroa get-database-metrics <database-id> --json
178
+ voroa tool restart web one whose short name an everyday command already uses
179
+ ```
180
+
181
+ These are the same operations, with the same checks and the same output, that the Voroa connector
182
+ for AI clients offers - the CLI runs that tool set directly, so the two cannot drift apart.
183
+
184
+ - **Arguments** are flags named after the argument (`--service-id`, `--database-id`). Required ones
185
+ can also be given as bare words, in the order `--help` lists them. `--args '<json>'` or
186
+ `--args-file <path>` passes them all as one JSON object.
187
+ - **Keep secrets off the command line.** A flag's value lands in your shell history. When an argument
188
+ is a secret - a variable's value, a secret file's contents - put the arguments in a file, or pipe
189
+ them: `cat vars.json | voroa set-env-vars --args-file -`.
190
+ - **A service** can be given by name, and can be left out in a folder you have run `voroa link` in.
191
+ Databases, buckets and the rest are given by id - the matching `list-...` command prints it.
192
+ - **Output** is a table for a simple list and JSON otherwise. `--json` is always JSON.
193
+ - **They need an API token**, not just a sign-in. Either set `VOROA_TOKEN`, or create one and keep it
194
+ on this machine:
195
+
196
+ ```
197
+ voroa tokens create --name cli --scope services:read,services:act,services:write,services:admin --save
198
+ ```
199
+
200
+ With `--save` the value is stored in `~/.voroa/config.json` and never printed. A token expires
201
+ (90 days, or 30 when it carries `services:admin`); when it does, run the same command again.
202
+ - **Creating or deleting a resource is proposed, not done.** `voroa propose --action create_bucket
203
+ --args '{...}'` raises a request that a person approves in the dashboard. Nothing irreversible
204
+ happens from the terminal, and no command here can move money or read a secret's value.
205
+
206
+ ## Environment variables
207
+
208
+ Values are write-only: they can be set or replaced, never shown or downloaded, so there is no
209
+ `env pull`. A value is never taken from the command line (it would land in shell history) - pipe
210
+ it in: `echo "$DATABASE_URL" | voroa env set DATABASE_URL`. Replacing an existing key needs
211
+ `--overwrite`, because the old value is gone for good. Setting or removing a variable redeploys.
212
+
213
+ ## Confirming it is you
214
+
215
+ Revealing a database password and creating an API token need a fresh confirmation on top of
216
+ being signed in. The CLI asks only when Voroa asks for it: your authenticator code (or a recovery
217
+ code with `--recovery`) if two-step verification is on, otherwise your password or an emailed
218
+ code. A passkey cannot be used from a terminal. `voroa login` completes two-step sign-in the same
219
+ way. `db connect` hands the password to psql through its environment, never the command line, and
220
+ prints it only with `--show-url`.
221
+
222
+ ## Exit codes
223
+
224
+ | Code | Meaning |
225
+ |---|---|
226
+ | 0 | The command succeeded. With `deploy --wait`, the service is live. |
227
+ | 1 | The command failed - including a deploy that failed or failed to start. |
228
+ | 2 | `deploy --wait` ran out of time while the deploy was still going. |
229
+
230
+ Code 2 is deliberately not 0. A timeout means we stopped watching, not that the deploy
231
+ worked.
232
+
233
+ ## Global flags
234
+
235
+ ```
236
+ --workspace <id> act on this workspace for this command only
237
+ --api-url <url> talk to a different Voroa API
238
+ --json machine-readable output (services, status, workspace list)
239
+ --lines <n> with logs: how many lines to fetch (default 100, maximum 1000)
240
+ --search <text> with logs: only lines containing the text, ignoring case (last 24 hours)
241
+ --timeout <min> with deploy --wait: how long to wait (default 10)
242
+ --help, --version
243
+ ```
244
+
245
+ `--lines` and `--timeout` take a number. A value that is not one is refused rather than
246
+ quietly replaced by the default — `--timeout abc` used to wait ten minutes without saying so.
247
+ `--lines` above the server's maximum of 1000 is clamped to it, because that ceiling is
248
+ published and clamping is what it means.
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "voroa-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Voroa command line interface - deploy, inspect and manage your services, databases, cron jobs and storage from the terminal.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/srtayde94/voroa.git",
11
+ "directory": "cli"
12
+ },
13
+ "bin": {
14
+ "voroa": "src/index.js"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "files": [
20
+ "src",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "test": "node --test",
25
+ "prepack": "node scripts/pack-mcp.mjs",
26
+ "postpack": "node scripts/pack-mcp.mjs --clean"
27
+ },
28
+ "keywords": [
29
+ "voroa",
30
+ "cli",
31
+ "paas",
32
+ "deploy",
33
+ "logs"
34
+ ],
35
+ "homepage": "https://getvoroa.com/docs/cli",
36
+ "bugs": {
37
+ "url": "https://getvoroa.com/docs/cli",
38
+ "email": "support@getvoroa.com"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }
package/src/agent.js ADDED
@@ -0,0 +1,95 @@
1
+ // `voroa agent` - how to drive this CLI, written for an AI agent to read.
2
+ //
3
+ // A customer's coding agent (Claude Code, Cursor, anything that can run a shell command) does not need
4
+ // a plugin to use Voroa: it needs to know five rules and where to look things up. This prints them.
5
+ // The exit codes and error codes are READ from the modules that own them, not retyped, so the guide
6
+ // cannot describe a contract the binary does not keep (test/agent.test.js).
7
+ import { ERROR_CODES } from './errors.js';
8
+ import { EXIT_OK, EXIT_FAILED, EXIT_TIMEOUT, VERDICT_MEANINGS } from './status.js';
9
+ import { out, json } from './output.js';
10
+
11
+ const EXIT_CODES = {
12
+ [EXIT_OK]: 'Succeeded. With deploy --wait: the service is live.',
13
+ [EXIT_FAILED]: 'Failed. The error says why.',
14
+ [EXIT_TIMEOUT]: 'Still going when the wait ended (deploy --wait), or not live yet (domains verify). Not a failure and not a success - check again.',
15
+ };
16
+
17
+ const RULES = [
18
+ 'Authenticate with the VOROA_TOKEN environment variable: an API token that starts with voroa_cat_. A person creates it (dashboard: Settings -> API tokens, or `voroa tokens create`). You cannot sign in yourself - `voroa login` sends a code to a person\'s email.',
19
+ 'Add --json to every command. The result is JSON on stdout. A failure is one JSON object on stderr: {"error": "...", "code": "..."}.',
20
+ 'Set VOROA_NO_INPUT=1 (or pass --no-input). A step that needs a person then fails at once with code NEEDS_HUMAN instead of waiting for an answer that will not come.',
21
+ 'Read the exit code, not the text. It says whether the COMMAND worked - not whether a service is healthy: `voroa status` exits 0 for a stopped service too. For health, read the "verdict" field (table below), never the raw "status" field.',
22
+ 'Prefer the everyday commands (services, status, logs, deploy, restart, rollback, env, domains). The `voroa tools` commands cover everything else; a few overlap (services and list-services are the same list).',
23
+ 'To stay read-only, run only commands that `voroa tools` marks "changes: no", plus services, status and logs.',
24
+ 'A service is named by its name or its id. Every other resource (database, bucket, task) takes an id - get it from the matching list command.',
25
+ 'Creating or deleting a resource is a PROPOSAL: `voroa propose` returns a request a person approves in the dashboard. Tell the person; do not retry, and do not look for another way.',
26
+ 'Never put a secret value in a command line. Pipe it: `printf %s "$VALUE" | voroa env set KEY <service>`, or pass arguments with --args-file.',
27
+ ];
28
+
29
+ const DISCOVER = [
30
+ ['voroa tools <word> --json', 'platform commands matching a word (bucket, backup, task...), each with its argument schema and whether it changes anything'],
31
+ ['voroa tools --json', 'all of them (large)'],
32
+ ['voroa <tools-command> --help', 'one of those: what it does and what it takes'],
33
+ ['voroa --help', 'the everyday commands and their flags'],
34
+ ['voroa agent --json', 'this guide as data'],
35
+ ];
36
+
37
+ const EXAMPLES = [
38
+ ['voroa services --json', 'the services (names, ids, repository, branch)'],
39
+ ['voroa status --all --json', 'how every service is doing: its verdict'],
40
+ ['voroa status <service> --json', 'one service in full'],
41
+ ['voroa deploy <service> --wait --json', 'deploy and wait; exit 0 only when live'],
42
+ ['voroa logs <service> --lines 100 --json', 'recent output'],
43
+ ['voroa get-deploy-history <service> --json', 'what was deployed, and how it went'],
44
+ ['voroa rollback <service> --json', 'serve the previous version again'],
45
+ ];
46
+
47
+ export function agentGuide(version) {
48
+ return {
49
+ cli: 'voroa',
50
+ version,
51
+ rules: RULES,
52
+ exitCodes: EXIT_CODES,
53
+ errorCodes: ERROR_CODES,
54
+ verdicts: VERDICT_MEANINGS,
55
+ discover: DISCOVER.map(([command, what]) => ({ command, what })),
56
+ examples: EXAMPLES.map(([command, what]) => ({ command, what })),
57
+ };
58
+ }
59
+
60
+ const table = (rows) => {
61
+ const width = Math.max(...rows.map(([a]) => String(a).length));
62
+ return rows.map(([a, b]) => ` ${String(a).padEnd(width)} ${b}`).join('\n');
63
+ };
64
+
65
+ export function agent({ flags, version }) {
66
+ const guide = agentGuide(version);
67
+ if (flags.json) {
68
+ json(guide);
69
+ return 0;
70
+ }
71
+ out(
72
+ [
73
+ `Voroa CLI ${version} - guide for an AI agent`,
74
+ '',
75
+ 'Rules',
76
+ ...RULES.map((r, i) => ` ${i + 1}. ${r}`),
77
+ '',
78
+ 'Exit codes',
79
+ table(Object.entries(EXIT_CODES)),
80
+ '',
81
+ 'Error codes (the "code" field of a --json failure)',
82
+ table(Object.entries(ERROR_CODES)),
83
+ '',
84
+ 'A service\'s "verdict"',
85
+ table(Object.entries(VERDICT_MEANINGS)),
86
+ '',
87
+ 'Look things up',
88
+ table(DISCOVER),
89
+ '',
90
+ 'Examples',
91
+ table(EXAMPLES),
92
+ ].join('\n')
93
+ );
94
+ return 0;
95
+ }