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.
This commit is contained in:
Waleed
2026-08-15 19:25:34 -07:00
committed by GitHub
parent 6a29a9e2f4
commit fed891f69d
44 changed files with 11027 additions and 577 deletions
+11 -4
View File
@@ -113,14 +113,21 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
// Academy lessons are video-first: drop the "On this page" TOC and go full
// width so the lesson hero/video gets the room (chapters live in-page instead).
const isAcademy = slug?.[0] === 'academy'
const isCli = slug?.[0] === 'cli'
const pageTreeRecord = source.pageTree as Record<string, Root>
const pageTree = pageTreeRecord[lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
const rawNeighbours = pageTree ? findNeighbour(pageTree, page.url) : null
// Academy and API Reference are self-contained sections; keep prev/next inside
// the section instead of spilling into the main documentation tree. Match both
// the section's pages (`/<slug>/...`) and its index (`/<slug>`).
const sectionSlug = isApiReference ? 'api-reference' : isAcademy ? 'academy' : null
// Academy, API Reference, and CLI are self-contained sections; keep prev/next
// inside the section instead of spilling into the main documentation tree.
// Match both the section's pages (`/<slug>/...`) and its index (`/<slug>`).
const sectionSlug = isApiReference
? 'api-reference'
: isAcademy
? 'academy'
: isCli
? 'cli'
: null
const inSection = (url?: string) =>
url != null && (url.includes(`/${sectionSlug}/`) || url.endsWith(`/${sectionSlug}`))
const neighbours = sectionSlug
+38 -8
View File
@@ -8,23 +8,53 @@ import { SimWordmark } from '@/components/ui/sim-logo'
import { ThemeToggle } from '@/components/ui/theme-toggle'
import { cn } from '@/lib/utils'
/**
* Sections that own a tab, in reading order: the main docs, then the two
* reference surfaces, then Academy. `Documentation` matches by exclusion, so
* every section listed here is one it must not claim.
*/
const SECTION_TABS = ['api-reference', 'academy', 'cli'] as const
/**
* Whether a pathname is inside a section, matched by whole path segment.
*
* A substring test is wrong: `/integrations/clickup` and
* `/integrations/clickhouse` both contain `/cli`, which lit the CLI tab and
* unlit Documentation on two existing integration pages. Anchoring to the start
* is also wrong, because a non-default locale prefixes the path (`/ja/cli`), so
* the segment can sit anywhere.
*/
function isInSection(pathname: string, section: string): boolean {
return (
pathname === `/${section}` ||
pathname.endsWith(`/${section}`) ||
pathname.includes(`/${section}/`)
)
}
const NAV_TABS = [
{
label: 'Documentation',
href: '/introduction',
match: (p: string) => !p.includes('/api-reference') && !p.includes('/academy'),
external: false,
},
{
label: 'Academy',
href: '/academy',
match: (p: string) => p.includes('/academy'),
match: (p: string) => !SECTION_TABS.some((section) => isInSection(p, section)),
external: false,
},
{
label: 'API Reference',
href: '/api-reference/getting-started',
match: (p: string) => p.includes('/api-reference'),
match: (p: string) => isInSection(p, 'api-reference'),
external: false,
},
{
label: 'CLI',
href: '/cli',
match: (p: string) => isInSection(p, 'cli'),
external: false,
},
{
label: 'Academy',
href: '/academy',
match: (p: string) => isInSection(p, 'academy'),
external: false,
},
] as const
+34
View File
@@ -0,0 +1,34 @@
import type { ReactNode } from 'react'
interface CommandTableProps {
children: ReactNode
}
/**
* Column sizing for the generated CLI reference tables.
*
* Auto layout gives a column width in proportion to its content, which is
* backwards here: descriptions are sentences and flags are short, so the flag
* column collapsed until `--enabled-filter <value>` wrapped across three lines
* while the description beside it kept most of the row empty. A fixed layout
* with explicit widths reserves the space the flag actually needs.
*
* Cells align to the top because a wrapped four-line description would
* otherwise float its flag into the middle of the row, away from the line it
* belongs to.
*/
export function CommandTable({ children }: CommandTableProps) {
return (
<div
className={[
'[&_table]:w-full [&_table]:table-fixed',
'[&_th:nth-child(1)]:w-[30%] [&_th:nth-child(2)]:w-[5.5rem]',
'[&_td]:align-top [&_th]:align-bottom',
// Long flags and dotted paths have no spaces to break on.
'[&_td:nth-child(1)_code]:break-words',
].join(' ')}
>
{children}
</div>
)
}
@@ -0,0 +1,62 @@
---
title: Audit Logs
description: Manage audit logs — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim audit-logs` is also spelled `sim audit-log`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Get audit log
```bash
sim audit-logs get <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Audit-log entry identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--organization <value>` | Yes | Organization ID (personal API key required). |
</CommandTable>
## List audit logs
```bash
sim audit-logs list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--action <value>` | No | Filter by exact action name. |
| `--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. |
| `--resource-id <value>` | No | Filter by exact resource identifier. |
| `--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. |
| `--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. |
| `--include-departed` | No | Include actions by users who have left the organization. |
| `--no-include-departed` | No | Send --include-departed as false. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--organization <value>` | Yes | Organization ID (personal API key required). |
| `--actor-email <value>` | No | Filter by actor email address. |
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
</CommandTable>
@@ -0,0 +1,167 @@
---
title: Authentication
description: Sign in from the terminal, authenticate CI with an API key, and keep several accounts side by side
---
import { Callout } from 'fumadocs-ui/components/callout'
The CLI authenticates with a Sim API key. On a workstation, `sim login` mints and
stores one for you. In CI, you supply one through the environment and nothing
touches the filesystem.
## Signing in
```bash
sim login
```
The terminal prints a pairing code and a URL:
```
Pairing code: K7M2-P9XT
Confirm this code matches what the browser shows before approving.
https://sim.ai/cli/auth?request=…&scope=platform
Waiting for approval…
✓ Logged in. Key stored in /Users/you/.sim/credentials
Personal key, defaulting to ws_abc123. Override per command with --workspace.
```
This is the same browser handoff shape as `gh auth login`. Nothing redeemable
crosses the browser leg, and there is no loopback listener — so it works over
SSH and inside containers.
<Callout type="warn">
Confirm the pairing code in your terminal matches the one the browser shows
before you approve. That check is what binds the approval to *your* terminal.
</Callout>
| Option | What it does |
| --- | --- |
| `--no-browser` | Print the URL instead of opening a browser |
| `--scope <scope>` | Key space to mint from: `platform` (default) or `copilot` |
| `-y, --yes` | Overwrite an existing profile without prompting |
### Picking a workspace
The approval page is where you choose the workspace — the terminal has no key
yet, so it cannot list them for you.
`sim login` issues a **personal** key. The workspace you pick becomes the
profile's default `workspace`; it does **not** restrict the key to that
workspace. Target another workspace the key can reach with `--workspace`:
```bash
sim workflows list --workspace ws_other
```
`sim login --workspace <id>` preselects a workspace in the picker, and
re-logging into an existing profile preselects the one already configured.
## Checking who you are
```bash
sim whoami
```
This prints the resolved endpoint, workspace, output format, and account — and
which source each value came from. Reach for it first whenever a command targets
something you did not expect.
## Signing out
```bash
sim logout # remove the stored key
sim logout --all # remove the profile entirely, including its settings
```
<Callout type="warn">
`sim logout` removes the key from disk but does **not** revoke it. Revoke keys in
Sim under **Settings → API keys**.
</Callout>
## Authenticating CI
Skip `sim login` entirely. Set the key and workspace in the environment and the
CLI never reads or writes a config file:
```bash
export SIM_API_KEY="sim_…"
export SIM_WORKSPACE="ws_abc123"
sim workflows run wf_7Yb2 --input '{"source":"nightly"}' --output json
```
Create the key in Sim under **Settings → API keys**. Store it as a secret in your
CI provider — never commit it.
<Callout type="info">
`SIM_CONFIG_DIR` relocates both files if you do need them somewhere other than
`~/.sim` — a container image, or a runner with no writable home directory.
</Callout>
### GitHub Actions
```yaml title=".github/workflows/nightly.yml"
jobs:
digest:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install --global sim
- run: sim workflows run wf_7Yb2 --output json
env:
SIM_API_KEY: ${{ secrets.SIM_API_KEY }}
SIM_WORKSPACE: ${{ vars.SIM_WORKSPACE }}
```
## Several accounts at once
Each profile holds one identity and one set of defaults, so a production account
and a local stack can coexist without re-authenticating:
```bash
sim login --profile dev --endpoint http://localhost:3000
sim login --profile prod
sim workflows list --profile dev
sim workflows list --profile prod
```
See [Configuration](/cli/configuration) for how profiles are stored and resolved.
## Self-hosted and non-production deployments
Point the CLI at any Sim deployment with `--endpoint`, then sign in against it:
```bash
sim login --profile local --endpoint http://localhost:3000
```
Save it so you do not have to repeat the flag:
```bash
sim configure --set-endpoint http://localhost:3000 --profile local
```
## Where the key is stored
Keys live in `~/.sim/credentials`, written with `0600` permissions, kept apart
from the non-secret `~/.sim/config` so the two can be handled differently — you
can commit `config` to a dotfiles repo, and never `credentials`.
```ini title="~/.sim/credentials"
[default]
api_key = sim_…
[dev]
api_key = sim_…
```
## Organization audit logs
`sim audit-logs` requires a **personal** API key — the kind `sim login` issues.
A workspace-scoped key cannot read organization-level audit logs.
+45
View File
@@ -0,0 +1,45 @@
---
title: Billing
description: Manage billing — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
Every command below also accepts the [global options](/cli/commands#global-options).
## Show billing status and current-period credit usage
```bash
sim billing status [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
</CommandTable>
## List credit usage events
```bash
sim billing logs [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--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`. |
| `--period <value>` | No | Billing period. Accepted values: `1d`, `7d`, `30d`, `all`, `custom`. |
| `--start-date <value>` | No | Custom period start (ISO 8601). |
| `--end-date <value>` | No | Custom period end (ISO 8601). |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--all-workspaces` | No | Do not filter to the configured workspace (personal API key required for account-wide access). |
</CommandTable>
+112
View File
@@ -0,0 +1,112 @@
---
title: Overview
description: Global options, and every sim command group
---
import { CommandTable } from '@/components/ui/command-table'
Every `sim` command follows the same shape:
```bash
sim <resource> [sub-resource] <verb> [arguments] [options]
```
Resource groups are plural, and each one also accepts its singular spelling —
`sim workflow get` and `sim workflows get` are the same command. `knowledge`
additionally answers to `kb`.
## Global options
These apply to every command, and may be written before or after it.
| Option | Description |
| --- | --- |
| `-P, --profile <name>` | Profile to use (env: SIM_PROFILE). |
| `--endpoint <url>` | Sim deployment to talk to (env: SIM_ENDPOINT). |
| `-w, --workspace <id>` | Workspace to target (env: SIM_WORKSPACE). |
| `--output <format>` | Output format for this command. Accepted values: `table`, `json`, `yaml`, `text`. |
## Command groups
| Group | Description |
| --- | --- |
| [`sim audit-logs`](/cli/audit-logs) | Manage audit logs |
| [`sim billing`](/cli/billing) | Manage billing |
| [`sim credentials`](/cli/credentials) | Manage credentials |
| [`sim custom-tools`](/cli/custom-tools) | Manage custom tools |
| [`sim files`](/cli/files) | Manage files |
| [`sim knowledge`](/cli/knowledge) | Manage knowledge |
| [`sim logs`](/cli/logs) | Manage logs |
| [`sim mcp-servers`](/cli/mcp-servers) | Manage mcp servers |
| [`sim secrets`](/cli/secrets) | Manage secrets |
| [`sim skills`](/cli/skills) | Manage skills |
| [`sim tables`](/cli/tables) | Manage tables |
| [`sim workflows`](/cli/workflows) | Manage workflows |
| [`sim workspaces`](/cli/workspaces) | Manage workspaces |
## Authorize this terminal and store an API key for the profile
```bash
sim login [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--scope <scope>` | No | Key space to mint from: platform or copilot. Defaults to `platform`. |
| `--no-browser` | No | Print the URL instead of opening a browser. |
| `-y, --yes` | No | Overwrite an existing profile without prompting. |
</CommandTable>
## Remove the profile's stored API key
```bash
sim logout [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--all` | No | Remove the profile entirely, including its settings. |
</CommandTable>
## Show the resolved profile and where each setting came from
```bash
sim whoami
```
## List the profiles defined in the config and credentials files
```bash
sim profiles
```
Also available as `sim profile`.
## Set a profile's endpoint, default workspace, or output format
```bash
sim configure [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--set-endpoint <url>` | No | Sim deployment to talk to. |
| `--set-workspace <id>` | No | Default workspace for workspace-scoped commands. |
| `--set-output <format>` | No | Default output format (table \| json \| yaml \| text). |
| `--unset <key...>` | No | Remove settings (endpoint, workspace, output). |
</CommandTable>
@@ -0,0 +1,147 @@
---
title: Configuration
description: Profiles, config files, environment variables, and how each setting is resolved
---
import { Callout } from 'fumadocs-ui/components/callout'
The CLI has four settings: which **endpoint** to talk to, which **API key** to
use, which **workspace** to target, and which **output format** to print. Each
one resolves independently, so you can save a default and still override it for
a single command.
## Profiles
A profile is one identity plus one set of defaults. Profiles work like the AWS
CLI, which is what lets a production account and a local stack sit side by side
without re-authenticating.
Select one with `-P`, `--profile`, or `SIM_PROFILE`:
```bash
sim workflows list --profile dev
SIM_PROFILE=dev sim workflows list
```
The profile is named `default` when you do not pick one.
```bash
sim profiles # list them; * marks the active one
```
## Setting defaults
```bash
sim configure --set-endpoint http://localhost:3000 --profile dev
sim configure --set-workspace ws_local --profile dev
sim configure --set-output json
```
| Option | What it sets |
| --- | --- |
| `--set-endpoint <url>` | The Sim deployment to talk to |
| `--set-workspace <id>` | Default workspace for workspace-scoped commands |
| `--set-output <format>` | Default output format: `table`, `json`, `yaml`, or `text` |
| `--unset <key...>` | Remove settings — `endpoint`, `workspace`, or `output` |
Run `sim configure` with no flags to print the profile's stored settings.
<Callout type="info">
API keys are deliberately **not** settable through `sim configure`. Use
[`sim login`](/cli/authentication), or `SIM_API_KEY` for CI.
</Callout>
## Where settings come from
Each setting resolves independently, and the first match wins:
| Rank | Source |
| --- | --- |
| 1 | Command-line flag — `--endpoint`, `--workspace`, `--output` |
| 2 | Environment — `SIM_ENDPOINT`, `SIM_API_KEY`, `SIM_WORKSPACE`, `SIM_OUTPUT` |
| 3 | `~/.sim/config` and `~/.sim/credentials`, for the selected profile |
| 4 | Built-in default — `https://sim.ai` and `table` |
Because they resolve independently, a saved profile still supplies the workspace
when you override only the output format.
`sim whoami` prints the winning source for each setting, which is usually the
fastest way to explain a surprising result:
```bash
sim whoami
```
## The files
Non-secret settings live in `~/.sim/config`. It is safe to commit to a dotfiles
repo:
```ini title="~/.sim/config"
[default]
endpoint = https://sim.ai
workspace = ws_abc123
output = table
[profile dev]
endpoint = http://localhost:3000
workspace = ws_local
```
Keys live in `~/.sim/credentials`, written `0600`:
```ini title="~/.sim/credentials"
[default]
api_key = sim_…
[dev]
api_key = sim_…
```
<Callout type="info">
The section-naming asymmetry — `[profile dev]` in config, `[dev]` in credentials
— is the AWS convention, kept so existing habits and tooling carry over. The
`default` profile is spelled `[default]` in both.
</Callout>
## Environment variables
| Variable | Effect |
| --- | --- |
| `SIM_PROFILE` | Profile to use |
| `SIM_ENDPOINT` | Deployment to talk to |
| `SIM_API_KEY` | API key — skips `sim login` entirely |
| `SIM_WORKSPACE` | Workspace to target |
| `SIM_OUTPUT` | Output format |
| `SIM_CONFIG_DIR` | Relocate both files away from `~/.sim` |
| `SIM_CONFIG_FILE` | Relocate only the config file |
| `SIM_CREDENTIALS_FILE` | Relocate only the credentials file |
For CI, set `SIM_API_KEY` and `SIM_WORKSPACE` and nothing needs to touch the
filesystem at all.
## Choosing a workspace
Workspace-scoped commands need a workspace. Supply it per command, save it to
the profile, or set it in the environment:
```bash
sim tables list --workspace ws_other
sim configure --set-workspace ws_abc123
export SIM_WORKSPACE=ws_abc123
```
Without one, the command fails and tells you how to set it. A few commands —
`sim billing status`, `sim billing logs`, and `sim audit-logs list` — accept
`--all-workspaces` to drop the filter instead. It cannot be combined with
`--workspace`.
## Repairing a bad setting
An invalid `output` value fails with the list of accepted formats. Because a
higher-priority source still wins, you can repair a profile without editing the
file by hand:
```bash
sim --output table configure --set-output json
```
@@ -0,0 +1,144 @@
---
title: Credentials
description: Manage credentials — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim credentials` is also spelled `sim credential`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Disconnect credential
```bash
sim credentials delete <credentialId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `credentialId` | Yes | Credential to disconnect. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## List credential providers
```bash
sim credentials providers list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <value>` | No | Case-insensitive substring match against the credential provider name. |
</CommandTable>
## List credentials
```bash
sim credentials list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--type <value>` | No | Restrict results to this credential type. Accepted values: `oauth`, `service_account`. |
| `--provider-id <value>` | No | Restrict results to credentials for this integration provider. |
| `--search <value>` | No | Case-insensitive substring match against the credential display name. |
| `--sort-by <value>` | No | Field used to sort the result. Accepted values: `displayName`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Create a service-account credential using its discovered provider schema
```bash
sim credentials create <providerId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `providerId` | Yes | Service-account provider to create a credential for |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <displayName>` | Yes | Name shown for the credential in Sim. |
| `--credentials <json\|@file>` | Yes | Provider credentials as JSON (or @path / @- to read a file or stdin). |
| `--description <description>` | No | Optional credential description. |
| `--id <credentialId>` | No | Client-generated credential ID when provider discovery requires it. |
</CommandTable>
## Create a short-lived link for connecting an OAuth provider
```bash
sim credentials connect <providerId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `providerId` | Yes | OAuth provider to connect |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <displayName>` | Yes | Name shown for the new credential in Sim. |
</CommandTable>
## Create a short-lived link for reconnecting an OAuth credential
```bash
sim credentials reconnect <credentialId>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `credentialId` | Yes | Existing OAuth credential to re-authorize |
</CommandTable>
@@ -0,0 +1,117 @@
---
title: Custom Tools
description: Manage custom tools — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim custom-tools` is also spelled `sim custom-tool`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Create custom tool
```bash
sim custom-tools create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--title <value>` | Yes | Display title, unique within the workspace. |
| `--schema <json\|@file>` | Yes | OpenAI function schema: &#123;"type":"function","function":&#123;"name":"...","parameters":&#123;"type":"object","properties":&#123;&#125;&#125;&#125;&#125; (JSON, or @path / @- to read a file or stdin). |
| `--code <value>` | Yes | Tool implementation executed in the sandboxed function runtime. |
</CommandTable>
## Delete custom tool
```bash
sim custom-tools delete <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique custom tool identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Get custom tool
```bash
sim custom-tools get <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique custom tool identifier. |
</CommandTable>
## List custom tools
```bash
sim custom-tools list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <value>` | No | Case-insensitive substring match against the tool title. |
| `--sort-by <value>` | No | Field used to sort the result. Accepted values: `title`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Update custom tool
```bash
sim custom-tools update <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique custom tool identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--title <value>` | No | New display title for the tool. |
| `--schema <json\|@file>` | No | OpenAI function schema: &#123;"type":"function","function":&#123;"name":"...","parameters":&#123;"type":"object","properties":&#123;&#125;&#125;&#125;&#125; (JSON, or @path / @- to read a file or stdin). |
| `--code <value>` | No | Replacement tool implementation. |
</CommandTable>
+433
View File
@@ -0,0 +1,433 @@
---
title: Files
description: Manage files — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim files` is also spelled `sim file`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Delete several files at once
```bash
sim files batch-delete [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line). |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Create file
```bash
sim files create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | File name, including its extension. Path separators and dot segments are rejected. |
| `--content-type <value>` | No | MIME type. When omitted, it is inferred from the file extension. |
| `--folder <value>` | No | Folder path; the leading / is optional. |
| `--content <value>` | No | Initial file content. Omit or send an empty string for a zero-byte file. The 70,000,000-character bound guards the JSON envelope; the decoded bytes must be at most 50 MiB, and a longer base64 payload is rejected with `413`. Use an upload session for anything larger. |
| `--encoding <value>` | No | Encoding of the content field. Accepted values: `utf-8`, `base64`. |
</CommandTable>
## Create a file folder at a path
```bash
sim files folders create <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete folder
```bash
sim files folders delete <path> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--recursive` | No | Delete the folder and its descendants. |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## List folders
```bash
sim files folders list [options]
```
Also available as `sim files folders ls`.
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--parent <value>` | No | Direct parent folder path. |
| `--search <value>` | No | Case-insensitive substring match against the folder name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
</CommandTable>
## Rename or move a file folder
```bash
sim files folders move <path> <destination>
```
Also available as `sim files folders mv`.
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
| `destination` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete file
```bash
sim files delete <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Show file metadata and sharing status
```bash
sim files describe <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--scope <value>` | No | Which lifecycle set to read from: `active` (default) resolves live files only and returns `404` for a file a `DELETE` soft-deleted; `archived` also resolves soft-deleted files, so metadata stays readable before `POST /files/&#123;fileId&#125;/restore`. Authorization is identical for both. Accepted values: `active`, `archived`. |
</CommandTable>
## Show a file’s share settings
```bash
sim files share get <fileId>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
## Enable or disable sharing for a file
```bash
sim files share set <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--is-active <true\|false>` | Yes | Whether the share should resolve. Disabling preserves the token and the whole access configuration, so re-enabling restores the share as it was; enabling rewrites the credentials the resulting mode does not use. Accepted values: `true`, `false`. |
| `--auth-type <value>` | No | How access to the share is gated. The stored mode is kept when omitted. Enabling `public` clears the stored password and empties `allowedEmails`; `password` empties `allowedEmails`; `email` and `sso` clear the stored password. Accepted values: `public`, `password`, `email`, `sso`. |
| `--password <value>` | No | Password for a password-gated share. Kept when omitted; enabling `password` with neither a supplied nor a stored password is a 400. |
| `--allowed-emails <value...>` | No | Allowed addresses or `@domain` patterns for email and SSO shares. Kept when omitted; enabling `email` or `sso` with an empty resulting list is a 400. (space-separated, or @path / @- with one value per line). |
</CommandTable>
## List files
```bash
sim files list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--folder <value>` | No | Folder path; the leading / is optional. |
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) for live files, `archived` for files a `DELETE` soft-deleted. `folderPath` resolves against active folders only, so pairing it with `scope=archived` returns an empty page when the containing folder was archived too. Accepted values: `active`, `archived`. |
| `--search <value>` | No | Case-insensitive substring match against the file name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `size`, `uploadedAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Move files into another folder
```bash
sim files move [options]
```
Also available as `sim files mv`.
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line). |
| `--to <value>` | No | Destination folder path; omit for root. |
</CommandTable>
## Rename a file
```bash
sim files rename <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | New file name, including its extension. |
</CommandTable>
## Restore file
```bash
sim files restore create <fileId>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
## Replace a file’s contents
```bash
sim files set-content <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--content <value>` | Yes | Complete replacement content for the file. The 70,000,000-character bound guards the JSON envelope; the decoded bytes must be at most 50 MiB, and a longer base64 payload is rejected with `413`. |
| `--encoding <value>` | No | Content encoding. Accepted values: `utf-8`, `base64`. |
</CommandTable>
## Upload a file to the workspace
```bash
sim files upload <path> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Local file to upload |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--folder <path>` | No | Destination folder path (defaults to /). |
| `--name <name>` | No | Store it under a different name. |
</CommandTable>
## Get a file’s content
```bash
sim files get <fileId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File whose content to read |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-o, --output-file <path>` | No | Write content to a file instead of stdout. |
| `--force` | No | Overwrite --output-file if it already exists. |
</CommandTable>
## List file resources and child folders together
```bash
sim files ls [path] [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | No | Folder path to list; defaults to the root folder |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <text>` | No | Filter folders and resources by name. |
| `--limit <n>` | No | Maximum combined items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Create a file directory at a path
```bash
sim files mkdir <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path to create; the leading / is optional |
</CommandTable>
+162
View File
@@ -0,0 +1,162 @@
---
title: Sim CLI
description: Drive workflows, tables, files, knowledge bases, and logs from your shell
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
`sim` is the command line for Sim. Sign in once, then run workflows, query tables,
move files, search knowledge bases, and read run logs from the terminal. Every
command has a `--output json` mode, so results pipe cleanly into `jq`, cron jobs,
CI pipelines, and any other tool you already use.
## Install
<Tabs items={['npm', 'pnpm', 'bun', 'yarn']}>
<Tab value="npm">
```bash
npm install --global sim
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add --global sim
```
</Tab>
<Tab value="bun">
```bash
bun add --global sim
```
</Tab>
<Tab value="yarn">
```bash
yarn global add sim
```
</Tab>
</Tabs>
The CLI needs **Node.js 20 or newer**. Verify the install:
```bash
sim --version
```
Prefer using Sim as a library? See the [TypeScript](/api-reference/typescript)
and [Python](/api-reference/python) SDKs, or call the
[HTTP API](/api-reference/getting-started) directly.
## Your first command
<Steps>
<Step>
### Sign in
```bash
sim login
```
The terminal prints a pairing code and a URL. Approve it in the browser, pick a
workspace, and the key comes back over the CLI's own connection. Nothing
redeemable crosses the browser leg and there is no loopback listener, so this
works over SSH and inside containers.
See [Authentication](/cli/authentication) for CI keys, multiple accounts, and
self-hosted deployments.
</Step>
<Step>
### Check what you are pointed at
```bash
sim whoami
```
This prints the resolved endpoint, workspace, and output format — and **where
each one came from**. It is the fastest way to explain a surprising result.
</Step>
<Step>
### List your workflows
```bash
sim workflows list
```
```
ID NAME FOLDER DEPLOYED RUNS LAST RUN
wf_7Yb2 Refund triage /Support yes 412 2026-08-15 14:02:11
wf_9Kd4 Weekly digest /Reporting no 18 2026-08-11 09:00:04
```
</Step>
<Step>
### Run one
```bash
sim workflows run wf_7Yb2 --input '{"ticketId":"T-4821"}'
```
A workflow must be deployed before it can be run. Deploy from the editor, or
with `sim workflows deploy <id>`.
</Step>
</Steps>
## How commands are shaped
Every command reads the same way:
```bash
sim <resource> [sub-resource] <verb> [arguments] [options]
```
```bash
sim workflows list
sim tables rows query tbl_123 --limit 50
sim knowledge documents upload kb_123 ./handbook.pdf
```
Resource groups are plural, and each also accepts its singular spelling —
`sim workflow get` and `sim workflows get` are the same command. `knowledge`
also answers to `kb`.
Every command accepts `--help`, at any depth:
```bash
sim --help
sim tables --help
sim tables rows query --help
```
## What you can do
| Group | What it covers |
| --- | --- |
| [`workflows`](/cli/workflows) | Run, deploy, roll back, import, export, and organize workflows |
| [`logs`](/cli/logs) | Read run diagnostics, including the full trace tree |
| [`tables`](/cli/tables) | Query, insert, update, and import rows; manage columns and views |
| [`files`](/cli/files) | Upload, download, share, and organize workspace files |
| [`knowledge`](/cli/knowledge) | Search knowledge bases and manage their documents and tags |
| [`skills`](/cli/skills) | Manage agent skills |
| [`mcp-servers`](/cli/mcp-servers) | Manage MCP server connections and their tools |
| [`custom-tools`](/cli/custom-tools) | Manage custom tool definitions |
| [`credentials`](/cli/credentials) | Connect, reconnect, and disconnect integration credentials |
| [`secrets`](/cli/secrets) | Set and remove workspace secrets |
| [`billing`](/cli/billing) | Check plan status and credit usage |
| [`audit-logs`](/cli/audit-logs) | Read organization audit logs |
| [`workspaces`](/cli/workspaces) | Inspect the active workspace and its members |
The [command reference](/cli/commands) documents every subcommand, argument, and
flag, and is generated from the CLI itself.
## Where to go next
- [Authentication](/cli/authentication) — signing in, API keys for CI, and multiple accounts
- [Configuration](/cli/configuration) — profiles, config files, environment variables, and precedence
- [Output formats](/cli/output) — `table`, `json`, `yaml`, and `text`, and when to use each
- [Scripting](/cli/scripting) — piping, file inputs, exit codes, and automation recipes
- [Troubleshooting](/cli/troubleshooting) — what each error means, and how to resolve it
- [Command reference](/cli/commands) — every command, argument, and flag
+488
View File
@@ -0,0 +1,488 @@
---
title: Knowledge
description: Manage knowledge — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim knowledge` is also spelled `sim kb`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Enable or disable every matching document
```bash
sim knowledge documents batch-update <knowledgeBaseId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `knowledgeBaseId` | Yes | Unique knowledge base identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--operation <value>` | Yes | Whether the selected documents become enabled or disabled for search. Accepted values: `enable`, `disable`. |
| `--document <value...>` | No | Documents to update, by identifier. (space-separated, or @path / @- with one value per line). |
| `--select-all` | No | Apply to every document in the knowledge base. |
| `--enabled-filter <value>` | No | With `selectAll`, restrict the update to documents in this state. Accepted values: `all`, `enabled`, `disabled`. |
</CommandTable>
## Delete document
```bash
sim knowledge documents delete <knowledgeBaseId> <documentId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `knowledgeBaseId` | Yes | Unique knowledge base identifier. |
| `documentId` | Yes | Unique knowledge document identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Get document
```bash
sim knowledge documents get <knowledgeBaseId> <documentId>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `knowledgeBaseId` | Yes | Unique knowledge base identifier. |
| `documentId` | Yes | Unique knowledge document identifier. |
</CommandTable>
## List documents
```bash
sim knowledge documents list <knowledgeBaseId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `knowledgeBaseId` | Yes | Unique knowledge base identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--search <value>` | No | Case-insensitive substring match against the document filename. |
| `--enabled-filter <value>` | No | Filter by whether documents are enabled for search. Accepted values: `all`, `enabled`, `disabled`. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `filename` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `filename`, `fileSize`, `tokenCount`, `chunkCount`, `uploadedAt`, `processingStatus`, `enabled`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--tag-filters <value>` | No | A JSON-encoded array of at most 10 tag filters, using the same display-name shape as knowledge search: `[&#123;"tagName":"category","operator":"eq","value":"billing"&#125;]`. Every filter must hold, including two that name the same tag. A name that is not defined in this knowledge base is rejected, never ignored. |
</CommandTable>
## Update document
```bash
sim knowledge documents update <id> <documentId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
| `documentId` | Yes | Unique knowledge document identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--filename <value>` | No | New filename for the document. |
| `--enabled` | No | Whether the document participates in search. Disabling keeps it indexed. |
| `--no-enabled` | No | Send --enabled as false. |
| `--tag1 <value>` | No | New value for tag slot 1. |
| `--tag2 <value>` | No | New value for tag slot 2. |
| `--tag3 <value>` | No | New value for tag slot 3. |
| `--tag4 <value>` | No | New value for tag slot 4. |
| `--tag5 <value>` | No | New value for tag slot 5. |
| `--tag6 <value>` | No | New value for tag slot 6. |
| `--tag7 <value>` | No | New value for tag slot 7. |
| `--number1 <value>` | No | New value for number tag slot 1. |
| `--number2 <value>` | No | New value for number tag slot 2. |
| `--number3 <value>` | No | New value for number tag slot 3. |
| `--number4 <value>` | No | New value for number tag slot 4. |
| `--number5 <value>` | No | New value for number tag slot 5. |
| `--date1 <value>` | No | New value for date tag slot 1, formatted YYYY-MM-DD. |
| `--date2 <value>` | No | New value for date tag slot 2, formatted YYYY-MM-DD. |
| `--boolean1` | No | New value for boolean tag slot 1. |
| `--no-boolean1` | No | Send --boolean1 as false. |
| `--boolean2` | No | New value for boolean tag slot 2. |
| `--no-boolean2` | No | Send --boolean2 as false. |
| `--boolean3` | No | New value for boolean tag slot 3. |
| `--no-boolean3` | No | Send --boolean3 as false. |
| `--retry-processing` | No | Requeue a failed or stuck document for processing. Send it alone — no other field may accompany it — and it answers with a queue acknowledgement rather than the document. |
| `--no-retry-processing` | No | Send --retry-processing as false. |
</CommandTable>
## Upload a document to a knowledge base
```bash
sim knowledge documents upload <knowledgeBaseId> <path> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `knowledgeBaseId` | Yes | Knowledge base to upload into |
| `path` | Yes | Local file to upload |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <name>` | No | Store it under a different name. |
| `--tag <value...>` | No | Document tags, in tag1 through tag7 order. |
| `--recipe <name>` | No | Document processing recipe. |
| `--lang <code>` | No | Document language code. |
</CommandTable>
## Create knowledge base
```bash
sim knowledge create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | Human-readable knowledge base name. |
| `--description <value>` | No | Optional knowledge base description. |
| `--chunking-config <json\|@file>` | No | Chunking configuration; defaults are applied when omitted. (JSON, or @path / @- to read a file or stdin). |
| `--folder <value>` | No | Folder path; the leading / is optional. |
</CommandTable>
## Create a knowledge folder at a path
```bash
sim knowledge folders create <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete folder
```bash
sim knowledge folders delete <path> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--recursive` | No | Delete the folder and its descendants. |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## List folders
```bash
sim knowledge folders list [options]
```
Also available as `sim knowledge folders ls`.
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--parent <value>` | No | Direct parent folder path. |
| `--search <value>` | No | Case-insensitive substring match against the folder name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
</CommandTable>
## Rename or move a knowledge folder
```bash
sim knowledge folders move <path> <destination>
```
Also available as `sim knowledge folders mv`.
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
| `destination` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete knowledge base
```bash
sim knowledge delete <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Get knowledge base
```bash
sim knowledge get <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
</CommandTable>
## List knowledge bases
```bash
sim knowledge list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--folder <value>` | No | Folder path; the leading / is optional. |
| `--search <value>` | No | Case-insensitive substring match against the resource name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## List tags
```bash
sim knowledge tags list <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
</CommandTable>
## Search knowledge
```bash
sim knowledge search [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--kb <value...>` | Yes | Knowledge base ID (repeatable) (space-separated, or @path / @- with one value per line). |
| `--query <value>` | No | Text to search for. |
| `--top-k <value>` | No | Maximum number of search results to return. Must be a whole number between 1 and 100; the boundary schema only bounds the range, so a fractional value is admitted here and then rejected with 400 during search. |
| `--tag-filters <json\|@file>` | No | Tag filters as [&#123;"tagName":"...","operator":"...","value":"..."&#125;] (JSON, or @path / @- to read a file or stdin). |
| `--search-mode <value>` | No | Search algorithm. Accepted values: `vector`, `hybrid`. |
| `--reranker-enabled` | No | Re-order retrieved chunks with a reranking model before truncating to `topK`. Ignored for a tag-only search, and billed as an additional search unit. Reranking is best-effort — a provider failure falls back to vector ordering, so check `rerankerStatus` on the response. |
| `--no-reranker-enabled` | No | Send --reranker-enabled as false. |
| `--reranker-model <value>` | No | Reranking model to use when `rerankerEnabled` is true. Defaults to `rerank-v4.0-fast`. Accepted values: `rerank-v4.0-pro`, `rerank-v4.0-fast`, `rerank-v3.5`. |
| `--reranker-input-count <value>` | No | How many candidate chunks to retrieve before reranking. Defaults to four times `topK`, capped at 100. A larger pool costs more retrieval work but gives the reranker more to choose from. |
</CommandTable>
## Update knowledge base
```bash
sim knowledge update <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | No | New knowledge base name. |
| `--description <value>` | No | New knowledge base description. |
| `--chunking-config <json\|@file>` | No | New document chunking configuration. (JSON, or @path / @- to read a file or stdin). |
| `--folder <value>` | No | Folder path; the leading / is optional. |
</CommandTable>
## Move a knowledge base to a folder
```bash
sim knowledge mv <id> <folder>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique knowledge base identifier. |
| `folder` | Yes | Folder path; the leading / is optional |
</CommandTable>
## List knowledge resources and child folders together
```bash
sim knowledge ls [path] [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | No | Folder path to list; defaults to the root folder |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <text>` | No | Filter folders and resources by name. |
| `--limit <n>` | No | Maximum combined items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Create a knowledge directory at a path
```bash
sim knowledge mkdir <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path to create; the leading / is optional |
</CommandTable>
+68
View File
@@ -0,0 +1,68 @@
---
title: Logs
description: Manage logs — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim logs` is also spelled `sim log`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Show run diagnostics
```bash
sim logs get <runId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `runId` | Yes | Unique workflow run identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--trace` | No | Show expanded trace spans with inputs, outputs, errors, timing, and cost. |
</CommandTable>
## List logs
```bash
sim logs list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <value...>` | No | Comma-separated workflow identifiers to include. An empty entry is rejected. (space-separated, or @path / @- with one value per line). |
| `--trigger <value...>` | No | Comma-separated trigger types to include. An empty entry is rejected. Values are matched exactly and are case-sensitive — every recorded trigger is lowercase, so `API` matches nothing while `api` matches. The vocabulary is open: it covers the core trigger types (`manual`, `api`, `schedule`, `chat`, `webhook`, `mcp`, `copilot`, `workflow`, `custom_block`) and the provider id of any webhook trigger (`slack`, `gmail`, `github`, …), so an unrecognized member is not rejected — it selects no runs. The literal value `all` is a sentinel that disables this filter entirely, so a list containing it returns runs of every trigger type; no real trigger type is named `all`. (space-separated, or @path / @- with one value per line). |
| `--level <value>` | No | Severity level to include. Accepted values: `info`, `error`. |
| `--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. |
| `--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. |
| `--min-duration-ms <value>` | No | Minimum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected. |
| `--max-duration-ms <value>` | No | Maximum total execution duration in milliseconds. Whole milliseconds from 0 to 2147483647; the stored duration is a 32-bit integer, so a fractional or out-of-range bound is rejected. |
| `--min-cost <value>` | No | Minimum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run. |
| `--max-cost <value>` | No | Maximum execution cost in USD, from 0 to 1000000. A run is never charged a negative amount, so a negative bound is rejected rather than treated as a filter that matches every run. |
| `--model <value>` | No | AI model used during execution. |
| `--details <value>` | No | Response detail level. Accepted values: `basic`, `full`. |
| `--include-trace-spans` | No | Include trace spans in JSON or YAML output (implies full detail). |
| `--include-final-output` | No | Include final output in JSON or YAML output (implies full detail). |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--order <value>` | No | Sort direction by execution start time. This list is sortable only by execution start time, so it takes `order` in place of `sortBy`/`sortOrder`, which it rejects. Accepted values: `asc`, `desc`. |
| `--run-id <value>` | No | Exact run identifier to match. |
| `--folder <value...>` | No | Folder path; the leading / is optional (space-separated, or @path / @- with one value per line). |
</CommandTable>
@@ -0,0 +1,162 @@
---
title: MCP Servers
description: Manage mcp servers — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim mcp-servers` is also spelled `sim mcp-server`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Create MCP server
```bash
sim mcp-servers create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | Server display name. |
| `--description <value>` | No | Optional server description. |
| `--transport <value>` | No | Transport used to communicate with the server. Applied server-side as `streamable-http` when omitted on create. Accepted values: `streamable-http`. |
| `--url <value>` | Yes | Absolute HTTP or HTTPS endpoint URL without `&#123;&#123;ENV_VAR&#125;&#125;` references. It determines server identity and is immutable: delete and recreate the server to change endpoints. |
| `--auth-type <value>` | No | Authentication method. When omitted, and no `headers` are sent, registration probes the endpoint once to classify it, falling back to `headers` when the probe fails or the server does not advertise OAuth. A server publishing RFC 9728 metadata is therefore stored as `oauth`, and headers configured afterwards will not authenticate — send this field explicitly to pin the method. Accepted values: `none`, `headers`, `oauth`. |
| `--headers <json\|@file>` | No | Write-only request headers sent to the server. Replaced wholesale rather than merged on update: sending this field drops every stored header it does not repeat. (JSON, or @path / @- to read a file or stdin). |
| `--timeout <value>` | No | Per-request timeout in milliseconds. Applied server-side as 30000 when omitted on create. |
| `--retries <value>` | No | Number of retries per request. Applied server-side as 3 when omitted on create. |
| `--enabled` | No | Whether the server tools are available to workflows. Applied server-side as true when omitted on create. |
| `--no-enabled` | No | Send --enabled as false. |
| `--oauth-client-id <value>` | No | Pre-registered OAuth client identifier. Changing it on update revokes the stored OAuth grant and forces reauthorization. |
| `--oauth-client-secret <value>` | No | Write-only pre-registered OAuth client secret. Sending it on update as null or a new value revokes the stored OAuth grant and forces reauthorization, as does switching away from OAuth authentication. |
</CommandTable>
## Delete MCP server
```bash
sim mcp-servers delete <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique MCP server identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Get MCP server
```bash
sim mcp-servers get <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique MCP server identifier. |
</CommandTable>
## List MCP servers
```bash
sim mcp-servers list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <value>` | No | Case-insensitive substring match against the server name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## List MCP server tools
```bash
sim mcp-servers tools list <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique MCP server identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--refresh` | No | Bypass the short-lived per-workspace tool cache and reconnect under your own credentials. A cached result reflects whichever workspace member last ran discovery, so this is the only way to pick up a tool added since then; it costs a live round trip. |
| `--no-refresh` | No | Send --refresh as false. |
</CommandTable>
## Update MCP server
```bash
sim mcp-servers update <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique MCP server identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | No | Server display name. |
| `--description <value>` | No | Optional server description. |
| `--transport <value>` | No | Transport used to communicate with the server. Applied server-side as `streamable-http` when omitted on create. Accepted values: `streamable-http`. |
| `--url <value>` | No | Immutable server URL. When provided, it must equal the current URL; use delete and create to change endpoints. |
| `--auth-type <value>` | No | Authentication method. When omitted, and no `headers` are sent, registration probes the endpoint once to classify it, falling back to `headers` when the probe fails or the server does not advertise OAuth. A server publishing RFC 9728 metadata is therefore stored as `oauth`, and headers configured afterwards will not authenticate — send this field explicitly to pin the method. Accepted values: `none`, `headers`, `oauth`. |
| `--headers <json\|@file>` | No | Write-only request headers sent to the server. Replaced wholesale rather than merged on update: sending this field drops every stored header it does not repeat. (JSON, or @path / @- to read a file or stdin). |
| `--timeout <value>` | No | Per-request timeout in milliseconds. Applied server-side as 30000 when omitted on create. |
| `--retries <value>` | No | Number of retries per request. Applied server-side as 3 when omitted on create. |
| `--enabled` | No | Whether the server tools are available to workflows. Applied server-side as true when omitted on create. |
| `--no-enabled` | No | Send --enabled as false. |
| `--oauth-client-id <value>` | No | Pre-registered OAuth client identifier. Changing it on update revokes the stored OAuth grant and forces reauthorization. |
| `--oauth-client-secret <value>` | No | Write-only pre-registered OAuth client secret. Sending it on update as null or a new value revokes the stored OAuth grant and forces reauthorization, as does switching away from OAuth authentication. |
</CommandTable>
+29
View File
@@ -0,0 +1,29 @@
{
"title": "CLI",
"root": true,
"pages": [
"---Sim CLI---",
"index",
"authentication",
"configuration",
"output",
"scripting",
"troubleshooting",
"---Commands---",
"commands",
"audit-logs",
"billing",
"credentials",
"custom-tools",
"files",
"knowledge",
"logs",
"mcp-servers",
"secrets",
"skills",
"tables",
"workflows",
"workspaces",
"reference"
]
}
+106
View File
@@ -0,0 +1,106 @@
---
title: Output formats
description: table, json, yaml, and text — what each is for, and how to select one
---
import { Callout } from 'fumadocs-ui/components/callout'
Every command renders through the same four formats. Pick one per command with
`--output`, save a profile default with `sim configure --set-output`, or set
`SIM_OUTPUT` ambiently for CI.
| Format | For |
| --- | --- |
| `table` | reading (default) |
| `json` | piping into `jq` |
| `yaml` | piping into anything that reads YAML |
| `text` | shell loops — tab-separated, no header, no colour |
```bash
sim --output json tables get tbl_123 # before the command
sim tables get tbl_123 --output json # or after; both work
sim configure --set-output json # for this profile, from now on
SIM_OUTPUT=yaml sim logs list > logs.yaml # for one invocation or a whole job
```
## Raw values versus rendered cells
`json` and `yaml` emit the API's **raw** values, not the table's formatting — a
duration stays `1500`, not `"1.5s"`. Switching format never changes the data,
only how it is displayed.
`text` uses the rendered cells, because it exists for shell plumbing rather than
parsing.
<Callout type="info">
An absent value prints as an em-dash (`—`) in `table` and as an **empty field**
in `text`, so emptiness tests downstream behave as you would expect.
</Callout>
## table
The default. Uppercase dim headers, one row per record, values formatted for
reading — timestamps without milliseconds, sizes as `4.2 MB`, booleans as
`yes`/`no`, costs as `$0.0142`.
Long cells are clipped so rows stay on one line. When you need the untruncated
value, switch to `json`.
```bash
sim files list
```
## json
```bash
sim logs list --level error --output json | jq -r '.[].runId'
sim tables rows query tbl_123 --output json | jq '.[] | select(.score > 10)'
```
## yaml
```bash
sim workflows get wf_123 --output yaml
sim logs get run_123 --output yaml > run.yaml
```
## text
Tab-separated, no header, no colour — built for `read` loops:
```bash
SIM_OUTPUT=text sim files list | while IFS=$'\t' read -r id name folder size type uploader uploaded; do
echo "$id $name"
done
```
## Reading a run in detail
`sim logs get` keeps its default human output concise. Add `--trace` for the
expanded recursive trace — span inputs, outputs, errors, timing, and cost:
```bash
sim logs get run_123 --trace
```
`json` and `yaml` always carry the complete structured response, so `--trace` is
a no-op there — the data is already present:
```bash
sim logs get run_123 --output json | jq '.traceSpans'
sim logs list --include-trace-spans --output json
```
## Commands that ignore the format
`sim profiles` and `sim configure`'s listing mode always print for humans — they
report on your local configuration rather than on API data.
`sim workflows export` is the opposite case: it emits a document rather than a
record to look at, so it prints raw JSON — or YAML when the profile says so —
whatever the display format is. That is what makes it round-trip:
```bash
sim workflows export wf_123 > wf.json
sim workflows import --workflow @wf.json
```
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
---
title: Scripting
description: File and stdin inputs, list flags, pagination, exit codes, and automation recipes
---
import { Callout } from 'fumadocs-ui/components/callout'
The CLI is built to be driven by other programs. Everything below applies to
every command.
## Reading input from files and stdin
Any flag that takes JSON or a list also accepts `@path` to read a file, or `@-`
to read stdin.
```bash
sim workflows import --workflow @wf.json
sim tables rows query tbl_123 --filter @filter.json
cat wf.json | sim workflows import --workflow @-
```
This keeps large payloads out of your shell history and out of the argument
length limit.
## List flags
Primitive lists take space-separated values. With `@`, the file supplies one
value per line:
```bash
sim files mv --file-ids file_1 file_2 --to Archive
sim files mv --file-ids @file-ids.txt --to Archive
printf 'file_1\nfile_2\n' | sim files mv --file-ids @- --to Archive
```
Arrays of objects stay JSON inputs, because they cannot be flattened to a list
without losing structure.
## Filtering table rows
`--filter` takes the same predicate tree the API uses: `all` (AND) or `any` (OR)
groups of `{field, op, value}` conditions, nestable. It is JSON because the
grammar is a tree, and there is no honest flag encoding for one.
```bash
sim tables rows query tbl_123 \
--filter '{"all":[{"field":"status","op":"eq","value":"open"},
{"field":"score","op":"gt","value":10}]}' \
--limit 50
```
Operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `contains`,
`ncontains`, `startsWith`, `endsWith`, `like`, `ilike`, `nlike`, `nilike`,
`isEmpty`, `isNotEmpty`, `isNull`, `isNotNull`.
`--sort` is also JSON, an ordered list of keys:
```bash
sim tables rows query tbl_123 --sort '[{"field":"createdAt","direction":"desc"}]'
```
Row columns are discovered at runtime from the returned data, unioned across the
page, so a sparse row does not hide a column.
## Pagination
List commands page automatically up to `--limit`, which defaults to `100`. Pass
`--limit 0` to fetch everything:
```bash
sim logs list --limit 0 --output json > all-logs.json
```
## Destructive commands
Deletions require an explicit selector **and** `--yes`. There is no "delete
everything" default:
```bash
sim tables rows batch-delete tbl_123 --row row_1 row_2 --yes
sim files delete file_123 --yes
```
Without `--yes` the command explains what it would have destroyed and stops.
<Callout type="warn">
`batch-delete` and `batch-update` also carry the standard `--limit` default of
`100`. Set `--limit 0` when you intend to affect every matching row.
</Callout>
## Exit codes
| Code | Meaning |
| --- | --- |
| `0` | Success |
| `1` | Anything else — API error, bad configuration, invalid arguments, or a missing `--yes` |
Errors print one line to stderr, prefixed `Error:`, plus the API's error code and
validation details when it supplies them. Failures are safe to branch on:
```bash
if ! sim workflows run wf_7Yb2 --output json > result.json; then
echo "run failed" >&2
exit 1
fi
```
<Callout type="info">
An unexpected error keeps its stack trace on purpose — that is a bug in the CLI,
and hiding it behind a friendly message would make it unreportable. Please
[open an issue](https://github.com/simstudioai/sim/issues) if you see one.
</Callout>
## Selecting workflow output
`--select-output` takes `blockName.field` selectors. Fields that a run did not
produce are simply omitted:
```bash
sim workflows run wf_7Yb2 --select-output agent_1.content --output json
```
## Polling a long run
Start the run asynchronously, then poll its status:
```bash
run_id=$(sim workflows run wf_7Yb2 --async --output json | jq -r '.runId')
until sim workflows runs get "$run_id" --workflow wf_7Yb2 --output json \
| jq -e '.status | IN("completed","failed","cancelled")' > /dev/null; do
sleep 5
done
sim logs get "$run_id" --trace
```
`workflows runs get` is the lightweight status resource; `logs get` is the full
diagnostic one. For a paused run, the status includes the context ID that
`sim workflows runs resume` needs.
## Working with folders
Every folder-backed resource — `workflows`, `tables`, `files`, `knowledge` —
shares the same path commands:
```bash
sim tables ls Reports
sim tables mkdir Reports/Quarterly
sim tables folders mv Reports/Quarterly Archive/Quarterly
sim tables folders delete Archive --recursive --yes
```
`ls` is a directory view: it combines the resources at its path with that
folder's direct child folders, and never includes deeper descendants. Its `ref`
column is the resource ID or canonical folder path to pass to the next command.
Use `list` when you want resources only, or `folders ls` for folders only.
The leading `/` is optional on input; the API returns the canonical
leading-slash form.
## A nightly job, end to end
```bash title="nightly-digest.sh"
#!/usr/bin/env bash
set -euo pipefail
export SIM_API_KEY="${SIM_API_KEY:?missing}"
export SIM_WORKSPACE="${SIM_WORKSPACE:?missing}"
export SIM_OUTPUT=json
run_id=$(sim workflows run wf_7Yb2 --input '{"source":"nightly"}' | jq -r '.runId')
if [ "$(sim workflows runs get "$run_id" --workflow wf_7Yb2 | jq -r '.status')" != "completed" ]; then
sim logs get "$run_id" >&2
exit 1
fi
```
+84
View File
@@ -0,0 +1,84 @@
---
title: Secrets
description: Manage secrets — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim secrets` is also spelled `sim secret`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Delete secret
```bash
sim secrets delete <name> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `name` | Yes | Secret to create, replace, or delete. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--scope <value>` | Yes | Whether the secret belongs to the workspace or to the caller. A personal secret belongs to the caller across every workspace, not to one workspace. Accepted values: `workspace`, `personal`. |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## List secrets
```bash
sim secrets list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--scope <value>` | No | Restrict results to one ownership scope. Accepted values: `workspace`, `personal`. |
| `--search <value>` | No | Case-insensitive substring match against the secret name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Create or replace a named secret
```bash
sim secrets set <name> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `name` | Yes | Secret name, as referenced in workflows |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--scope <scope>` | Yes | Secret ownership scope. Accepted values: `workspace`, `personal`. |
| `--value <value>` | No | Secret value; visible to shell history when supplied directly. |
</CommandTable>
+117
View File
@@ -0,0 +1,117 @@
---
title: Skills
description: Manage skills — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim skills` is also spelled `sim skill`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Create skill
```bash
sim skills create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | Kebab-case name, unique within the workspace and not reserved by a built-in skill. |
| `--description <value>` | Yes | One-line summary of when the skill applies. |
| `--content <value>` | Yes | Skill body containing the instructions given to the agent. |
</CommandTable>
## Delete skill
```bash
sim skills delete <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Get skill
```bash
sim skills get <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`. |
</CommandTable>
## List skills
```bash
sim skills list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <value>` | No | Case-insensitive substring match against the skill name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Update skill
```bash
sim skills update <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | No | New kebab-case skill name. |
| `--description <value>` | No | New one-line summary of when the skill applies. |
| `--content <value>` | No | Replacement skill body. |
</CommandTable>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,244 @@
---
title: Troubleshooting
description: What the CLI's errors mean, and the fastest way to resolve each one
---
import { Callout } from 'fumadocs-ui/components/callout'
Every error the CLI can explain prints one line to stderr, prefixed `Error:`, and
exits `1`. Where the API supplies an error code or validation details, those
follow on dimmed lines.
<Callout type="info">
Start with `sim whoami`. Most surprises are a command running against a different
profile, endpoint, or workspace than you assumed, and `whoami` prints the winning
value for each setting **and where it came from**.
</Callout>
## Authentication
### `Not logged in on profile "default". Run: sim login --profile default`
No API key resolved for the profile. Either sign in, or supply a key through the
environment:
```bash
sim login
# or, for CI
export SIM_API_KEY="sim_…"
```
Remember that the key is per profile. If you signed in as `dev` and are running
without `--profile dev`, the default profile is still unauthenticated.
### An error ending in `— run: sim login --profile <name>`
The API rejected the key with a `401`. The key was revoked, or it belongs to a
different deployment than the endpoint you are pointed at. Sign in again:
```bash
sim login --profile <name>
```
Check the endpoint first if you did not expect this — a key minted against a
local stack will not authenticate against production:
```bash
sim whoami --profile <name>
```
### `Timed out waiting for browser approval.`
`sim login` waits 15 minutes for you to approve the pairing code. Run it again.
If the browser never opened, use the printed URL directly:
```bash
sim login --no-browser
```
### `Profile "default" already exists. Re-run with --yes to overwrite it.`
Signing in again over a profile that already holds a key. This is a guard, not a
failure — confirm you mean to replace it:
```bash
sim login --yes
```
## Workspace
### `No workspace set for profile "default". Pass --workspace, or run: sim configure --profile default --set-workspace <id>`
The command is workspace-scoped and no workspace resolved. Set one for the
profile, pass it per command, or export it:
```bash
sim configure --set-workspace ws_abc123
sim tables list --workspace ws_abc123
export SIM_WORKSPACE=ws_abc123
```
The workspace ID is in the Sim URL: `https://sim.ai/workspace/{workspaceId}/…`.
<Callout type="warn">
`sim login` sets the profile's default workspace to whichever one you picked on
the approval page. It does **not** limit the key to that workspace — use
`--workspace` to reach any other workspace the key can access.
</Callout>
### A command returns rows from the wrong workspace
The workspace resolved from a higher-priority source than you expected. `--workspace`
beats `SIM_WORKSPACE`, which beats the profile. Confirm with `sim whoami`.
`sim billing status`, `sim billing logs`, and `sim audit-logs list` also accept
`--all-workspaces`, which drops the filter entirely and cannot be combined with
`--workspace`.
## Connectivity
### `Could not reach https://sim.ai: <reason>`
The request never got a response — DNS, TLS, a proxy, or a self-hosted stack that
is not running. Check the endpoint the CLI actually used:
```bash
sim whoami
```
For a local deployment, confirm it is up and that the endpoint has the right port
and scheme:
```bash
sim configure --set-endpoint http://localhost:3000 --profile local
```
### `Request cancelled.`
The request was aborted, usually by `Ctrl-C` or a CI job timeout. Re-run it.
## Arguments and flags
### `error: missing required argument '<name>'`
The CLI prints an `Example:` line beneath showing the full invocation. `--help`
gives the complete signature at any depth:
```bash
sim tables rows query --help
```
### `error: option '--x <value>' argument 'y' is invalid. Allowed choices are …`
The value is outside the accepted set. The accepted values are in `--help` and in
this section's [command reference](/cli/commands).
### `<message> Re-run with --yes to confirm.`
A destructive command needs explicit confirmation. The message says what would be
destroyed — read it, then repeat the command with `--yes`:
```bash
sim files delete file_123 --yes
```
There is no "delete everything" default: deletions require an explicit selector
**and** `--yes`.
### `--limit must be a non-negative number`
Use `0` for "everything" rather than a negative number:
```bash
sim logs list --limit 0
```
### `--input @- reads stdin, but nothing is piped in`
`@-` reads from stdin, so something has to be piped to it. Either pipe a value or
point at a file:
```bash
cat wf.json | sim workflows import --workflow @-
sim workflows import --workflow @wf.json
```
### A JSON flag rejects a value that looks like valid JSON
Your shell probably consumed the quotes. Wrap the whole value in single quotes,
or read it from a file:
```bash
sim tables rows query tbl_123 --filter '{"all":[{"field":"status","op":"eq","value":"open"}]}'
sim tables rows query tbl_123 --filter @filter.json
```
## Output
### `Error: … output …` naming the accepted formats
A stored or exported output format is not one of `table`, `json`, `yaml`, or
`text`. A higher-priority source still wins, so you can repair the profile
without editing the file:
```bash
sim --output table configure --set-output json
```
### A value looks truncated
`table` clips long cells so rows stay on one line. The data is not truncated —
switch to a machine format to see it in full:
```bash
sim logs get run_123 --output json
```
### `sim profiles` ignores `--output`
`sim profiles` and `sim configure`'s listing mode always print for humans; they
report on your local configuration rather than on API data.
## Files
### `<path> already exists. Pass --force to overwrite it, or choose another output path.`
`sim files get -o` will not overwrite an existing file by accident:
```bash
sim files get file_123 -o ./report.csv --force
```
### `sim files get` refuses to print to the terminal
Writing arbitrary binary to an interactive terminal can corrupt it, so non-text
content must go to a file or a pipe:
```bash
sim files get file_123 -o ./image.png
sim files get file_123 | shasum
```
## Secrets
### `Interactive secret input requires a terminal. Pass --value instead.`
`sim secrets set` prompts with masked input when it can. In CI there is no
terminal, so supply the value directly — from a CI secret, not a literal:
```bash
sim secrets set MY_KEY --scope workspace --value "$MY_KEY"
```
## Something else
An unexpected error keeps its stack trace on purpose — that is a bug in the CLI,
not a message meant for you. Please
[open an issue](https://github.com/simstudioai/sim/issues) with the command you
ran and the trace.
Include the version:
```bash
sim --version
```
+566
View File
@@ -0,0 +1,566 @@
---
title: Workflows
description: Manage workflows — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim workflows` is also spelled `sim workflow`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Cancel a running workflow run
```bash
sim workflows runs cancel <runId> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `runId` | Yes | Unique workflow run identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <workflowId>` | Yes | Workflow ID. |
</CommandTable>
## Show run status
```bash
sim workflows runs get <runId> [options]
```
Show run status (requested outputs are included in JSON or YAML output)
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `runId` | Yes | Unique workflow run identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <workflowId>` | Yes | Workflow ID. |
| `--include-output` | No | Include the final output in JSON or YAML output. |
| `--select-output <value...>` | No | Include blockName.field values in JSON or YAML output (e.g. agent_1.content) (space-separated, or @path / @- with one value per line). |
</CommandTable>
## List runs for a workflow
```bash
sim workflows runs list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <workflowId>` | Yes | Workflow ID. |
| `--status <value>` | No | Filter by run status. Accepted values: `pending`, `running`, `completed`, `failed`, `cancelled`, `paused`. |
| `--trigger <value>` | No | Filter by trigger type. |
| `--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. |
| `--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. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--order <value>` | No | Sort direction by run start time. This list is sortable only by run start time, so it takes `order` in place of `sortBy`/`sortOrder`, which it rejects. Accepted values: `asc`, `desc`. |
</CommandTable>
## Resume a paused run
```bash
sim workflows runs resume <runId> [options]
```
Resume a paused run (output is included in JSON or YAML output)
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `runId` | Yes | Unique workflow run identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <workflowId>` | Yes | Workflow ID. |
| `--context <value>` | Yes | Pause context ID returned by run status. |
| `--input <json\|@file>` | No | Resume input as JSON (JSON, or @path / @- to read a file or stdin). |
</CommandTable>
## Create workflow
```bash
sim workflows create [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | Yes | Workflow name. |
| `--description <value>` | No | Optional workflow description. |
| `--folder <value>` | No | Folder path; the leading / is optional. |
</CommandTable>
## Create a workflow folder at a path
```bash
sim workflows folders create <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete workflow folder
```bash
sim workflows folders delete <path> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--recursive` | No | Delete the folder and its descendants. |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## List workflow folders
```bash
sim workflows folders list [options]
```
Also available as `sim workflows folders ls`.
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--parent <value>` | No | Direct parent folder path. |
| `--search <value>` | No | Case-insensitive substring match against the folder name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `name`, `createdAt`, `updatedAt`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
</CommandTable>
## Rename or move a workflow folder
```bash
sim workflows folders move <path> <destination>
```
Also available as `sim workflows folders mv`.
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path; the leading / is optional |
| `destination` | Yes | Folder path; the leading / is optional |
</CommandTable>
## Delete workflow
```bash
sim workflows delete <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `-y, --yes` | No | Skip the confirmation. |
</CommandTable>
## Deploy workflow
```bash
sim workflows deploy <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | No | Optional label for the deployment version. |
| `--description <value>` | No | Optional release note for the deployment version. |
</CommandTable>
## Run a deployed workflow
```bash
sim workflows run <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--input <json\|@file>` | No | Trigger input as JSON (JSON, or @path / @- to read a file or stdin). |
| `--async` | No | Queue the run and return immediately. |
| `--execution-timeout-seconds <value>` | No | Requested server-side timeout for an asynchronous run, in seconds. An upper bound, not the effective timeout: the run uses the smaller of this value and the plan's execution timeout, so requesting more than the plan allows silently yields the plan timeout. Rejected with `400` unless `async` is true. |
| `--select-output <value...>` | No | Return blockName.field values (e.g. agent_1.content); missing fields are omitted (space-separated, or @path / @- with one value per line). |
| `--include-file-base64` | No | Inline eligible output files as base64 content. Rejected when `async` is true. |
| `--no-include-file-base64` | No | Send --include-file-base64 as false. |
| `--base64-max-bytes <value>` | No | Maximum total bytes of file content to inline as base64. Rejected when `async` is true. |
</CommandTable>
## Print a workflow as a portable JSON document
```bash
sim workflows export <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
## Get workflow
```bash
sim workflows get <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
## Get workflow deployment
```bash
sim workflows deployment list <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
## Get workflow version
```bash
sim workflows versions get <id> <version>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
| `version` | Yes | Numeric deployment version. |
</CommandTable>
## List workflow versions
```bash
sim workflows versions list <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Import workflow
```bash
sim workflows import [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--workflow <json\|@file>` | Yes | Workflow export object, bare workflow state, or JSON string containing either form. (JSON, or @path / @- to read a file or stdin). |
| `--folder <value>` | No | Folder path; the leading / is optional. |
| `--name <value>` | No | Override for the imported workflow name. |
| `--description <value>` | No | Override for the imported workflow description. |
</CommandTable>
## List workflows
```bash
sim workflows list [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--folder <value>` | No | Folder path; the leading / is optional. |
| `--deployed-only` | No | Return only workflows with an active deployment when true. |
| `--no-deployed-only` | No | Send --deployed-only as false. |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
| `--search <value>` | No | Case-insensitive substring match against the resource name. |
| `--sort-by <value>` | No | Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order. Accepted values: `position`, `name`, `createdAt`, `updatedAt`, `runCount`. |
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
</CommandTable>
## Rollback workflow
```bash
sim workflows rollback <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--version <value>` | No | Deployment version to reactivate. Omit to select the previous active version. |
</CommandTable>
## Take a workflow out of deployment
```bash
sim workflows undeploy <id>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
## Update workflow
```bash
sim workflows update <id> [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--name <value>` | No | Replacement workflow name. |
| `--description <value>` | No | Replacement workflow description; null clears it. |
| `--folder <value>` | No | Folder path; the leading / is optional. |
</CommandTable>
## Move a workflow to a folder
```bash
sim workflows mv <id> <folder>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `id` | Yes | Unique workflow identifier. |
| `folder` | Yes | Folder path; the leading / is optional |
</CommandTable>
## List workflow resources and child folders together
```bash
sim workflows ls [path] [options]
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | No | Folder path to list; defaults to the root folder |
</CommandTable>
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--search <text>` | No | Filter folders and resources by name. |
| `--limit <n>` | No | Maximum combined items to return (0 for everything). Defaults to `100`. |
</CommandTable>
## Create a workflow directory at a path
```bash
sim workflows mkdir <path>
```
**Arguments**
<CommandTable>
| Argument | Required | Description |
| --- | --- | --- |
| `path` | Yes | Folder path to create; the leading / is optional |
</CommandTable>
@@ -0,0 +1,32 @@
---
title: Workspaces
description: Manage workspaces — every subcommand, argument, and flag
---
import { CommandTable } from '@/components/ui/command-table'
`sim workspaces` is also spelled `sim workspace`.
Every command below also accepts the [global options](/cli/commands#global-options).
## Get workspace
```bash
sim workspaces get
```
## List workspace members
```bash
sim workspaces members [options]
```
**Options**
<CommandTable>
| Option | Required | Description |
| --- | --- | --- |
| `--limit <n>` | No | Maximum items to return (0 for everything). Defaults to `100`. |
</CommandTable>
+2
View File
@@ -32,6 +32,8 @@
"check:openapi": "bun run scripts/check-openapi.ts",
"generate:cli-api": "bun run scripts/generate-v2-cli-api.ts",
"check:cli-api": "bun run scripts/generate-v2-cli-api.ts --check",
"generate:cli-docs": "bun run scripts/generate-cli-docs.ts",
"check:cli-docs": "bun run scripts/generate-cli-docs.ts --check",
"check:cron-parity": "bun run scripts/check-cron-parity.ts",
"check:api-validation:strict": "bun run scripts/check-api-validation-contracts.ts --check --enforce-boundary-baseline",
"check:realtime-prune": "bun run scripts/check-realtime-prune-graph.ts",
+12 -8
View File
@@ -8,12 +8,7 @@ sim login
sim workflows list
```
Prerelease channels track the corresponding Sim environments:
```bash
npm install --global sim@staging # staging
npm install --global sim@dev # dev
```
Full documentation: **https://docs.sim.ai/cli**
## Profiles
@@ -108,6 +103,10 @@ Settings → API keys.
## Commands
The commands below are the common ones. The complete reference — every group,
subcommand, argument, and flag, generated from this package — is at
[docs.sim.ai/cli/commands](https://docs.sim.ai/cli/commands).
Plural resource names are canonical, but every plural top-level resource group
also accepts its singular form: for example, `sim table list`,
`sim file get`, and `sim workflow get` are equivalent to their plural
@@ -142,7 +141,7 @@ sim tables list [--folder <path>]
sim tables get <tableId>
sim tables update <tableId> [--name <name>] [--description <text>] [--folder <path>]
sim tables mv <tableId> <folder>
sim tables columns <tableId>
sim tables columns create|update|delete|run <tableId>
sim tables rows list <tableId> [--limit <n>]
sim tables rows create <tableId> --data <json|@file>
sim tables rows create <tableId> --rows <json|@file>
@@ -173,6 +172,8 @@ sim knowledge search --query <text> --kb <id>… [--search-mode vector|hybrid]
sim knowledge documents list <knowledgeBaseId> [--search <text>]
sim knowledge documents get <knowledgeBaseId> <documentId>
sim knowledge documents upload <knowledgeBaseId> <path> [--tag <value>...]
sim knowledge documents update <knowledgeBaseId> <documentId> [--filename <name>] [--enabled]
sim knowledge documents batch-update <knowledgeBaseId> --operation enable|disable
sim knowledge documents delete <knowledgeBaseId> <documentId> --yes
sim billing status [--all-workspaces]
@@ -249,9 +250,12 @@ grammar is a tree; there's no honest flag encoding for it.
sim tables rows query tbl_123 \
--filter '{"all":[{"field":"status","op":"eq","value":"open"},
{"field":"score","op":"gt","value":10}]}' \
--sort score:desc --limit 50
--sort '[{"field":"score","direction":"desc"}]' --limit 50
```
`--sort` is JSON for the same reason: it is an ordered list of keys, each with a
`field` and a `direction` of `asc` or `desc`.
Row columns are discovered at runtime from the returned data, unioned across the
page so a sparse row doesn't hide a column.
+6 -3
View File
@@ -162,7 +162,8 @@ export function attachCredentialCommands(program: Command): void {
if (!credentials) throw new Error('The generated credentials command group is missing')
credentials
.command('create <providerId>')
.command('create')
.argument('<providerId>', 'Service-account provider to create a credential for')
.description('Create a service-account credential using its discovered provider schema')
.requiredOption('--name <displayName>', 'Name shown for the credential in Sim')
.requiredOption(
@@ -179,7 +180,8 @@ export function attachCredentialCommands(program: Command): void {
)
credentials
.command('connect <providerId>')
.command('connect')
.argument('<providerId>', 'OAuth provider to connect')
.description('Create a short-lived link for connecting an OAuth provider')
.requiredOption('--name <displayName>', 'Name shown for the new credential in Sim')
.action(async (providerId: string, options: { name: string }, command: Command) =>
@@ -187,7 +189,8 @@ export function attachCredentialCommands(program: Command): void {
)
credentials
.command('reconnect <credentialId>')
.command('reconnect')
.argument('<credentialId>', 'Existing OAuth credential to re-authorize')
.description('Create a short-lived link for reconnecting an OAuth credential')
.action((credentialId: string, _options: unknown, command: Command) =>
createConnectionLink(command, { credentialId })
@@ -174,7 +174,8 @@ export function isTerminalSafeContentType(contentType: string | null): boolean {
export function attachFileGet(files: Command): void {
files
.command('get <fileId>')
.command('get')
.argument('<fileId>', 'File whose content to read')
.description('Get a file’s content')
.option('-o, --output-file <path>', 'Write content to a file instead of stdout')
.option('--force', 'Overwrite --output-file if it already exists')
@@ -8,7 +8,8 @@ import { printProtocolResult } from './result'
export function attachFileUpload(files: Command): void {
files
.command('upload <path>')
.command('upload')
.argument('<path>', 'Local file to upload')
.description('Upload a file to the workspace')
.option('--folder <path>', 'Destination folder path (defaults to /)')
.option('--name <name>', 'Store it under a different name')
@@ -37,7 +37,9 @@ function uploadMetadata(options: KnowledgeDocumentUploadOptions): Record<string,
export function attachKnowledgeDocumentUpload(documents: Command): void {
documents
.command('upload <knowledgeBaseId> <path>')
.command('upload')
.argument('<knowledgeBaseId>', 'Knowledge base to upload into')
.argument('<path>', 'Local file to upload')
.description('Upload a document to a knowledge base')
.option('--name <name>', 'Store it under a different name')
.option('--tag <value...>', 'Document tags, in tag1 through tag7 order')
@@ -153,7 +153,8 @@ export function attachResourceDirectoryCommands(
config: ResourceDirectoryConfig
): void {
group
.command('ls [path]')
.command('ls')
.argument('[path]', 'Folder path to list; defaults to the root folder')
.allowExcessArguments(false)
.description(`List ${config.kind} resources and child folders together`)
.option('--search <text>', 'Filter folders and resources by name')
@@ -181,7 +182,8 @@ export function attachResourceDirectoryCommands(
})
group
.command('mkdir <path>')
.command('mkdir')
.argument('<path>', 'Folder path to create; the leading / is optional')
.allowExcessArguments(false)
.description(`Create a ${config.kind} directory at a path`)
.action(async (path: string, _options: Record<string, never>, command: Command) => {
@@ -94,7 +94,8 @@ function validateTargetOptions(options: ImportOptions): boolean {
export function attachTableImport(tables: Command): void {
tables
.command('import [path]')
.command('import')
.argument('[path]', 'Local CSV file to import; omit when using --file-id')
.description('Import a CSV, into a new table by default')
.option(
'--name <name>',
+2 -1
View File
@@ -53,7 +53,8 @@ export function attachSecretCommands(program: Command): void {
if (!secrets) throw new Error('The generated secrets command group is missing')
secrets
.command('set <name>')
.command('set')
.argument('<name>', 'Secret name, as referenced in workflows')
.description('Create or replace a named secret')
.addOption(
new Option('--scope <scope>', 'Secret ownership scope')
+14
View File
@@ -115,6 +115,20 @@ export const CLI_CONTRACT: CliContract = {
},
confirm: 'This updates every matching row and cannot be undone.',
},
// Same overload one level down: PATCH `/documents` is the bulk form of PATCH
// `/documents/[documentId]`. Both derived to `knowledge documents update`, and
// because commander resolves a duplicate name to the first registered match,
// the bulk form silently shadowed the single-document one — its flags were
// unreachable from the terminal.
bulkUpdateKnowledgeDocuments: {
command: 'knowledge documents batch-update',
describe: 'Enable or disable every matching document',
pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
flags: {
documentIds: { name: 'document', list: true },
selectAll: { boolean: true, describe: 'Apply to every document in the knowledge base' },
},
},
// `DELETE /workflows/[id]/deploy` is an undeploy, not a delete.
undeployWorkflow: {
command: 'workflows undeploy',
File diff suppressed because it is too large Load Diff
+3 -72
View File
@@ -1,79 +1,10 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs'
import chalk from 'chalk'
import { Command, Option } from 'commander'
import { loginCommand, logoutCommand, profilesCommand, whoamiCommand } from './commands/auth'
import { configureCommand } from './commands/configure'
import { attachCredentialCommands } from './commands/credentials'
import { attachProtocolCommands } from './commands/protocol/index'
import { attachSecretCommands } from './commands/secrets'
import { OUTPUT_FORMATS, ProfileConfigError } from './config/index'
import { ProfileConfigError } from './config/index'
import { formatApiErrorDetails, SimApiError } from './http/client'
import { sanitize } from './output/render'
import { buildGeneratedCommands } from './runtime/build'
const program = new Command()
function readPackageVersion(): string {
const metadata: unknown = JSON.parse(
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
)
if (
typeof metadata !== 'object' ||
metadata === null ||
!('version' in metadata) ||
typeof metadata.version !== 'string'
) {
throw new Error('CLI package metadata is missing a valid version')
}
return metadata.version
}
program
.name('sim')
.description('Talk to the Sim API from your terminal')
.version(readPackageVersion())
.option('-P, --profile <name>', 'Profile to use (env: SIM_PROFILE)')
.option('--endpoint <url>', 'Sim deployment to talk to (env: SIM_ENDPOINT)')
.option('-w, --workspace <id>', 'Workspace to target (env: SIM_WORKSPACE)')
.addOption(
new Option('--output <format>', 'Output format for this command').choices([...OUTPUT_FORMATS])
)
program.addCommand(loginCommand())
program.addCommand(logoutCommand())
program.addCommand(whoamiCommand())
program.addCommand(profilesCommand())
program.addCommand(configureCommand())
for (const command of buildGeneratedCommands()) {
program.addCommand(command)
}
attachCredentialCommands(program)
attachProtocolCommands(program)
attachSecretCommands(program)
program.addHelpText(
'after',
`
Profiles work like the AWS CLI: settings live in ~/.sim/config, keys in
~/.sim/credentials (0600). Select one with -P, --profile, or SIM_PROFILE.
Examples:
$ sim login Authorize the default profile
$ sim login --profile dev --endpoint http://localhost:3000
$ sim workflows list
$ sim logs list --level error --limit 20
$ sim --output json tables get tbl_123 Override output for one command
$ sim configure --set-output json Save a profile output default
$ sim knowledge search --query "refund policy" --kb kb_123
$ sim workflows export wf_123 > wf.json JSON flags read files with @
$ sim workflows import --workflow @wf.json
$ sim whoami --profile dev
`
)
import { buildProgram } from './program'
/**
* Anything the CLI can explain prints as one line and exits 1. An unexpected
@@ -82,7 +13,7 @@ Examples:
*/
async function main() {
try {
await program.parseAsync(process.argv)
await buildProgram().parseAsync(process.argv)
} catch (error) {
if (error instanceof ProfileConfigError) {
console.error(chalk.red(`Error: ${sanitize(error.message)}`))
+90
View File
@@ -0,0 +1,90 @@
import { readFileSync } from 'node:fs'
import { Command, Option } from 'commander'
import { loginCommand, logoutCommand, profilesCommand, whoamiCommand } from './commands/auth'
import { configureCommand } from './commands/configure'
import { attachCredentialCommands } from './commands/credentials'
import { attachProtocolCommands } from './commands/protocol/index'
import { attachSecretCommands } from './commands/secrets'
import { OUTPUT_FORMATS } from './config/index'
import { buildGeneratedCommands } from './runtime/build'
/** Root program description, shared by `--help` and the generated docs. */
export const PROGRAM_DESCRIPTION = 'Talk to the Sim API from your terminal'
export const HELP_EPILOGUE = `
Profiles work like the AWS CLI: settings live in ~/.sim/config, keys in
~/.sim/credentials (0600). Select one with -P, --profile, or SIM_PROFILE.
Examples:
$ sim login Authorize the default profile
$ sim login --profile dev --endpoint http://localhost:3000
$ sim workflows list
$ sim logs list --level error --limit 20
$ sim --output json tables get tbl_123 Override output for one command
$ sim configure --set-output json Save a profile output default
$ sim knowledge search --query "refund policy" --kb kb_123
$ sim workflows export wf_123 > wf.json JSON flags read files with @
$ sim workflows import --workflow @wf.json
$ sim whoami --profile dev
`
function readPackageVersion(): string {
const metadata: unknown = JSON.parse(
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
)
if (
typeof metadata !== 'object' ||
metadata === null ||
!('version' in metadata) ||
typeof metadata.version !== 'string'
) {
throw new Error('CLI package metadata is missing a valid version')
}
return metadata.version
}
/**
* Assemble the complete command tree.
*
* Kept separate from the entrypoint so the documentation generator can walk the
* same tree the terminal parses. A generator that rebuilt the surface from the
* contract instead would be a second implementation of `buildGeneratedCommands`,
* free to drift from the one users actually run.
*
* `version` is optional because the generator reads the package metadata itself
* and the emitted pages must not carry a version that goes stale on every
* release.
*/
export function buildProgram(options: { version?: boolean } = {}): Command {
const program = new Command()
program.name('sim').description(PROGRAM_DESCRIPTION)
if (options.version !== false) program.version(readPackageVersion())
program
.option('-P, --profile <name>', 'Profile to use (env: SIM_PROFILE)')
.option('--endpoint <url>', 'Sim deployment to talk to (env: SIM_ENDPOINT)')
.option('-w, --workspace <id>', 'Workspace to target (env: SIM_WORKSPACE)')
.addOption(
new Option('--output <format>', 'Output format for this command').choices([...OUTPUT_FORMATS])
)
program.addCommand(loginCommand())
program.addCommand(logoutCommand())
program.addCommand(whoamiCommand())
program.addCommand(profilesCommand())
program.addCommand(configureCommand())
for (const command of buildGeneratedCommands()) {
program.addCommand(command)
}
attachCredentialCommands(program)
attachProtocolCommands(program)
attachSecretCommands(program)
program.addHelpText('after', HELP_EPILOGUE)
return program
}
+11 -3
View File
@@ -5,7 +5,7 @@ import { V2_OPERATIONS, type V2OperationName } from '../generated/v2-api'
import { deriveCommandPath } from './derive'
import { executeOperation } from './execute'
import { addOperationOptions } from './options'
import { flagNameFor, isProfileWorkspacePath, PROFILE_INJECTED_FIELD } from './request'
import { flagNameFor, flagSpecFor, isProfileWorkspacePath, PROFILE_INJECTED_FIELD } from './request'
import type { OperationSpec } from './types'
const GROUP_ALIASES: Readonly<Record<string, string>> = {
@@ -95,7 +95,10 @@ function configureOperation(
for (const param of operationSpec.pathParams) {
if (spec.pathFlags?.[param] || isProfileWorkspacePath(spec, param)) continue
command.argument(`<${spec.pathArgumentNames?.[param] ?? param}>`)
command.argument(
`<${spec.pathArgumentNames?.[param] ?? param}>`,
operationSpec.pathParamDocs?.[param]
)
}
if (spec.allWorkspaces) {
@@ -111,7 +114,12 @@ function configureOperation(
if (spec.requestFields && !spec.requestFields.includes(field)) {
throw new Error(`${operation}.${field} is positional but not exposed`)
}
command.argument(`<${flagNameFor(operation, field)}>`)
// A field promoted to a positional keeps the prose it would have carried as
// a flag; the promotion changes where the value is typed, not what it means.
command.argument(
`<${flagNameFor(operation, field)}>`,
flagSpecFor(operation, field).describe ?? descriptor.describe
)
}
if (spec.requestFields) {
+35 -8
View File
@@ -13,6 +13,30 @@ import type { OperationSpec } from './types'
export const DEFAULT_LIMIT = 100
/**
* Help text for one flag, best source first.
*
* The CLI contract wins, because an entry there exists precisely to say
* something the schema cannot — that `workflowIds` is really a list, or that
* `conflictTarget` reads better as `--on`. Otherwise the field's own
* `.describe()` from the route contract carries through: it is the same prose
* the OpenAPI specs publish, so the terminal and the API reference explain a
* field the same way instead of diverging.
*
* `Set <name>` remains as a last resort for a field that documents itself
* nowhere. It is not documentation — it restates the flag name — so it is worth
* treating a fallback that shows up in `--help` as a missing `.describe()` on
* the contract rather than as finished work.
*/
function describeField(
flag: { describe?: string },
descriptor: FieldSpec,
name: string,
field: string
): string {
return flag.describe ?? descriptor.describe ?? `Set ${name.replaceAll('-', ' ') || field}`
}
function addFieldOption(
command: Command,
operation: V2OperationName,
@@ -36,21 +60,24 @@ function addFieldOption(
return
}
const documented = describeField(flag, descriptor, name, field)
if (descriptor.kind === 'boolean' || flag.boolean) {
if (descriptor.required) {
command.addOption(
new Option(
`${short}--${name} <true|false>`,
`${flag.describe ?? `Set ${field}`} (required)`
)
new Option(`${short}--${name} <true|false>`, `${documented} (required)`)
.choices(['true', 'false'])
.makeOptionMandatory()
)
return
}
command.option(`${short}--${name}`, flag.describe ?? `Set ${field}`)
if (!flag.boolean) command.option(`--no-${name}`, `Set ${field} to false`)
command.option(`${short}--${name}`, documented)
// The twin exists to send an explicit `false`. Restating the positive
// flag's prose here inverts its meaning ("Return only deployed workflows"
// on the flag that stops doing exactly that), so it names its counterpart
// instead and lets the reader look up one description, not two.
if (!flag.boolean) command.option(`--no-${name}`, `Send --${name} as false`)
return
}
@@ -58,7 +85,7 @@ function addFieldOption(
const wantsJson = takesJson(descriptor, flag)
const placeholder = takesList ? '<value...>' : wantsJson ? '<json|@file>' : '<value>'
const choices = flag.choices ?? descriptor.values
const describe = `${flag.describe ?? `Set ${name.replaceAll('-', ' ')}`}${
const describe = `${documented}${
takesList
? ' (space-separated, or @path / @- with one value per line)'
: wantsJson
@@ -91,7 +118,7 @@ export function addOperationOptions(
command.addOption(
new Option(
`${short}--${name} <${flag.placeholder ?? 'value'}>`,
`${flag.describe ?? `Set ${name.replaceAll('-', ' ')}`} (required)`
`${flag.describe ?? operationSpec.pathParamDocs?.[param] ?? `Set ${name.replaceAll('-', ' ')}`} (required)`
).makeOptionMandatory()
)
}
+2
View File
@@ -11,6 +11,8 @@ export interface FieldSpec {
required?: boolean
values?: readonly string[]
default?: unknown
/** The field's `.describe()` from the route contract, used as `--help` text. */
describe?: string
}
/**
+2
View File
@@ -5,6 +5,8 @@ export interface OperationSpec {
method: NonNullable<RequestOptions['method']>
path: string
pathParams: readonly string[]
/** `.describe()` per path parameter, used as positional-argument help. */
pathParamDocs?: Record<string, string>
query?: Record<string, FieldSpec>
body?: Record<string, FieldSpec>
opaqueBody?: boolean
+670
View File
@@ -0,0 +1,670 @@
#!/usr/bin/env bun
/**
* Generates the CLI command reference into `apps/docs/content/docs/en/cli`,
* alongside that section's hand-written guides, and owns the section's
* `meta.json` because the sidebar lists one entry per command group.
*
* The source of truth is the command tree the terminal itself parses —
* `buildProgram()` from `packages/sim-cli` — not the CLI contract and not the
* generated operation table. Both of those are upstream of the tree, so reading
* them instead would mean reimplementing `buildGeneratedCommands`, and the docs
* would be free to describe a surface no user can invoke.
*
* Run `bun run generate:cli-docs` after changing a command; `bun run
* check:cli-docs` fails when the checked-in pages are stale, which is how CI
* keeps them honest.
*/
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Command } from 'commander'
import { V2_OPERATIONS } from '../packages/sim-cli/src/generated/v2-api'
import { buildProgram } from '../packages/sim-cli/src/program'
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const OUTPUT_DIR = path.join(ROOT, 'apps/docs/content/docs/en/cli')
/** Commander's synthetic help command is not part of the documented surface. */
const HELP_COMMAND = 'help'
/**
* Hand-written pages in `OUTPUT_DIR`, in sidebar order.
*
* Generated pages sit beside them rather than in a subfolder so each command
* group is a root-level entry under the Commands heading instead of a nested
* folder the reader has to open. That means the stale-file sweep would delete
* these, so they are listed — the same guard `scripts/generate-docs.ts` uses for
* its hand-authored integration pages.
*/
const GUIDE_PAGES = [
'index',
'authentication',
'configuration',
'output',
'scripting',
'troubleshooting',
] as const
/** Generated page holding the global options and the commands that take no resource. */
const OVERVIEW_PAGE = 'commands'
/** Generated page carrying every command at once, for search and for agents. */
const REFERENCE_PAGE = 'reference'
/**
* Sidebar titles for groups whose command name does not title-case cleanly.
* Everything else gets its hyphens split and each word capitalized.
*/
const GROUP_TITLES: Record<string, string> = {
'audit-logs': 'Audit Logs',
'custom-tools': 'Custom Tools',
'mcp-servers': 'MCP Servers',
cli: 'CLI',
}
interface DocumentedCommand {
/** Full invocation path, e.g. `workflows runs get`. */
path: string[]
command: Command
}
function titleFor(name: string): string {
const override = GROUP_TITLES[name]
if (override) return override
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}
function subcommands(command: Command): Command[] {
return command.commands.filter((child) => child.name() !== HELP_COMMAND)
}
/** Depth-first walk yielding every leaf command, in the order commander lists them. */
function collectLeaves(command: Command, prefix: string[]): DocumentedCommand[] {
const children = subcommands(command)
if (children.length === 0) return [{ path: prefix, command }]
return children.flatMap((child) => collectLeaves(child, [...prefix, child.name()]))
}
/**
* Wraps a value in a code span for a Markdown table cell.
*
* A code span already shields `<` and `{` from MDX, and character references
* are NOT decoded inside one — escaping `<` to `&lt;` here would render the
* entity itself, so `|` is the only character that still has to be escaped. It
* has to be: a literal pipe ends the cell, and flags like `--mode <a|b>` and
* the row filter help both contain one.
*/
function code(value: string): string {
return `\`${escapeTablePipes(value)}\``
}
/**
* Escapes a value so a Markdown table row cannot be split by its content.
*
* A backslash has to be doubled before pipes are escaped, or an input already
* ending in one turns `a\` + `|` into `a\\|`: the table parser reads `\\` as an
* escaped backslash, leaving the pipe unescaped, and the cell splits early.
*
* The doubling is correct inside a code span too, even though code spans do not
* process backslash escapes — the table layer consumes one level of escaping
* before inline parsing runs, so `a\\\|` arrives at the code span as `a\|`.
*/
function escapeTablePipes(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|')
}
/**
* Escapes prose — text NOT inside a code span — for a Markdown table cell.
*
* Here the entities are the right answer: MDX reads `{` as the start of a JS
* expression and `<` as the start of a JSX tag, and both appear in help text
* that embeds JSON examples.
*/
function escapeCell(value: string): string {
return escapeTablePipes(escapeProse(value))
}
/** Escapes MDX-significant characters in body prose. */
function escapeProse(value: string): string {
return value
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\{/g, '&#123;')
.replace(/\}/g, '&#125;')
}
function usageLine(entry: DocumentedCommand): string {
const parts = ['sim', ...entry.path]
for (const argument of entry.command.registeredArguments) {
const name = argument.variadic ? `${argument.name()}...` : argument.name()
parts.push(argument.required ? `<${name}>` : `[${name}]`)
}
if (entry.command.options.length > 0) parts.push('[options]')
return parts.join(' ')
}
/**
* Commander help already spells required-ness inside the description of a
* derived flag. The table states it in its own column, so the trailing marker
* would read as "Yes | Workflow ID (required)".
*/
function stripRequiredSuffix(description: string): string {
return description.replace(/\s*\(required\)\s*$/i, '')
}
/** Help text is written without terminal punctuation; appended clauses need it. */
function asSentence(value: string): string {
if (!value) return ''
return /[.!?]$/.test(value) ? value : `${value}.`
}
/** Returns a table-ready cell: escaped prose, with code spans left intact. */
function describeOption(option: Command['options'][number]): string {
const parts = [asSentence(escapeCell(stripRequiredSuffix(option.description || '')))]
if (option.argChoices && option.argChoices.length > 0) {
parts.push(`Accepted values: ${option.argChoices.map(code).join(', ')}.`)
}
if (option.defaultValue !== undefined) {
parts.push(`Defaults to ${code(String(option.defaultValue))}.`)
}
const description = parts.filter(Boolean).join(' ')
return description || '—'
}
/**
* Wraps a Markdown table so `CommandTable` can size its columns.
*
* The blank lines are load-bearing: without them MDX treats the table as raw
* JSX children and stops parsing it as Markdown.
*/
function sizedTable(rows: string[]): string[] {
return ['', '<CommandTable>', '', ...rows, '', '</CommandTable>']
}
function renderArguments(entry: DocumentedCommand): string[] {
const args = entry.command.registeredArguments
if (args.length === 0) return []
// Positional descriptions come from the route contract and are usually absent;
// a column of em-dashes is worse than no column.
const described = args.some((argument) => Boolean(argument.description))
const rows = args.map((argument) => {
const name = argument.variadic ? `${argument.name()}...` : argument.name()
const required = argument.required ? 'Yes' : 'No'
const cells = [code(name), required]
if (described) cells.push(escapeCell(argument.description) || '—')
return `| ${cells.join(' | ')} |`
})
const header = described ? '| Argument | Required | Description |' : '| Argument | Required |'
const divider = described ? '| --- | --- | --- |' : '| --- | --- |'
return ['', '**Arguments**', ...sizedTable([header, divider, ...rows])]
}
function renderOptions(entry: DocumentedCommand): string[] {
const options = entry.command.options
if (options.length === 0) return []
const rows = options.map(
(option) =>
`| ${code(option.flags)} | ${option.mandatory ? 'Yes' : 'No'} | ${describeOption(option)} |`
)
return [
'',
'**Options**',
...sizedTable(['| Option | Required | Description |', '| --- | --- | --- |', ...rows]),
]
}
/**
* Words that must keep their casing when a heading is sentence-cased.
*
* The test for "this word was capitalized only because the summary is Title
* Case" cannot tell `Documents` from `JSON`, and lowercasing the latter is the
* more visible mistake.
*/
const ACRONYMS = new Set([
'API',
'APIs',
'CSV',
'ID',
'IDs',
'JSON',
'MCP',
'OAuth',
'SSE',
'SSO',
'URL',
'YAML',
])
/**
* The command's one-line description, as a heading.
*
* The command itself is the obvious heading and is the wrong one: every entry
* on a page shares the same `sim <group>` prefix, so the table of contents
* became a column of "sim knowledge documents …" that has to be read to the
* last word to tell two entries apart. The description distinguishes them at
* the first word instead, and the exact invocation is still directly below in
* the code block.
*
* A trailing parenthetical is dropped — those qualify behavior ("(requested
* outputs are included in JSON or YAML output)") and belong in the body, not in
* a sidebar entry.
*/
function headingFor(entry: DocumentedCommand): string {
const description = entry.command.description()
if (!description) return `sim ${entry.path.join(' ')}`
return description
.replace(/\s*\([^)]*\)\s*$/, '')
.trim()
.split(' ')
.map((word, index) => {
if (ACRONYMS.has(word)) return word
if (index === 0) return word.charAt(0).toUpperCase() + word.slice(1)
// Only fold words that look Title-Cased; `--tag` or `blockName` stay put.
return /^[A-Z][a-z]+$/.test(word) ? word.toLowerCase() : word
})
.join(' ')
}
interface RenderOptions {
/** Markdown heading depth, so the master page can nest commands under groups. */
level?: number
/**
* Use the invocation as the heading instead of the description.
*
* Correct on the master page for two reasons: descriptions are only unique
* within a group, so "Delete folder" would collide four ways across one page;
* and a reader — human or agent — arriving at a page of every command is
* looking one up by name, not browsing by task.
*/
commandHeadings?: boolean
}
function renderCommand(entry: DocumentedCommand, options: RenderOptions = {}): string[] {
const { level = 2, commandHeadings = false } = options
const aliases = entry.command.aliases()
const heading = commandHeadings ? `sim ${entry.path.join(' ')}` : headingFor(entry)
const description = entry.command.description()
const lines = [`${'#'.repeat(level)} ${escapeProse(heading)}`, '']
if (commandHeadings && description) lines.push(escapeProse(description), '')
lines.push('```bash', usageLine(entry), '```')
// Only when the heading dropped something — otherwise this restates it.
if (
!commandHeadings &&
description &&
description !== heading &&
description.trimEnd().endsWith(')')
) {
lines.push('', escapeProse(description))
}
if (aliases.length > 0) {
const spelled = aliases.map(
(alias) => `\`sim ${[...entry.path.slice(0, -1), alias].join(' ')}\``
)
lines.push('', `Also available as ${spelled.join(', ')}.`)
}
lines.push(...renderArguments(entry), ...renderOptions(entry), '')
return lines
}
function frontmatter(title: string, description: string, imports: string[] = []): string[] {
return [
'---',
`title: ${title}`,
`description: ${description}`,
'---',
'',
"import { CommandTable } from '@/components/ui/command-table'",
...imports,
'',
]
}
/**
* Fails when two commands on one page reduce to the same heading.
*
* Headings are descriptions now, and descriptions are not guaranteed unique the
* way command paths are. Two identical `## ` headings would collide on the same
* anchor, so one table-of-contents entry would scroll to the other command. The
* fix is a distinguishing `describe` in the CLI contract.
*/
function assertDistinctHeadings(page: string, entries: DocumentedCommand[]): void {
const byHeading = new Map<string, string[]>()
for (const entry of entries) {
const heading = headingFor(entry)
byHeading.set(heading, [...(byHeading.get(heading) ?? []), `sim ${entry.path.join(' ')}`])
}
const collisions = [...byHeading].filter(([, commands]) => commands.length > 1)
if (collisions.length === 0) return
for (const [heading, commands] of collisions) {
console.error(`${page}: "${heading}" is the heading for ${commands.join(' and ')}`)
}
console.error(
'\nTwo commands on a page share a heading, so they share an anchor.\n' +
'Give one a distinct `describe` in packages/sim-cli/src/contract/commands.ts.'
)
process.exit(1)
}
/**
* Every command on one page.
*
* A reference split across fourteen pages is fine to browse and bad to consult:
* an agent, or anyone using in-page search, has to guess which page holds a
* command before it can read it. This is the single fetch that answers any
* question about the surface — the same shape as Claude Code's own
* `cli-reference`, and the page `/cli/reference.mdx` serves as raw Markdown.
*/
function renderReferencePage(
program: Command,
groups: Command[],
globals: DocumentedCommand[]
): string {
const lines = [
...frontmatter('Complete reference', 'Every sim command, argument, and flag on a single page', [
"import { Callout } from 'fumadocs-ui/components/callout'",
]),
'Every command the CLI exposes, on one page, generated from the CLI itself.',
'For a guided tour start at the [overview](/cli/commands); this page exists to',
'be searched, bookmarked, and fed to tools.',
'',
'<Callout type="info">',
'Append `.mdx` to any page in these docs to get its raw Markdown —',
'[`/cli/reference.mdx`](/cli/reference.mdx) is this page as plain text. The whole',
'documentation set is also published as [`/llms.txt`](/llms.txt) and',
'[`/llms-full.txt`](/llms-full.txt) for coding agents.',
'</Callout>',
'',
'## Global options',
'',
'These apply to every command, and may be written before or after it.',
// Deliberately unwrapped, like the same table on the overview page:
// `CommandTable` sizes its second column for the `Required` cell of the
// three-column tables, which on this two-column one would crush the
// description into 5.5rem.
'',
'| Option | Description |',
'| --- | --- |',
...program.options.map((option) => `| ${code(option.flags)} | ${describeOption(option)} |`),
'',
]
for (const leaf of globals) lines.push(...renderCommand(leaf, { commandHeadings: true }))
for (const group of groups) {
lines.push(`## sim ${group.name()}`, '')
const aliases = group.aliases()
if (aliases.length > 0) {
lines.push(`Also spelled ${aliases.map((alias) => `\`sim ${alias}\``).join(', ')}.`, '')
}
for (const leaf of collectLeaves(group, [group.name()])) {
lines.push(...renderCommand(leaf, { level: 3, commandHeadings: true }))
}
}
return `${lines.join('\n').trimEnd()}\n`
}
function renderGroupPage(group: Command): string {
const name = group.name()
const leaves = collectLeaves(group, [name])
const aliases = group.aliases()
assertDistinctHeadings(`${name}.mdx`, leaves)
const lines = [
...frontmatter(
titleFor(name),
`${group.description() || `The sim ${name} commands`} — every subcommand, argument, and flag`
),
]
if (aliases.length > 0) {
lines.push(
`\`sim ${name}\` is also spelled ${aliases.map((alias) => `\`sim ${alias}\``).join(', ')}.`,
''
)
}
lines.push(
'Every command below also accepts the [global options](/cli/commands#global-options).',
''
)
for (const leaf of leaves) lines.push(...renderCommand(leaf))
return `${lines.join('\n').trimEnd()}\n`
}
function renderIndexPage(
program: Command,
groups: Command[],
globals: DocumentedCommand[]
): string {
const lines = [
...frontmatter('Overview', 'Global options, and every sim command group'),
'Every `sim` command follows the same shape:',
'',
'```bash',
'sim <resource> [sub-resource] <verb> [arguments] [options]',
'```',
'',
'Resource groups are plural, and each one also accepts its singular spelling —',
'`sim workflow get` and `sim workflows get` are the same command. `knowledge`',
'additionally answers to `kb`.',
'',
'## Global options',
'',
'These apply to every command, and may be written before or after it.',
'',
'| Option | Description |',
'| --- | --- |',
...program.options.map((option) => `| ${code(option.flags)} | ${describeOption(option)} |`),
'',
'## Command groups',
'',
'| Group | Description |',
'| --- | --- |',
...groups.map(
(group) =>
`| [${code(`sim ${group.name()}`)}](/cli/${group.name()}) | ${escapeCell(group.description()) || '—'} |`
),
'',
]
for (const leaf of globals) lines.push(...renderCommand(leaf))
return `${lines.join('\n').trimEnd()}\n`
}
/**
* The section's sidebar.
*
* Generated because the command-group entries are, and a hand-maintained copy
* would drift the moment a group is added. The hand-written guides stay in
* `GUIDE_PAGES` so adding one is an edit here rather than a change to how the
* pages are produced.
*/
function renderMeta(groups: Command[]): string {
return `${JSON.stringify(
{
title: 'CLI',
root: true,
pages: [
'---Sim CLI---',
...GUIDE_PAGES,
'---Commands---',
OVERVIEW_PAGE,
...groups.map((group) => group.name()),
REFERENCE_PAGE,
],
},
null,
2
)}\n`
}
/**
* Fails on two commands sharing one invocation path.
*
* Commander resolves a duplicate name to the first registered match, so the
* loser is unreachable from the terminal while still appearing in `--help` —
* which is how `knowledge documents update` shadowed the single-document
* update. Left alone the generator would emit two identical headings and
* document a command nobody can run, so the collision fails the build here
* instead: the fix is a `command` entry in the CLI contract.
*/
function assertNoDuplicatePaths(leaves: DocumentedCommand[]): void {
const seen = new Set<string>()
const duplicates = new Set<string>()
for (const leaf of leaves) {
const invocation = leaf.path.join(' ')
if (seen.has(invocation)) duplicates.add(invocation)
seen.add(invocation)
}
if (duplicates.size === 0) return
for (const invocation of duplicates) {
console.error(`duplicate command path: sim ${invocation}`)
}
console.error(
'\nTwo operations derive to the same command, so one is unreachable.\n' +
'Give one of them a `command` in packages/sim-cli/src/contract/commands.ts.'
)
process.exit(1)
}
/**
* Fails on a request field that documents itself nowhere.
*
* Without a `.describe()` on the route contract, the CLI can only fall back to
* restating the flag name — `--sort-by Set sort by` — and that fallback lands
* verbatim in `--help` and in these pages. It reads like documentation while
* telling the reader nothing, which is worse than an obvious hole.
*
* The contract is the right place to fix it because the same prose feeds the
* OpenAPI specs and the API reference, so one `.describe()` documents the
* field everywhere it appears.
*
* `workspaceId` and `cursor` are exempt: neither is ever a flag — the profile
* supplies one and pagination consumes the other.
*/
function assertEveryFieldDocumented(): void {
const undocumented: string[] = []
for (const [operation, spec] of Object.entries(V2_OPERATIONS)) {
for (const slot of ['query', 'body'] as const) {
const fields = (spec as Record<string, unknown>)[slot]
if (!fields || typeof fields !== 'object') continue
for (const [field, descriptor] of Object.entries(fields)) {
if (field === 'workspaceId' || field === 'cursor') continue
const described = (descriptor as { describe?: unknown }).describe
if (typeof described !== 'string' || !described.trim()) {
undocumented.push(`${operation}.${slot}.${field}`)
}
}
}
}
if (undocumented.length === 0) return
for (const field of undocumented) console.error(`undocumented request field: ${field}`)
console.error(
`\n${undocumented.length} field(s) would render as "Set <name>" in --help and in the docs.\n` +
'Add a `.describe()` in apps/sim/lib/api/contracts/v2, then run:\n' +
' bun run generate:cli-api && bun run generate:cli-docs'
)
process.exit(1)
}
/** The sidebar names every guide, so a renamed or deleted one must not fail silently. */
function assertGuidesExist(): void {
const missing = GUIDE_PAGES.filter((page) => !fs.existsSync(path.join(OUTPUT_DIR, `${page}.mdx`)))
if (missing.length === 0) return
for (const page of missing) console.error(`missing hand-written guide: cli/${page}.mdx`)
console.error('\nCreate it, or drop it from GUIDE_PAGES in scripts/generate-cli-docs.ts.')
process.exit(1)
}
function build(): Map<string, string> {
assertEveryFieldDocumented()
assertGuidesExist()
const program = buildProgram({ version: false })
const top = subcommands(program)
const groups = top.filter((command) => subcommands(command).length > 0)
const globals = top
.filter((command) => subcommands(command).length === 0)
.map((command) => ({ path: [command.name()], command }))
assertNoDuplicatePaths([
...groups.flatMap((group) => collectLeaves(group, [group.name()])),
...globals,
])
const files = new Map<string, string>()
files.set('meta.json', renderMeta(groups))
files.set(`${OVERVIEW_PAGE}.mdx`, renderIndexPage(program, groups, globals))
files.set(`${REFERENCE_PAGE}.mdx`, renderReferencePage(program, groups, globals))
for (const group of groups) files.set(`${group.name()}.mdx`, renderGroupPage(group))
return files
}
/** Generated pages already on disk. Hand-written guides are not the sweep's to remove. */
function currentFiles(): Map<string, string> {
if (!fs.existsSync(OUTPUT_DIR)) return new Map()
const guides = new Set<string>(GUIDE_PAGES.map((page) => `${page}.mdx`))
return new Map(
fs
.readdirSync(OUTPUT_DIR, { withFileTypes: true })
.filter((entry) => entry.isFile() && !guides.has(entry.name))
.map(
(entry) => [entry.name, fs.readFileSync(path.join(OUTPUT_DIR, entry.name), 'utf8')] as const
)
)
}
function main(): void {
const check = process.argv.includes('--check')
const expected = build()
const actual = currentFiles()
const stale = [...actual.keys()].filter((name) => !expected.has(name))
const changed = [...expected.entries()].filter(([name, content]) => actual.get(name) !== content)
if (check) {
if (stale.length === 0 && changed.length === 0) {
console.log(`CLI docs are up to date (${expected.size} files).`)
return
}
for (const name of changed)
console.error(`stale: ${path.relative(ROOT, path.join(OUTPUT_DIR, name[0]))}`)
for (const name of stale)
console.error(`orphaned: ${path.relative(ROOT, path.join(OUTPUT_DIR, name))}`)
console.error('\nRun `bun run generate:cli-docs` and commit the result.')
process.exit(1)
}
fs.mkdirSync(OUTPUT_DIR, { recursive: true })
for (const name of stale) fs.rmSync(path.join(OUTPUT_DIR, name))
for (const [name, content] of expected) {
fs.writeFileSync(path.join(OUTPUT_DIR, name), content)
}
console.log(
`Wrote ${expected.size} files to ${path.relative(ROOT, OUTPUT_DIR)}` +
(stale.length > 0 ? `, removed ${stale.length} orphaned` : '')
)
}
main()
+39
View File
@@ -291,6 +291,28 @@ function pathParams(routePath: string): string[] {
return [...routePath.matchAll(/\[([^\]]+)\]/g)].map((m) => m[1])
}
/**
* `.describe()` for each path parameter, so a positional argument can explain
* itself the way a flag does.
*
* The params schema is otherwise read only for its field names, which the route
* path already supplies — the prose attached to them was being discarded, and
* `sim tables rows get <tableId> <rowId>` had nothing to say about either.
*/
function pathParamDocs(schema: z.ZodType | undefined): Record<string, string> {
if (!schema) return {}
const json = z.toJSONSchema(schema, { io: 'input', unrepresentable: 'any' }) as JsonSchema
const docs: Record<string, string> = {}
for (const [key, property] of Object.entries(json.properties ?? {})) {
const description = (property as JsonSchema).description
if (typeof description === 'string' && description.trim()) docs[key] = description.trim()
}
return docs
}
/**
* The kind a request field reduces to for the CLI's purposes.
*
@@ -411,6 +433,15 @@ function renderSlotMap(schema: z.ZodType | undefined, indent: string): string |
)
}
if (property.default !== undefined) parts.push(`default: ${JSON.stringify(property.default)}`)
// The contract's own `.describe()` is the field's documentation, and it is
// already what the OpenAPI specs publish. Carrying it here is what lets
// `--help` say what a flag means instead of restating its name back at the
// reader as "Set sort by". Read from the reference site first: a field that
// narrows a shared `$defs` schema describes its own use of it.
const description = properties[key].description ?? property.description
if (typeof description === 'string' && description.trim()) {
parts.push(`describe: ${JSON.stringify(description.trim())}`)
}
return `${indent} ${JSON.stringify(key)}: { ${parts.join(', ')} },`
})
@@ -480,6 +511,14 @@ function render(operations: Operation[]): string {
out.push(` method: '${op.contract.method}',`)
out.push(` path: '${op.contract.path}',`)
out.push(` pathParams: [${params.map((p) => `'${p}'`).join(', ')}] as const,`)
const paramDocs = pathParamDocs(op.contract.params)
const documentedParams = params.filter((p) => paramDocs[p])
if (documentedParams.length > 0) {
const entries = documentedParams.map(
(p) => `${JSON.stringify(p)}: ${JSON.stringify(paramDocs[p])}`
)
out.push(` pathParamDocs: { ${entries.join(', ')} },`)
}
out.push(` responseMode: '${op.contract.response.mode}',`)
// OpenAPI writes `{id}` where the contract writes `[id]`.
const summary = summaries.get(