Skip to content

Commit fed891f

Browse files
authored
docs(cli): add a CLI docs section generated from the command tree (#6762)
* docs(cli): add a CLI section, generated from the command tree The `sim` CLI shipped with no coverage in the docs site. Adds a fourth top-level tab for it, and moves Academy last. The command reference is generated. `sim` exposes 147 leaf commands across 33 groups, most of them derived at runtime from the v2 route contracts, so a hand-written reference would be wrong the week after it was written. The generator walks the command tree `buildProgram()` hands to commander — the same tree the terminal parses — rather than re-deriving it from the contract, which would be a second implementation free to describe commands nobody can invoke. `check:cli-docs` is a zero-arg `check:*` script, so the existing audit runner picks it up and stale pages fail CI. Generating against the real tree surfaced a collision it had been hiding: `bulkUpdateKnowledgeDocuments` and `updateKnowledgeDocument` both derived to `sim knowledge documents update`. Commander resolves a duplicate to the first match, so the bulk form shadowed the single-document one and its flags were unreachable while still appearing in `--help`. The bulk form is now `batch-update`, matching how `tables rows batch-delete`/`batch-update` already handle the same REST overload, and the generator fails on any duplicate path so the next one cannot land silently. Five hand-written guides cover install, auth, configuration, output formats, and scripting. Also corrects two commands in the package README that do not exist as documented (`tables columns <tableId>`, and `--sort score:desc`, which is JSON). * docs(cli): document every flag from the contracts, add troubleshooting and a single-page reference The command reference was structurally complete but said almost nothing: 223 of 377 flags rendered as "Set sort by" because the CLI only ever read flag help from its own contract overrides, and fell back to restating the flag name. The prose already existed. The v2 route contracts carry 931 `.describe()` calls and the OpenAPI specs publish all of them — 327 parameters and 282 body properties, 100% coverage — but the generated operation table dropped every one, carrying only a per-operation summary. It now carries the field descriptions, the path-parameter descriptions, and positional help, so `--help` and the docs explain a flag the same way the API reference does. Placeholder descriptions are now zero, and 147/147 commands, 377/377 flags and 130/130 arguments are documented. `check:cli-docs` fails on a request field with no `.describe()` rather than letting it render as documentation that says nothing. Also in this pass: - Commands are root-level sidebar entries under a Commands heading rather than a folder, and headings are the command's description, so the table of contents distinguishes entries at the first word instead of repeating "sim knowledge documents …" fourteen times. A guard fails the build if two descriptions on a page collide, since they would share an anchor. - A single-page `Complete reference` carrying all 147 commands, for in-page search and for agents fetching `/cli/reference.mdx`. It keys on exact command paths because descriptions are only unique within a group. - A troubleshooting page, with every message copied from the source. - Table columns are sized by a local component; the flag column was starved while descriptions kept most of the row empty. - The prerelease install channels are dropped from the docs and the package README, which is what npm renders. * fix(docs): match the CLI tab by path segment, and escape backslashes before pipes `pathname.includes('/cli')` also matches `/integrations/clickup` and `/integrations/clickhouse`, so both existing integration pages lit the CLI tab and unlit Documentation. Matching is now per path segment. Anchoring to the start would not work either — a non-default locale prefixes the path, as in `/ja/cli` — so the segment is matched wherever it sits. Table cells now double a backslash before escaping pipes. A value ending in one turned `a\` + `|` into `a\\|`, which the table parser reads as an escaped backslash followed by an unescaped pipe, splitting the cell early. Nothing in the command surface contains a backslash today, so this was latent rather than visible. The reference page's global options table is two-column and was being wrapped in `CommandTable`, which sizes the second column for the `Required` cell of the three-column tables and crushed the description into 5.5rem. It now matches the overview page, which leaves that table unsized.
1 parent 6a29a9e commit fed891f

44 files changed

Lines changed: 11026 additions & 576 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,21 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
113113
// Academy lessons are video-first: drop the "On this page" TOC and go full
114114
// width so the lesson hero/video gets the room (chapters live in-page instead).
115115
const isAcademy = slug?.[0] === 'academy'
116+
const isCli = slug?.[0] === 'cli'
116117

117118
const pageTreeRecord = source.pageTree as Record<string, Root>
118119
const pageTree = pageTreeRecord[lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
119120
const rawNeighbours = pageTree ? findNeighbour(pageTree, page.url) : null
120-
// Academy and API Reference are self-contained sections; keep prev/next inside
121-
// the section instead of spilling into the main documentation tree. Match both
122-
// the section's pages (`/<slug>/...`) and its index (`/<slug>`).
123-
const sectionSlug = isApiReference ? 'api-reference' : isAcademy ? 'academy' : null
121+
// Academy, API Reference, and CLI are self-contained sections; keep prev/next
122+
// inside the section instead of spilling into the main documentation tree.
123+
// Match both the section's pages (`/<slug>/...`) and its index (`/<slug>`).
124+
const sectionSlug = isApiReference
125+
? 'api-reference'
126+
: isAcademy
127+
? 'academy'
128+
: isCli
129+
? 'cli'
130+
: null
124131
const inSection = (url?: string) =>
125132
url != null && (url.includes(`/${sectionSlug}/`) || url.endsWith(`/${sectionSlug}`))
126133
const neighbours = sectionSlug

apps/docs/components/navbar/navbar.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,53 @@ import { SimWordmark } from '@/components/ui/sim-logo'
88
import { ThemeToggle } from '@/components/ui/theme-toggle'
99
import { cn } from '@/lib/utils'
1010

11+
/**
12+
* Sections that own a tab, in reading order: the main docs, then the two
13+
* reference surfaces, then Academy. `Documentation` matches by exclusion, so
14+
* every section listed here is one it must not claim.
15+
*/
16+
const SECTION_TABS = ['api-reference', 'academy', 'cli'] as const
17+
18+
/**
19+
* Whether a pathname is inside a section, matched by whole path segment.
20+
*
21+
* A substring test is wrong: `/integrations/clickup` and
22+
* `/integrations/clickhouse` both contain `/cli`, which lit the CLI tab and
23+
* unlit Documentation on two existing integration pages. Anchoring to the start
24+
* is also wrong, because a non-default locale prefixes the path (`/ja/cli`), so
25+
* the segment can sit anywhere.
26+
*/
27+
function isInSection(pathname: string, section: string): boolean {
28+
return (
29+
pathname === `/${section}` ||
30+
pathname.endsWith(`/${section}`) ||
31+
pathname.includes(`/${section}/`)
32+
)
33+
}
34+
1135
const NAV_TABS = [
1236
{
1337
label: 'Documentation',
1438
href: '/introduction',
15-
match: (p: string) => !p.includes('/api-reference') && !p.includes('/academy'),
39+
match: (p: string) => !SECTION_TABS.some((section) => isInSection(p, section)),
1640
external: false,
1741
},
1842
{
19-
label: 'Academy',
20-
href: '/academy',
21-
match: (p: string) => p.includes('/academy'),
43+
label: 'API Reference',
44+
href: '/api-reference/getting-started',
45+
match: (p: string) => isInSection(p, 'api-reference'),
2246
external: false,
2347
},
2448
{
25-
label: 'API Reference',
26-
href: '/api-reference/getting-started',
27-
match: (p: string) => p.includes('/api-reference'),
49+
label: 'CLI',
50+
href: '/cli',
51+
match: (p: string) => isInSection(p, 'cli'),
52+
external: false,
53+
},
54+
{
55+
label: 'Academy',
56+
href: '/academy',
57+
match: (p: string) => isInSection(p, 'academy'),
2858
external: false,
2959
},
3060
] as const
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { ReactNode } from 'react'
2+
3+
interface CommandTableProps {
4+
children: ReactNode
5+
}
6+
7+
/**
8+
* Column sizing for the generated CLI reference tables.
9+
*
10+
* Auto layout gives a column width in proportion to its content, which is
11+
* backwards here: descriptions are sentences and flags are short, so the flag
12+
* column collapsed until `--enabled-filter <value>` wrapped across three lines
13+
* while the description beside it kept most of the row empty. A fixed layout
14+
* with explicit widths reserves the space the flag actually needs.
15+
*
16+
* Cells align to the top because a wrapped four-line description would
17+
* otherwise float its flag into the middle of the row, away from the line it
18+
* belongs to.
19+
*/
20+
export function CommandTable({ children }: CommandTableProps) {
21+
return (
22+
<div
23+
className={[
24+
'[&_table]:w-full [&_table]:table-fixed',
25+
'[&_th:nth-child(1)]:w-[30%] [&_th:nth-child(2)]:w-[5.5rem]',
26+
'[&_td]:align-top [&_th]:align-bottom',
27+
// Long flags and dotted paths have no spaces to break on.
28+
'[&_td:nth-child(1)_code]:break-words',
29+
].join(' ')}
30+
>
31+
{children}
32+
</div>
33+
)
34+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
title: Audit Logs
3+
description: Manage audit logs — every subcommand, argument, and flag
4+
---
5+
6+
import { CommandTable } from '@/components/ui/command-table'
7+
8+
`sim audit-logs` is also spelled `sim audit-log`.
9+
10+
Every command below also accepts the [global options](/cli/commands#global-options).
11+
12+
## Get audit log
13+
14+
```bash
15+
sim audit-logs get <id> [options]
16+
```
17+
18+
**Arguments**
19+
20+
<CommandTable>
21+
22+
| Argument | Required | Description |
23+
| --- | --- | --- |
24+
| `id` | Yes | Audit-log entry identifier. |
25+
26+
</CommandTable>
27+
28+
**Options**
29+
30+
<CommandTable>
31+
32+
| Option | Required | Description |
33+
| --- | --- | --- |
34+
| `--organization <value>` | Yes | Organization ID (personal API key required). |
35+
36+
</CommandTable>
37+
38+
## List audit logs
39+
40+
```bash
41+
sim audit-logs list [options]
42+
```
43+
44+
**Options**
45+
46+
<CommandTable>
47+
48+
| Option | Required | Description |
49+
| --- | --- | --- |
50+
| `--action <value>` | No | Filter by exact action name. |
51+
| `--resource-type <value>` | No | Filter by resource type. Accepts a comma-separated set; members are trimmed and deduplicated, and member order affects neither the result nor the cursor. |
52+
| `--resource-id <value>` | No | Filter by exact resource identifier. |
53+
| `--start-date <value>` | No | Only include runs started at or after this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant. |
54+
| `--end-date <value>` | No | Only include runs started at or before this UTC ISO 8601 timestamp, e.g. `2026-08-06T00:00:00Z`. A date without a time, or a timestamp carrying a UTC offset instead of `Z`, is rejected, as is year `0000`, which names no storable instant. |
55+
| `--include-departed` | No | Include actions by users who have left the organization. |
56+
| `--no-include-departed` | No | Send --include-departed as false. |
57+
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
58+
| `--organization <value>` | Yes | Organization ID (personal API key required). |
59+
| `--actor-email <value>` | No | Filter by actor email address. |
60+
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
61+
62+
</CommandTable>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
title: Authentication
3+
description: Sign in from the terminal, authenticate CI with an API key, and keep several accounts side by side
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
8+
The CLI authenticates with a Sim API key. On a workstation, `sim login` mints and
9+
stores one for you. In CI, you supply one through the environment and nothing
10+
touches the filesystem.
11+
12+
## Signing in
13+
14+
```bash
15+
sim login
16+
```
17+
18+
The terminal prints a pairing code and a URL:
19+
20+
```
21+
Pairing code: K7M2-P9XT
22+
Confirm this code matches what the browser shows before approving.
23+
24+
https://sim.ai/cli/auth?request=…&scope=platform
25+
Waiting for approval…
26+
27+
✓ Logged in. Key stored in /Users/you/.sim/credentials
28+
Personal key, defaulting to ws_abc123. Override per command with --workspace.
29+
```
30+
31+
This is the same browser handoff shape as `gh auth login`. Nothing redeemable
32+
crosses the browser leg, and there is no loopback listener — so it works over
33+
SSH and inside containers.
34+
35+
<Callout type="warn">
36+
Confirm the pairing code in your terminal matches the one the browser shows
37+
before you approve. That check is what binds the approval to *your* terminal.
38+
</Callout>
39+
40+
| Option | What it does |
41+
| --- | --- |
42+
| `--no-browser` | Print the URL instead of opening a browser |
43+
| `--scope <scope>` | Key space to mint from: `platform` (default) or `copilot` |
44+
| `-y, --yes` | Overwrite an existing profile without prompting |
45+
46+
### Picking a workspace
47+
48+
The approval page is where you choose the workspace — the terminal has no key
49+
yet, so it cannot list them for you.
50+
51+
`sim login` issues a **personal** key. The workspace you pick becomes the
52+
profile's default `workspace`; it does **not** restrict the key to that
53+
workspace. Target another workspace the key can reach with `--workspace`:
54+
55+
```bash
56+
sim workflows list --workspace ws_other
57+
```
58+
59+
`sim login --workspace <id>` preselects a workspace in the picker, and
60+
re-logging into an existing profile preselects the one already configured.
61+
62+
## Checking who you are
63+
64+
```bash
65+
sim whoami
66+
```
67+
68+
This prints the resolved endpoint, workspace, output format, and account — and
69+
which source each value came from. Reach for it first whenever a command targets
70+
something you did not expect.
71+
72+
## Signing out
73+
74+
```bash
75+
sim logout # remove the stored key
76+
sim logout --all # remove the profile entirely, including its settings
77+
```
78+
79+
<Callout type="warn">
80+
`sim logout` removes the key from disk but does **not** revoke it. Revoke keys in
81+
Sim under **Settings → API keys**.
82+
</Callout>
83+
84+
## Authenticating CI
85+
86+
Skip `sim login` entirely. Set the key and workspace in the environment and the
87+
CLI never reads or writes a config file:
88+
89+
```bash
90+
export SIM_API_KEY="sim_…"
91+
export SIM_WORKSPACE="ws_abc123"
92+
93+
sim workflows run wf_7Yb2 --input '{"source":"nightly"}' --output json
94+
```
95+
96+
Create the key in Sim under **Settings → API keys**. Store it as a secret in your
97+
CI provider — never commit it.
98+
99+
<Callout type="info">
100+
`SIM_CONFIG_DIR` relocates both files if you do need them somewhere other than
101+
`~/.sim` — a container image, or a runner with no writable home directory.
102+
</Callout>
103+
104+
### GitHub Actions
105+
106+
```yaml title=".github/workflows/nightly.yml"
107+
jobs:
108+
digest:
109+
runs-on: ubuntu-latest
110+
steps:
111+
- uses: actions/setup-node@v4
112+
with:
113+
node-version: '20'
114+
- run: npm install --global sim
115+
- run: sim workflows run wf_7Yb2 --output json
116+
env:
117+
SIM_API_KEY: ${{ secrets.SIM_API_KEY }}
118+
SIM_WORKSPACE: ${{ vars.SIM_WORKSPACE }}
119+
```
120+
121+
## Several accounts at once
122+
123+
Each profile holds one identity and one set of defaults, so a production account
124+
and a local stack can coexist without re-authenticating:
125+
126+
```bash
127+
sim login --profile dev --endpoint http://localhost:3000
128+
sim login --profile prod
129+
130+
sim workflows list --profile dev
131+
sim workflows list --profile prod
132+
```
133+
134+
See [Configuration](/cli/configuration) for how profiles are stored and resolved.
135+
136+
## Self-hosted and non-production deployments
137+
138+
Point the CLI at any Sim deployment with `--endpoint`, then sign in against it:
139+
140+
```bash
141+
sim login --profile local --endpoint http://localhost:3000
142+
```
143+
144+
Save it so you do not have to repeat the flag:
145+
146+
```bash
147+
sim configure --set-endpoint http://localhost:3000 --profile local
148+
```
149+
150+
## Where the key is stored
151+
152+
Keys live in `~/.sim/credentials`, written with `0600` permissions, kept apart
153+
from the non-secret `~/.sim/config` so the two can be handled differently — you
154+
can commit `config` to a dotfiles repo, and never `credentials`.
155+
156+
```ini title="~/.sim/credentials"
157+
[default]
158+
api_key = sim_…
159+
160+
[dev]
161+
api_key = sim_…
162+
```
163+
164+
## Organization audit logs
165+
166+
`sim audit-logs` requires a **personal** API key — the kind `sim login` issues.
167+
A workspace-scoped key cannot read organization-level audit logs.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: Billing
3+
description: Manage billing — every subcommand, argument, and flag
4+
---
5+
6+
import { CommandTable } from '@/components/ui/command-table'
7+
8+
Every command below also accepts the [global options](/cli/commands#global-options).
9+
10+
## Show billing status and current-period credit usage
11+
12+
```bash
13+
sim billing status [options]
14+
```
15+
16+
**Options**
17+
18+
<CommandTable>
19+
20+
| Option | Required | Description |
21+
| --- | --- | --- |
22+
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
23+
24+
</CommandTable>
25+
26+
## List credit usage events
27+
28+
```bash
29+
sim billing logs [options]
30+
```
31+
32+
**Options**
33+
34+
<CommandTable>
35+
36+
| Option | Required | Description |
37+
| --- | --- | --- |
38+
| `--source <value>` | No | Filter by usage source; sim-chat combines Copilot and workspace chat. Accepted values: `workflow`, `wand`, `sim-chat`, `mcp_copilot`, `mothership_block`, `knowledge-base`, `voice-input`, `enrichment`, `voice-output`. |
39+
| `--period <value>` | No | Billing period. Accepted values: `1d`, `7d`, `30d`, `all`, `custom`. |
40+
| `--start-date <value>` | No | Custom period start (ISO 8601). |
41+
| `--end-date <value>` | No | Custom period end (ISO 8601). |
42+
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
43+
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
44+
45+
</CommandTable>

0 commit comments

Comments
 (0)