Skip to content

Commit c930830

Browse files
authored
fix(v2,cli): second audit pass over the v2 API and CLI (#7126)
* fix(chat): resolve a caller-supplied conversation id through its owner The v2 chat route used the caller-supplied conversationId verbatim, with no existence, owner, or workspace check, against a store keyed by bare text with no owner column. A caller who knew another user's conversation id reached that conversation. Ids now resolve through the same owner-scoped loader the web chat path uses, and anything unresolvable answers one uniform 404 before any lifecycle work runs. Omitting the id mints a server-issued conversation. The contract also accepted any 1-128 character string for a column typed uuid, so a malformed id raised a driver error and rendered 500 while an unknown but well-formed id rendered 404 - a shape oracle, and a 500 on ordinary input. The ownership predicate had no coverage anywhere: the route test mocked the module and the lifecycle test drove a chain mock that ignores its where clause, so deleting the owner condition left both suites green. It is now asserted by composition and by condition count, which is what catches a dropped condition. Also renames the reply's model identifier away from a term the project's own copy rules forbid on a user-facing surface. * fix(v2): conceal workspace absence, and stop archived tables faulting their page Two reads answered a caller more than they were entitled to know. A workspace a caller cannot reach at all returned FORBIDDEN while one that does not exist returned NOT_FOUND, so a workspace-key holder could enumerate which workspace ids exist by diffing the two. Both now answer the same absence, using the concealment policy the billing routes already use. A refusal from inside the workspace - a member whose role is too low - still answers FORBIDDEN, because that caller already knows the workspace exists. Separately, archiving a folder cascades onto its tables but leaves each table pointing at the archived folder row. The archived listing resolved those paths strictly, so one such row faulted the whole page and no cursor could step past it - which also made the ids undiscoverable and left restore unreachable for exactly the tables that need it. The archived scope now resolves leniently to the root, where a restore would place them, matching the shipped workflows behavior. Active listings still fault loudly on a dangling folder. * fix(knowledge): validate upload processing options without stranding live sessions recipe and lang were accepted as free strings up to their length caps, silently discarded, and echoed back nowhere, so a typo was unobservable: uploading with a misspelled recipe returned 200 and quietly used the default. Both are now validated at the boundary and a bad value answers 400 naming what is accepted. The accepted recipe set deliberately includes the sentinel every first-party caller sends today alongside the three real chunker recipes, and the three are derived from the chunker's own union so removing one there is a compile error here rather than a silent 400 in production. The same schema also parses metadata read back off a persisted upload session, so tightening it would have thrown out of resume and complete for any session created before this - a 500 on work that could then never finish. The read-back path now drops a value it no longer recognises instead of rejecting it; the request boundary stays strict. Neither field reaches chunking, so nothing here moves chunk boundaries, embeddings, or search results. * fix(v2): honour a requested stats window, and answer a claimed graph id with a conflict Log statistics accepted a start and an end, filtered the totals by them, and then built the series against wall-clock now. Bucket width was computed over a span the caller never asked for, and every bucket past the requested end was structurally empty - so a bounded historical query returned a wrong-width series with fabricated trailing buckets, under a window label that disagreed with the request. Each edge now honours the bound it was given and keeps its previous derivation when omitted, so an unbounded request is unchanged. Separately, block, edge and subflow ids are global primary keys while the delete that precedes a state replace is scoped to one workflow. An id owned by another workflow survived that delete, the insert violated the key, and because callers pass their own transaction the driver error escaped unclassified as a server fault. The write now refuses such an id up front with a conflict naming it, and re-classifies the same violation if one races past the check, since the lock covers only the workflow being written. The dry run checks the ids a commit would insert and reports the warnings a commit would report, which is what its own contract already promised. * fix(secrets): let a workspace secret change its metadata without resending the value Restoring redaction cost more than removing it. The only way to flip a secret back to redacted was to re-send the plaintext, because the write required a value and omitting it fell into an interactive prompt that cannot run in CI. A workspace secret can now change its description or visibility on its own; the stored value is never re-encrypted or rewritten, a write that names no existing secret answers not-found rather than creating one, and a personal secret still requires a value because it has no other writable field. The path parameter was also one shared schema across the write and the delete, so a single description had to cover both and the delete documented an argument that could create and replace. Split, mirroring the credentials pair. The metadata write is a new update against the credentials table, so its scope is asserted by composition and by condition count: an unscoped update would let one workspace flip another workspace's identically-named secret out of redaction, and the cache invalidation would then carry that flag into the other workspace's runtime catalog. * fix(v2): say what an error means in terms the caller can act on A size-limit refusal collapsed every value under a kilobyte to "0 Bytes", so a 28-byte file over a 27-byte ceiling read "is 0 Bytes, above the 0 Bytes limit" - self-contradictory, and useless for choosing a value that would work. Errors and field descriptions also told callers to invoke raw HTTP endpoints. These strings serve the REST reference and the CLI's own help equally, so they now name the operation and its object rather than a method and a path. A sweep test walks every v2 schema description and holds the line, with the remaining offenders in files this change does not own recorded explicitly rather than left to be rediscovered. Listing the editors of a built-in skill claimed the skill did not exist, while reading the same id succeeded - a well-formed request for a real resource is not malformed, so the list answers an empty roster and only the mutations refuse. Bulk folder deletion recorded only the leaf name in its audit trail while the single delete recorded the full path, leaving two same-named folders under different parents indistinguishable after the fact. Bulk chunk enable, disable and delete each treated an unmatched id differently behind one sentence of documentation. They now follow one rule. A workspace-scoped list refused with the name of a resource the caller never addressed, which reads as an empty workspace rather than an unreachable one. * fix(cli): stop a config value forging a section it was never meant to write The config file is written by joining names and values into INI lines, and nothing checked what was in them. A profile name carrying a newline and a section header wrote a section that merged into a different profile and took over its endpoint - and the next command sent that profile's stored API key there. A workspace value could do the same from the other side, since only the endpoint flag validated its input. The refusal now lives at the writer, the single place untrusted text enters the document, with the flag-level checks kept for the better message. Either alone blocks the forgery; the pair is deliberate. Rejecting rather than escaping, because the format has no escape syntax and these files are hand-edited and read by other tools that would not decode one we invented. The forbidden set covers control characters and the two Unicode line separators, which the previous guard missed - those parse as an unreadable line, so the key silently vanished on read and the next write appended a duplicate while the command reported success. Login also wrote the key before the settings, so a malformed response from the deployment could leave a key on disk with no endpoint beside it, and the next command would send it to the default host. Settings are written first, and the response is checked before anything touches disk. Name validation applies only when creating a profile, so a hand-written one that predates the rule keeps working. * fix(docs): tell the reader which key a command needs, and stop the ids contradicting the CLI Around sixty v2 operations refuse a workspace API key, and the CLI's help said nothing about it - the caller found out from a 403 after the request went out. The restriction is already stated in the API spec, so the generator now reads it from there and the command description carries it. The sentinel sentences are imported from the spec's own constants rather than copied, so a reword cannot silently unmark every command, and the test pins the count as well as named operations because a reword confined to one family would otherwise slip past. The generated reference also rendered an empty default as a sentence pointing at nothing - "Defaults to ." - for every repeatable filter. Omitted now, while false and zero still render, which is the trap that shape of check usually walks into. The hand-written guides used a workflow-shaped id for workflows that the CLI's own help says never names one, and five other families were equally wrong. All of them now match the scheme the CLI declares, consistently per entity across pages, with the shared ones taken from that help text so the two read as one voice. The page documenting every flag was linked from nowhere; both landing links pointed at the overview instead. And the generator's test file was absent from the hand-maintained list CI runs, so its guards never executed. * fix(cli): stop a page-size default capping a destructive filter Every request field named limit inherited the pager's default of 100, but only a cursor-paginated command interprets that flag. The two filter-based row mutations declare no cursor, so the default went onto the wire as a row cap: a filter matching 250 rows deleted 100, exited 0, and said nothing - while the confirmation the user had just answered promised every matching row. The flag's own help offered 0 for everything, which those endpoints reject; the unbounded form is the field being absent. The pager's default now applies only where the pager runs, and the tests pin the omission on the request body rather than in help text. A cap typed alongside an explicit row list was silently ignored; it is now refused on the client, where refusing costs nothing to already-installed versions. Lists also truncated at a hundred with no signal in any format, and the two inventory endpoints that do report truncation had that field dropped on the way out - so a caller reconciling against a clipped list could not tell. One note now goes to stderr while stdout stays a bare array, and a flag raised on a later page survives the fold. Also: a folder whose name contains the separator no longer prints a path that resolves to a different folder; validation errors name the flag the user typed instead of the wire field; an unknown subcommand with --help exits non-zero instead of printing the parent's help; a fractional or negative page size is refused rather than floored; an empty query filter is refused rather than silently returning everything; and the two spellings of the missing-workspace message became one. * fix(cli): gate destructive table imports and fix follow-mode rendering A `tables import --mode replace` empties the table before its first batch, so the only warning was in the describe. It now confirms, and the wording tells the truth per mode: cancelling a replace leaves a prefix of the new file with the originals already gone, while an append re-adds its rows if the file is imported twice. `--yes` skips the gate, and the gate runs before the file is opened. Import and export cancellation carried no describe at all; the import one now confirms, the export one records why it deliberately does not. Follow-mode output truncated cells to whatever the first row happened to measure, so a longer status or workflow name arrived clipped with no signal. Cells now clamp at a shared ceiling and pad to the lock, and the log columns carry width floors so a short first page cannot pin a column narrower than its own values. Interrupting a staged download left the staging directory behind; it is now removed on SIGINT and SIGTERM before the signal is re-raised. `--select-output` without `--follow` selected from a response that does not carry outputs, and said nothing. It is refused client-side, with a separate message for `--async`. Its describe now names what the path addresses. `secrets set` always read a value, even when only metadata flags were passed. Off a TTY that was an immediate refusal, so a metadata-only edit exited 1 in CI for a value it was never asked for; on a TTY it stopped to prompt, and the prompt rejects an empty entry, so there was no way to say "leave the stored value alone" short of re-typing the secret. The read is now skipped and the field omitted, which is what lets a metadata-only edit run unattended. On a TTY, setting only a description no longer prompts. Passing both spellings of the reveal flag is refused rather than silently resolved. Four mandatory hand-authored flags now say so, `billing logs` names its key-type scope, and the dispatch list declares its columns. * fix: close the gaps an adversarial review of this branch found A conflict handler added earlier in this branch was dead code. It read the Postgres error code off the thrown object, but the driver error arrives wrapped with the real one on `cause`, so the check returned false on its first line and the 409 never fired. Its test passed only because it threw a flat shape production never produces. It now reads through the cause chain with the shared helpers, compares the constraint name exactly instead of matching a substring of the SQL, and its test throws the real wrapped error. Resuming a conversation checked its workflow and its workspace but not its type, so a conversation created by the web surface could be continued as a CLI turn. It now refuses through the same uniform 404 as every other mismatch, which closes the same omission on the web posting path. Minting one no longer leaves a blank untitled row at the top of the Chat list. The pre-write check on a minted API key refused fewer characters than the writer does, so a key the check accepted could still fail at the write — after the endpoint beside it was already stored, pairing a new endpoint with the previous key. The two had drifted because the set was spelled three times; there is now one. A description claimed a processed count reported only the chunks that changed. The update returns every row it matched, so re-enabling chunks that were already enabled counts them all. Two OpenAPI sentences promised no conflict detection and no persistence warnings in a dry run, both of which the same branch had just made false. A described window was wrong whenever a start was supplied without an end. Listing the editors of a built-in skill answered a read with a modification refusal on the internal surface. Archived table listings could reach the strict folder projector again through a third scope value the input type still allowed. A metadata-only secret write skipped the guard its personal-scope twin has. The internal document boundary still took the two processing fields as unbounded strings. Truncation was reported only from the response envelope, so a clipped file body, row search and workflow-stats list said nothing. A staged download stopped watching for signals before it finished removing its directory, and cleared every listener for the signal rather than its own. Three tests asserted a contract constant against itself; they now drive rendered help, real argv, or real render output. * chore: regenerate the API reference, CLI surface, and CLI docs The published reference still marked a secret value required and described the delete parameter as one that also creates, the CLI surface still lacked the marker that says which operations refuse a workspace key, and the reference rendered an empty sentence for every repeatable filter whose default is an empty list. * test(cli): use the package's own delay helper in the staging poll The audit bans a hand-rolled setTimeout promise. `sim-cli` does not depend on the shared utils package, and its own idiom is `node:timers/promises`. * fix: act on a second review round, and correct two earlier claims The conflict pre-check read block ids from the wrong side. The writer inserts each block's own `id` field while the check read the record key, and the two can diverge because preparation copies a value under its key without reconciling them. Edges already read the value and subflows are genuinely keyed by the record key, so only blocks were wrong — collecting every family from the values, as first suggested, would have broken subflows instead. A minted API key carrying leading or trailing whitespace passed the pre-write check but failed the writer, leaving the new endpoint on disk beside the previous key. It is refused up front now rather than trimmed: a key is opaque, so trimming would store a value the server never issued and turn a loud failure into an unexplained 401 later. The endpoint normalizer does trim, which is what made a padded `--endpoint` fail only after the browser flow had already minted a key. A metadata-only secret write raced with deletion returned 500, because the follow-up read that only assembles the response body threw an unclassified error; it now reports the same not-found the non-racing miss already gave. An unusable output format in the environment silently printed a table instead of refusing. Two validation messages printed control characters verbatim. A dry run now reports the preparation warnings its own commit path returns. The chat route created a titled conversation and never wrote a message, so it appeared in the Chat list promising content it did not have. Both sides of a successful turn are now persisted; a failed turn still writes nothing, so a question is never stored without its answer. Two claims of mine were wrong. The earlier commit message said `secrets set` sent an empty value that overwrote the stored secret — it did not; the prompt refuses off a TTY and rejects empty on one, so the old behaviour was a clean refusal. And the delay helper commit said this package's idiom is `node:timers/promises`; the package carries its own `sleep`, which is the audit's sanctioned home and has five callers. It uses that now. Also: a test asserting a deadlock stays unclassified could not fail, since every candidate rejects it; it now pins a unique violation carrying no constraint name. Workflow ids spelled with the file prefix are corrected in the remaining fixtures, leaving the genuine file ids alone. * fix: close a credential-misdirection path this branch had opened Making the endpoint normalizer trim handled whitespace around a value but not a control character inside one, and the URL parser removes those from anywhere in its input — so a value that reads as one host could resolve to another, and the profile's key went with it. The flag and environment paths never touch the config writer, so its guard did not cover this. The normalizer now refuses the same character set the writer does, which also keeps the invariant that nothing it blesses can be refused by the write that stores it. Comparing the parsed URL back against its input was the alternative and is wrong: the parser rewrites percent-encoding, case, internationalized hosts and default ports, so legitimate endpoints would be refused. The blank-query guard tested for exactly empty, so a whitespace-only value still reached the wire — as a real zero on a numeric filter, an explicit false on a boolean one, and as an encoded space the server then rejected. It now refuses any value that is blank once trimmed, while a body string keeps its meaning, an explicit zero still sends, and a value with content around its whitespace is passed through untouched rather than trimmed. A graph-id conflict reported 409 on the v2 route and fell through the older persistence wrapper as an unclassified 500. That wrapper now classifies orchestration failures through the cause chain, which also fixes a pre-existing case where a workflow archived between authorization and the locked read reported 500 rather than 404. Persisting a chat turn claimed its row by id alone, so a conversation soft-deleted mid-turn still received the messages and was bumped back up the list. It now requires a live row. A turn whose caller hung up after the model had already answered persisted nothing, though the work was done and billed; it now persists and still reports the connection as closed. An empty workspace id from the login response was read as no workspace at all. A published description still promised a language-tag standard the schema does not enforce. The test asserting that a turn is stored before the final event drained the whole response first, so it held whichever order the code used. It now reads the stream incrementally and fails if the write moves after the event.
1 parent 2d4dfcf commit c930830

148 files changed

Lines changed: 7712 additions & 696 deletions

File tree

Some content is hidden

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

apps/docs/content/docs/en/cli/audit-logs.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Every command below also accepts the [global options](/cli/commands#global-optio
1515
sim audit-logs get <auditLogId> [options]
1616
```
1717

18+
Get Audit Log (personal API key required)
19+
1820
**Arguments**
1921

2022
<CommandTable>
@@ -41,6 +43,8 @@ sim audit-logs get <auditLogId> [options]
4143
sim audit-logs list [options]
4244
```
4345

46+
List Audit Logs (personal API key required)
47+
4448
**Options**
4549

4650
<CommandTable>

apps/docs/content/docs/en/cli/authentication.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ https://www.sim.ai/cli/auth?request=…&scope=platform
2424
Waiting for approval…
2525
2626
✓ Logged in. Key stored in /Users/you/.sim/credentials
27-
Personal key, defaulting to ws_abc123. Override per command with --workspace.
27+
Personal key, defaulting to 2f6d0b1c-8a34-4d92-b7e5-31c8a0f45d67. Override per command with --workspace.
2828
```
2929

3030
There is no loopback listener, so this works over SSH and inside containers.
@@ -47,7 +47,7 @@ profile's default `workspace`; it does **not** restrict the key to that
4747
workspace. Target another workspace the key can reach with `--workspace`:
4848

4949
```bash
50-
sim workflows list --workspace ws_other
50+
sim workflows list --workspace 9b4c7e02-1d58-4f36-a0c9-6e2b85df413a
5151
```
5252

5353
`sim login --workspace <id>` preselects a workspace in the picker, and
@@ -58,7 +58,7 @@ a workspace profile:
5858

5959
```bash
6060
sim workspaces list
61-
sim profile add acme --workspace ws_acme
61+
sim profile add acme --workspace 7e2d9c14-6b83-4a55-8f01-c4d3e9a76b28
6262
sim --profile acme whoami
6363
```
6464

@@ -108,9 +108,9 @@ config file:
108108

109109
```bash
110110
export SIM_API_KEY="sim_…"
111-
export SIM_WORKSPACE="ws_abc123"
111+
export SIM_WORKSPACE="2f6d0b1c-8a34-4d92-b7e5-31c8a0f45d67"
112112

113-
sim workflows run wf_7Yb2 --input '{"source":"nightly"}' --output json
113+
sim workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --input '{"source":"nightly"}' --output json
114114
```
115115

116116
Create the key in Sim under **Settings → API keys**. Store it as a secret in your
@@ -130,7 +130,7 @@ jobs:
130130
with:
131131
node-version: '20'
132132
- run: npm install -g sim
133-
- run: sim workflows run wf_7Yb2 --output json
133+
- run: sim workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --output json
134134
env:
135135
SIM_API_KEY: ${{ secrets.SIM_API_KEY }}
136136
SIM_WORKSPACE: ${{ vars.SIM_WORKSPACE }}
@@ -151,8 +151,8 @@ sim workflows list --profile prod
151151
Use workspace profiles when one personal key should target several workspaces:
152152

153153
```bash
154-
sim profile add marketing --workspace ws_marketing
155-
sim profile add support --workspace ws_support
154+
sim profile add marketing --workspace c3a70e58-9f21-4d6b-b842-05e7f19c6a3d
155+
sim profile add support --workspace e0d94b17-3c62-45af-9718-b6a2c8035f4e
156156

157157
sim workflows list --profile marketing
158158
sim workflows list --profile support

apps/docs/content/docs/en/cli/billing.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Show billing status and current-period credit usage (credits and storage require
3131
sim billing logs [options]
3232
```
3333

34+
List credit usage events (a personal API key reports only your own events; a workspace API key reports every member's)
35+
3436
**Options**
3537

3638
<CommandTable>

apps/docs/content/docs/en/cli/configuration.mdx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ sim profiles # list them; * marks the active one
2828
Add a profile for another workspace without creating or copying an API key:
2929

3030
```bash
31-
sim profile add acme --workspace ws_acme
31+
sim profile add acme --workspace 7e2d9c14-6b83-4a55-8f01-c4d3e9a76b28
3232
```
3333

3434
## Setting defaults
3535

3636
```bash
3737
sim configure --set-endpoint http://localhost:3000 --profile dev
38-
sim configure --set-workspace ws_local --profile dev
38+
sim configure --set-workspace 5c81f3a6-0e27-4b94-8d15-a7f60c39b2e8 --profile dev
3939
sim configure --set-output json
4040
```
4141

@@ -76,16 +76,16 @@ repo:
7676
```ini title="~/.sim/config"
7777
[default]
7878
endpoint = https://www.sim.ai
79-
workspace = ws_abc123
79+
workspace = 2f6d0b1c-8a34-4d92-b7e5-31c8a0f45d67
8080
output = table
8181

8282
[profile dev]
8383
endpoint = http://localhost:3000
84-
workspace = ws_local
84+
workspace = 5c81f3a6-0e27-4b94-8d15-a7f60c39b2e8
8585

8686
[profile acme]
8787
auth_profile = default
88-
workspace = ws_acme
88+
workspace = 7e2d9c14-6b83-4a55-8f01-c4d3e9a76b28
8989
```
9090

9191
Keys live in `~/.sim/credentials`, written `0600`:
@@ -132,17 +132,17 @@ filesystem at all.
132132
Workspace-scoped commands need a workspace:
133133

134134
```bash
135-
sim tables list --workspace ws_other
136-
sim configure --set-workspace ws_abc123
137-
export SIM_WORKSPACE=ws_abc123
135+
sim tables list --workspace 9b4c7e02-1d58-4f36-a0c9-6e2b85df413a
136+
sim configure --set-workspace 2f6d0b1c-8a34-4d92-b7e5-31c8a0f45d67
137+
export SIM_WORKSPACE=2f6d0b1c-8a34-4d92-b7e5-31c8a0f45d67
138138
```
139139

140140
For a reusable selection, create a workspace profile backed by the current
141141
stored login:
142142

143143
```bash
144144
sim workspaces list
145-
sim profile add acme --workspace ws_acme
145+
sim profile add acme --workspace 7e2d9c14-6b83-4a55-8f01-c4d3e9a76b28
146146
sim --profile acme tables list
147147
```
148148

apps/docs/content/docs/en/cli/credentials.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Every command below also accepts the [global options](/cli/commands#global-optio
1515
sim credentials delete <credentialId> [options]
1616
```
1717

18+
Disconnect Credential (personal API key required)
19+
1820
**Arguments**
1921

2022
<CommandTable>
@@ -78,6 +80,8 @@ sim credentials list [options]
7880
sim credentials update <credentialId> [options]
7981
```
8082

83+
Update Credential (personal API key required)
84+
8185
**Arguments**
8286

8387
<CommandTable>

apps/docs/content/docs/en/cli/files.mdx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ sim files batch-delete [options]
2121

2222
| Option | Required | Description |
2323
| --- | --- | --- |
24-
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line). |
24+
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
2525
| `-y, --yes` | Yes | Confirm this destructive operation. |
2626

2727
</CommandTable>
@@ -107,7 +107,7 @@ Also available as `sim files folders ls`.
107107
| `--search <value>` | No | Case-insensitive substring match against the folder name. |
108108
| `--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`. |
109109
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
110-
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) returns live folders only; `archived` returns folders a recursive `DELETE` soft-deleted, which is how a caller finds a path to hand to `POST /api/v2/files/folders/restore`. Authorization is identical for both. Accepted values: `active`, `archived`. |
110+
| `--scope <value>` | No | Which lifecycle set to list: `active` (default) returns live folders only; `archived` returns folders a recursive delete soft-deleted, which is how a caller finds a path to hand to the folder restore. Authorization is identical for both. Accepted values: `active`, `archived`. |
111111

112112
</CommandTable>
113113

@@ -194,7 +194,7 @@ sim files describe <fileId> [options]
194194

195195
| Option | Required | Description |
196196
| --- | --- | --- |
197-
| `--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`. |
197+
| `--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 the file is restored. Authorization is identical for both. Accepted values: `active`, `archived`. |
198198

199199
</CommandTable>
200200

@@ -220,6 +220,8 @@ sim files share get <fileId>
220220
sim files share set <fileId> [options]
221221
```
222222

223+
Enable or disable sharing for a file (personal API key required)
224+
223225
**Arguments**
224226

225227
<CommandTable>
@@ -239,7 +241,7 @@ sim files share set <fileId> [options]
239241
| `--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`. |
240242
| `--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`. |
241243
| `--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. |
242-
| `--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). |
244+
| `--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; @@value for a literal leading @). |
243245

244246
</CommandTable>
245247

@@ -258,7 +260,7 @@ sim files list [options]
258260
| `--folder <value>` | No | Folder path as shown in the app; the leading / is optional. |
259261
| `--recursive` | No | Whether the folder filter includes files in subfolders. Defaults to true when a search is set, false otherwise, so listing a folder shows that folder while searching one looks through everything in it. Ignored when no folder filter is set, which already spans the workspace. |
260262
| `--no-recursive` | No | Send --recursive as false. |
261-
| `--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`. |
263+
| `--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`. |
262264
| `--search <value>` | No | Case-insensitive substring match against the file name. |
263265
| `--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`. |
264266
| `--sort-order <value>` | No | Sort direction. Accepted values: `asc`, `desc`. |
@@ -280,7 +282,7 @@ Also available as `sim files mv`.
280282

281283
| Option | Required | Description |
282284
| --- | --- | --- |
283-
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line). |
285+
| `--file-ids <value...>` | Yes | File identifiers to update. (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
284286
| `--to <value>` | No | Destination folder path; omit for root. |
285287

286288
</CommandTable>

apps/docs/content/docs/en/cli/index.mdx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,17 @@ sim workflows list
7777
```
7878

7979
```
80-
ID NAME FOLDER DEPLOYED RUNS LAST RUN
81-
wf_7Yb2 Refund triage /Support yes 412 2026-08-15 14:02:11
82-
wf_9Kd4 Weekly digest /Reporting no 18 2026-08-11 09:00:04
80+
ID NAME FOLDER DEPLOYED RUNS LAST RUN
81+
3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 Refund triage /Support yes 412 2026-08-15 14:02:11
82+
b8c0d247-9e13-4a86-97f5-2ad4e1638c09 Weekly digest /Reporting no 18 2026-08-11 09:00:04
8383
```
8484
</Step>
8585

8686
<Step>
8787
### Run one
8888

8989
```bash
90-
sim workflows run wf_7Yb2 --input '{"ticketId":"T-4821"}'
90+
sim workflows run 3a9e21d8-5f47-4c0b-b2ea-91d7c6034ef8 --input '{"ticketId":"T-4821"}'
9191
```
9292

9393
A workflow must be deployed before it can be run. Deploy from the editor, or
@@ -106,8 +106,8 @@ sim <resource> [sub-resource] <verb> [arguments] [options]
106106

107107
```bash
108108
sim workflows list
109-
sim tables rows query tbl_123 --limit 50
110-
sim knowledge documents upload kb_123 ./handbook.pdf
109+
sim tables rows query tbl_9f3c1a05d4b7426e8c2f0917ab35de64 --limit 50
110+
sim knowledge documents upload 4c1b7f60-2d55-4a3e-9c18-70b6ea2f9d31 ./handbook.pdf
111111
```
112112

113113
Resource groups are plural, and each also accepts its singular spelling —
@@ -146,8 +146,10 @@ sim tables rows query --help
146146
| [`workflow-mcp-servers`](/cli/workflow-mcp-servers) | Publish workflows as MCP tools for outside agents |
147147
| [`meta`](/cli/meta) | Check what this API supports and which limits apply |
148148

149-
The [command reference](/cli/commands) documents every subcommand, argument, and
150-
flag, and is generated from the CLI itself.
149+
The [command overview](/cli/commands) has the global options and the commands
150+
that take no resource; the [complete reference](/cli/reference) documents every
151+
subcommand, argument, and flag on one page. Both are generated from the CLI
152+
itself.
151153

152154
## Where to go next
153155

@@ -156,4 +158,5 @@ flag, and is generated from the CLI itself.
156158
- [Output formats](/cli/output)`table`, `json`, `yaml`, and `text`, and when to use each
157159
- [Scripting](/cli/scripting) — piping, file inputs, exit codes, and automation recipes
158160
- [Troubleshooting](/cli/troubleshooting) — what each error means, and how to resolve it
159-
- [Command reference](/cli/commands) — every command, argument, and flag
161+
- [Command overview](/cli/commands) — global options, the command groups, and the commands that take no resource
162+
- [Complete reference](/cli/reference) — every command, argument, and flag on a single page

0 commit comments

Comments
 (0)