@sr-connect/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,554 @@
1
+ # @sr-connect/cli
2
+
3
+ A CLI to work with [ScriptRunner Connect](https://scriptrunnerconnect.com). Run it with nothing but a verb and it asks for what it needs. Run it with flags and `--agent` and it never asks, which is what scripts and AI agents want.
4
+
5
+ ```sh
6
+ npx @sr-connect/cli # no install; lists the command groups
7
+ npm i -g @sr-connect/cli # then: sr-connect
8
+ npx @sr-connect/cli cli get-readme # prints this document from the installed copy
9
+ ```
10
+
11
+ Node 22 or newer. An older runtime gets one warning line per run and most commands still work, but live log streaming does not. Every example below spells the CLI as `npx @sr-connect/cli`, which is also how it refers to itself in its own hints. For daily use install it globally: `npx` spends most of a second resolving the package before the CLI starts, and the installed `sr-connect` answers in a fraction of that.
12
+
13
+ ## Getting started
14
+
15
+ ### Log in
16
+
17
+ ```sh
18
+ npx @sr-connect/cli auth login
19
+ ```
20
+
21
+ Login asks three things. Which instance to talk to: `EU`, `US`, or the base URL of a self-hosted server. Your account e-mail and an API key, which you generate in the web application under your profile's API Keys page; the prompt links to the right page for the EU and US instances. And whether agents may send feedback to the ScriptRunner Connect team on their own, which you can change later with `cli settings`.
22
+
23
+ The API key goes into the operating system's secure store: macOS Keychain, Windows Credential Manager, or Secret Service on Linux (GNOME Keyring, KWallet). On a machine without one, a headless Linux server for example, pass `--insecure-storage` to keep it in a file only you can read. `auth status` verifies the stored key against the instance and `auth logout` removes it.
24
+
25
+ ### Find your way around
26
+
27
+ Commands are arranged in groups of verbs: `script` is a group, `script get` is a verb in it. Typing a group with no verb, or the root with no group, prints what lives underneath. `--help` is only needed for a verb's flags.
28
+
29
+ ```sh
30
+ npx @sr-connect/cli # the groups
31
+ npx @sr-connect/cli script # the verbs in a group
32
+ npx @sr-connect/cli script get # runs it; anything missing is asked for
33
+ npx @sr-connect/cli script get --help # the flags, each ending in its conditions
34
+ ```
35
+
36
+ A verb run without its required values walks you through them. Team, then workspace, then environment, then the resource itself, each as a filterable list. A list with one entry is still shown, so you always see what is about to be touched. Every option in `--help` ends in a parenthesis of conditions: `required` or `optional`, whether it is asked for interactively, whether a session default or an environment variable can supply it, and anything it is exclusive with or refused alongside.
37
+
38
+ Twelve groups have a short alias, shown in each group's heading under [Commands](#commands) (`el` for `event-listener`, `ac` for `api-connection`, and so on). The alias covers every verb in the group.
39
+
40
+ ### Session defaults
41
+
42
+ After you have picked a team, a workspace and an environment, the CLI offers to remember them for the shell you are in. Say yes and the next command in that terminal asks `Use session defaults: team "…", workspace "…"?` instead of walking the lists again. Declining that question resolves for one run only. Declining the offer to remember is itself remembered, so it is not asked twice.
43
+
44
+ The record lives for 12 hours and belongs to one terminal window and one instance. `cli set-session` sets it deliberately and verifies what it is about to store, so a mistyped ID is refused rather than written and blamed on the next command; `cli clear-session` forgets it, `--no-session` ignores it for one run. Deleting a workspace or environment drops it from the record. Logging out clears every terminal's defaults, keeping a workspace lock still held so it can be given back.
45
+
46
+ A **workspace lock** rides the same record but is not a default, so clearing the scope keeps it: a clear that leaves one names the lock and the command that gives it back. Dropping the ID would strand the lock on the server, where it would refuse this shell's own later writes as another session.
47
+
48
+ ### Work from a local copy
49
+
50
+ `local-workspace clone` writes a workspace to disk as a TypeScript project: one file per script under `scripts/`, one module per API connection under `scripts/api/`, the parameter types in `ev-params.ts`, the README, the test payloads, and a `workspace.json` recording where it all came from. Open it in an editor, get type checking against the real API packages, commit it to git.
51
+
52
+ **The clone answers the scope questions.** Any command run at or below the directory takes its team, workspace and environment from `workspace.json`. On a terminal you are asked once, `Use the local workspace in ~/dev/x …?`. Without one the CLI prints a note and carries on, so this applies to agents as much as to people. A flag that names a different team or workspace wins, and the whole record is set aside for that run with a note, so the other two have to be passed as well. A flag naming a different environment of the same workspace keeps the record's team and workspace and replaces only the environment, which is how `-e <staging>` inside a clone of `Default` reads the other environment. `--no-local-workspace` switches the behaviour off.
53
+
54
+ Commands that change something the clone contains also keep the clone current. See [Local sync](#local-sync).
55
+
56
+ ## For agents
57
+
58
+ **Pass `--agent` on every call, or set `SR_CONNECT_CLI_AGENT=1` once.** That is the whole of it: `--agent` switches off every question on its own, so anything missing becomes exit 2 instead of a hang, and it tells the API that an agent is driving. You do not also need `--no-prompts`, which is the same prompt switch without the declaration and is there for scripts that are not agents. `--agent` does not change what stdout looks like — pair it with `--raw`, or set `SR_CONNECT_CLI_RAW=1` beside the other one.
59
+
60
+ - **Ask a verb what it takes before calling it — this is the fastest thing in the CLI, and it is where the per-verb documentation lives.** Every verb takes `--explain`: on stdout the example `--input` body where the verb has one and `{}` where it does not, and on stderr a sentence per parameter plus notes on everything the parameters do not cover — outcomes, exit codes, the shape of the document it answers with. Exit 0, no credentials, no scope, no request. This document does not repeat any of it, so `--explain` is the answer rather than a second opinion. Run it before a flags-only call too: a body key is a flag under another spelling, and the rule says which (`version` is `--package-version`, `eventListenerTypeId` is `--listener-type-id`, `waitForResponse` is `--wait`).
61
+
62
+ ```sh
63
+ npx @sr-connect/cli event-listener update --explain # what it accepts, and when each thing is refused
64
+ npx @sr-connect/cli log list-invocation-logs --explain # a verb with no body answers the same way
65
+ ```
66
+
67
+ - **Add** `--raw` **by default**, or set `SR_CONNECT_CLI_RAW=1` once. It gives one compact JSON document on stdout, exactly as the API sent it, and errors as a JSON envelope on stdout. Nothing refuses it except `log list-console-logs --expand-large` and `log list-http-logs --verbose`. Two verbs are worth reading **without** it, and the reason is size rather than taste: `log list-console-logs` renders 2 KB where the stored file is 5 KB of `{values:[{type,value}]}` wrappers, and `log list-http-logs` renders 160 bytes where the file is 1.3 KB. Both render what you would otherwise rebuild. Keep `--raw` everywhere else, the other two log listings included: their tables print the environment and the script by name, and the IDs are what the next call takes. See [Output](#output).
68
+
69
+ ```sh
70
+ npx @sr-connect/cli script create --explain --raw > body.json # rules on stderr
71
+ # edit body.json, then
72
+ npx @sr-connect/cli script create -w <workspaceId> -e <environmentId> --input body.json --raw --agent
73
+ ```
74
+
75
+ - **Supply scope with flags or environment variables.** `-w`, `-e` and `--team`, or `SR_CONNECT_CLI_WORKSPACE`, `SR_CONNECT_CLI_ENVIRONMENT` and `SR_CONNECT_CLI_TEAM`. A `workspace.json` at or above the working directory also supplies all three, with a stderr note. Session defaults are read too, and announced.
76
+ - **Credentials come from the environment.** `SR_CONNECT_CLI_USERNAME` and `SR_CONNECT_CLI_PASSWORD` replace a stored login entirely. They are not inputs to `auth login`; with them set, no login is needed. `SR_CONNECT_CLI_INSTANCE` names the instance.
77
+ - **Destructive verbs need** `--yes`**.** Without it, and without a terminal, they exit 2 having done nothing. `workspace update` takes one too, for its `--team` move alone: an ordinary update never needs it, and a move without it is exit 2 `CONFIRMATION_REQUIRED`. Four operations confirm only on a terminal and have no `--yes`: `connector-sharing set`, `environment target-release`, `script replay-invocation` and `script abort-invocation`.
78
+ - **`--version` belongs to the root.** Passed beside a verb it is exit 2 naming the flag the verb meant, because it used to print the CLI's version and exit 0 having sent nothing. The flags are `--release-version` and `--package-version`.
79
+ - **A streamed run narrates, it does not record.** `script trigger --stream-logs` prints rows as the frames arrive, and the order they are sent in is not guaranteed, so two lines can appear swapped. The stored file is ordered: read `log list-console-logs` when the order of two lines carries meaning.
80
+ - **Read the exit code.** 0 ok, 1 the API or the run failed, 2 usage, 3 not authenticated, 4 not found or an empty lookup, 130 cancelled. See [Exit codes](#exit-codes).
81
+ - **Read this document with** `cli get-readme`**.** It needs no credentials. Under `--raw` it prints the markdown source. It is orientation — what a verb takes is `--explain` on the verb.
82
+ - **Every request is recorded locally** for troubleshooting, with secrets redacted. `cli list-api-logs` reads the record back and needs no credentials.
83
+ - **Take the workspace lock before a series of edits** with `workspace-lock take`, even though every write takes one on its own. When somebody else holds it, never pass `--force` on your own: stop and ask the user whether to take it over. The one exception is a refusal that says the holder is **you** — "another session of yours, taken through the API" — where the hint says taking it back costs nobody anything, because it does. Trust the lapse time the CLI prints over any number in this document.
84
+ - **Push content, but rename and delete by ID.** `local-workspace push` is the way to change the content of several scripts at once. For a rename or a deletion use `script update --name` and `script delete`, which name the script by its ID, where a push has to infer a rename from content.
85
+ - **Give the lock back after a one-off write.** Every write takes the workspace lock and keeps it for the rest of a fifteen-minute lease, which is what you want for a series of edits and not for a single scripted change: until it lapses, a colleague's write is refused and names you. `workspace-lock release -w <workspaceId>` after the last write of a one-off run costs one request and saves them the wait.
86
+ - **Cache `app list` for the session.** Every event listener, API connection and connector verb takes IDs from it, and the catalogue almost never changes.
87
+ - **Create workspaces with `--language ts-strict`.** A workspace made from another workspace or a template inherits its language; check it and upgrade with `workspace update --language ts-strict`.
88
+
89
+ ## Global switches
90
+
91
+ Accepted by every command. A flag beats its environment variable, and both beat a stored setting; off wins from any of the three.
92
+
93
+ | flag | env var | effect |
94
+ | ------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `--instance <instance>` | `SR_CONNECT_CLI_INSTANCE` | `eu`, `us`, or a base URL. Overrides the stored instance |
96
+ | `--raw` | `SR_CONNECT_CLI_RAW=1` | Compact JSON on stdout. Not an agent switch: on a terminal, prompts still appear, on stderr. The only switch here that changes the shape of stdout, and the one `cli settings` does not report, so a stale export shows up as output looking wrong |
97
+ | `--agent` | `SR_CONNECT_CLI_AGENT=1` | Declares an agent is driving and switches every prompt off. An explicit `0`, `false`, `no`, `off` or empty value reads as off |
98
+ | `--no-prompts` | `SR_CONNECT_CLI_NO_PROMPTS=1` | Switches every prompt off without the declaration |
99
+ | `--no-session` | | Ignore this shell's session defaults |
100
+ | `--no-local-workspace` | `SR_CONNECT_CLI_NO_LOCAL_WORKSPACE=1` | Do not take scope from a `workspace.json` at or above the working directory |
101
+ | `--copy-output-to-file [file]` | | Append everything stdout gets to a file too. Default name `output-<unix-ts>.json`, or `.txt` without `--raw`. Put it after the command, or name the file: the value is optional, so a bare flag ahead of the command takes the group name as the file |
102
+ | `--no-record-api-calls` | `SR_CONNECT_CLI_NO_RECORD_API_CALLS=1` | Do not record this run's requests |
103
+ | `--no-lock` | `SR_CONNECT_CLI_NO_LOCK=1` | Write without taking or presenting a workspace lock. The API applies such a write whether or not somebody else holds the lock, so this is the one switch here that can overwrite an edit in progress |
104
+ | `--lock-id <lockId>` | `SR_CONNECT_CLI_LOCK_ID` | Present this lock on writes instead of the one the shell holds; never remembered by the shell |
105
+ | `--no-local-sync` | `SR_CONNECT_CLI_NO_LOCAL_SYNC=1` | Do not update a local copy of the workspace a command changes |
106
+ | `--no-update-check` | `SR_CONNECT_CLI_NO_UPDATE_CHECK=1` | Do not look NPM up for a newer version, and print no notice |
107
+ | `--no-version-gate` | `SR_CONNECT_CLI_NO_VERSION_GATE=1` | Run even when the server declares this CLI version unsupported |
108
+ | `--no-crash-reports` | `SR_CONNECT_CLI_NO_CRASH_REPORTS=1` | Do not write or offer a crash report |
109
+ | `--no-agentic-feedback` | `SR_CONNECT_CLI_NO_AGENTIC_FEEDBACK=1` | `feedback post` and `post-crash-report` are refused unless a human is answering |
110
+ | `-V, --version` | | Print the CLI version |
111
+
112
+ Five of these can be turned off for good with `cli settings`: recording, locking, local sync, crash reports and agentic feedback. The others are per run or per shell only.
113
+
114
+ One shape difference worth knowing: `cli settings`, `cli list-api-logs` and `cli list-crash-reports` answer with a bare JSON array, where every API-backed list answers with a named key (`{"teams":[…]}`). They are the CLI's own state rather than a response passed through, and the shape is kept for callers that already read it.
115
+
116
+ Other environment variables the CLI reads: `SR_CONNECT_CLI_USERNAME` and `SR_CONNECT_CLI_PASSWORD` (credentials), `SR_CONNECT_CLI_TEAM`, `SR_CONNECT_CLI_WORKSPACE` and `SR_CONNECT_CLI_ENVIRONMENT` (scope), `SR_CONNECT_CLI_BASIC_AUTH_PASSWORD` (the password for `connector create --basic-auth-username`), `SR_CONNECT_CLI_DISABLE_KEYCHAIN` (any value makes the keychain unavailable), `SR_CONNECT_CLI_STATE_HOME` (the root the state directory sits under, see below), `SR_CONNECT_CLI_SESSION_ID` (names the shell session when the terminal cannot), `SR_CONNECT_CLI_NPM_REGISTRY` (a mirror for the NPM lookups).
117
+
118
+ Where the CLI keeps its files:
119
+
120
+ | what | macOS and Linux | Windows | override |
121
+ | --------------------------------------------------------------------- | ---------------------------- | ---------------------------- | ------------------------------------------------------------------------------------ |
122
+ | The instance, and the credentials file with `--insecure-storage` | `~/.config/sr-connect/` | `%APPDATA%\sr-connect\` | `XDG_CONFIG_HOME` replaces the root on any platform |
123
+ | Session defaults, API-call recordings, crash reports, `settings.json` | `~/.local/state/sr-connect/` | `%LOCALAPPDATA%\sr-connect\` | `SR_CONNECT_CLI_STATE_HOME`, then `XDG_STATE_HOME`, replace the root on any platform |
124
+
125
+ The API key itself is in the operating system's secure store unless `--insecure-storage` was used.
126
+
127
+ ## Instances
128
+
129
+ An instance is `eu`, `us`, or the base URL of a self-hosted server (`https://api.example.com`; the scheme may be omitted, a path prefix is kept, credentials, query and fragment are refused). Resolution is `--instance`, then `SR_CONNECT_CLI_INSTANCE`, then what `auth login` stored in the config directory.
130
+
131
+ IDs are instance-scoped, so this shell keeps one session record **per instance**: defaults and workspace locks recorded against another instance are never reused, and never destroyed either — switching away and back finds them where they were. A `workspace.json` from another instance neither supplies scope nor accepts a push. A plain `http://` instance prints a warning that credentials travel unencrypted; loopback is exempt.
132
+
133
+ ## Output
134
+
135
+ **Human mode** is the default, even when stdout is a pipe. Lists are tables with the ID first, single resources a `LABEL value` detail view, mutations a green `✔ … created:` line followed by the detail. Prompts and spinners go to stderr, and so do notes and warnings. Three shapes, and which modes each prints in is worth knowing: a `⚠` warning prints in **both** modes, a `✔` status note only in human mode, and a **plain note with no glyph** in both. So `--raw` still tells you about anything the run carried on past, and stays quiet about what it merely did (the local sync's `✔ Local copy updated: …` among it). The glyphless ones say where a value came from rather than what happened — `Using session defaults: …` and `Using the local workspace in …` — and they print under `--raw` on purpose: the line naming the workspace a command was pointed at matters as much to an agent as to a person. Only the document goes to stdout.
136
+
137
+ `--raw` prints exactly one compact JSON document on stdout per run. API responses pass through untouched; no field is filtered or renamed. A response with no body becomes `{"updated":true,"id":…}` or `{"deleted":true,"id":…}`. Errors are one envelope on stdout:
138
+
139
+ ```json
140
+ { "error": { "code": "NOT_FOUND", "message": "…", "status": 404, "hint": "…" } }
141
+ ```
142
+
143
+ Exceptions, all listed here and noted again at the verb:
144
+
145
+ - **Nine verbs print a file rather than a document.** Whether `--raw` changes anything is the column that matters, and byte-exact means exactly that: nothing added, nothing trimmed, so `… > file` reproduces what is stored. That is what a released environment's echo rule and a clone's checksums both depend on. The cost is the one `cat` pays: a file whose last byte is not a newline leaves the shell prompt mid-line.
146
+
147
+ | verb | what `--raw` selects | byte-exact |
148
+ | ------------------------------------------------ | ------------------------------------------ | ------------------ |
149
+ | `log get-invocation-payload` | the stored payload over the rendered one | yes, under `--raw` |
150
+ | `log list-console-logs` | the stored file over the rendered console | yes, under `--raw` |
151
+ | `log list-http-logs` | the stored file over the rendered table | yes, under `--raw` |
152
+ | `log get-large-log-message` | the stored bytes (still JSON) | yes, under `--raw` |
153
+ | `readme get --content-only` | the markdown source over the rendered form | yes, under `--raw` |
154
+ | `cli get-readme` | the markdown source over the rendered form | yes, under `--raw` |
155
+ | `script get --content-only` | nothing — same bytes either way | always |
156
+ | `event-listener-test-payload get --content-only` | nothing — same bytes either way | always |
157
+ | `cli get-crash-report --content-only` | nothing — same bytes either way | always |
158
+
159
+ The last three take no envelope in either mode. The six above them render for a terminal without `--raw`, and a rendered form is not the stored one: `readme get --content-only` prints markdown through the renderer, which is where the trailing newline you may notice comes from.
160
+
161
+ - **A human answering prompts can produce several documents**: paging through `log list-audit-logs` or `log list-invocation-logs`, and a `script create` or `update` that accepted the offer to run the script. Agents never reach either.
162
+ - **Refused with** `--raw`: `log list-console-logs --expand-large` and `log list-http-logs --verbose`.
163
+ - `--dry-run` **on** `connector-sharing set` **and** `remove` prints an impact report as the document instead of the API's response.
164
+ - **An update whose answers change nothing prints nothing.** No request is made and a stderr note says so. This is the interactive updates. `environment target-release` is the one exception in both directions: it reads the environment first on the **flags** path too, so `--head` on an environment already running HEAD and a release ID naming the release already deployed both send nothing — and because that is the one state a scripted caller reaches, it answers a document rather than silence, `{"updated":false,"id":…,"reason":"already-current"}`, with the sentence in its place in human mode.
165
+ - `--explain` prints the example body pretty in human mode and compact under `--raw`, and `{}` in both where the verb sends no body; the key lines, rules and notes go to stderr either way.
166
+ - **A group typed with no verb prints its help on stdout and exits 0**, under `--raw` as well: `npx @sr-connect/cli team --raw` lists the verbs rather than answering a document. Nothing was resolved and nothing was asked for, so there is no response to shape — it is the same screen `--help` prints. An agent that reaches this has typed an incomplete command; a verb always answers a document.
167
+
168
+ Warnings are yellow stderr lines starting `⚠` and print in both modes. A `⚠` means the run carried on with something other than what you asked; `✖` means it failed.
169
+
170
+ `--copy-output-to-file` mirrors stdout to a file, ANSI stripped: the file gets the bytes stdout got, and a rendered document gets the newline that separates it from the next one. A verb whose document _is_ a file gets neither an added byte nor a separator, so the copy of a `--content-only` read is the stored bytes too. The file is opened before anything prints, so an unwritable path fails the run before any output. The six `log` verbs also offer the copy interactively before their first output. The file name is optional, and the flag takes the next word as the name unless it starts with `-`: `--copy-output-to-file team list` would write to a file called `team` and run nothing, so the CLI refuses a value that names a command group, exit 2 `OUTPUT_FILE_ERROR`. Put the flag after the command, or name the file.
171
+
172
+ ## Exit codes
173
+
174
+ | code | meaning |
175
+ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
+ | 0 | Done. Also a save whose TypeScript diagnostics were reported, a push with nothing to send, and a list that came back empty |
177
+ | 1 | The API refused for a reason of its own, the run failed, a lock was lost, a push was partial |
178
+ | 2 | Usage: a missing value nobody could be asked for, a bad flag, an exclusive pair, a name or body **this CLI** refused, a confirmation without `--yes`, an unsupported CLI version |
179
+ | 3 | Not authenticated, or the credentials were rejected |
180
+ | 4 | A single resource that does not exist. Also `workspace-lock check` when nobody holds the lock, and a write whose workspace is gone — the lock the CLI takes first answers 404 and the verb reports that rather than a lock failure |
181
+ | 130 | Cancelled: Ctrl-C, or a confirmation declined |
182
+
183
+ **The line between 1 and 2 is who refused, not how bad it was.** Anything the CLI validates before
184
+ sending is exit 2 with a named code; **every 400 from the API is exit 1** with `BAD_REQUEST`. So
185
+ `script create --name "bad name!"` is exit 2 `INVALID_SCRIPT_NAME` and a duplicate name is exit 1,
186
+ though both are your command to fix. A mistyped ID is exit 2 `INVALID_ID` for the same reason: the
187
+ shape is published, so the CLI owns it, while "no such workspace" is exit 4 and needs the API. Which rules the CLI checks itself is listed under
188
+ [Validated before sending](#validated-before-sending). A **403** is exit 1 too: the API answers it
189
+ rather than saying whether the ID exists.
190
+
191
+ **Two statuses are retried before you ever see them.** A `429` (rate limited) and a `503` (the API
192
+ could not reach its database for that request) are resent up to three times, honouring
193
+ `Retry-After`, with one dim stderr line per wait naming which of the two it was. A `503` that
194
+ outlives all four attempts is exit 1 `SERVICE_UNAVAILABLE`, and its hint says what the status
195
+ guarantees: the request never ran and nothing changed, so the same command is safe to send again.
196
+ A `500` is different and is not retried — it is reported as a crash you can send with
197
+ `feedback post-crash-report`.
198
+
199
+ **An empty list is exit 0**, not 4. `script list` in an empty workspace prints `{"scripts":[]}` and
200
+ succeeds, and so does a `log list-invocation-logs` whose filters match nothing. Exit 4 is for a
201
+ single resource that is not there.
202
+
203
+ ## Validated before sending
204
+
205
+ A rule this document or `--help` states, the CLI checks itself, and a failure is exit 2 with a
206
+ named code and no request made. That covers script, connector, event queue, test payload,
207
+ environment and workspace names, the API connection path, an event listener's URL path, a
208
+ parameter key, a CRON expression, a release version and label, the eviction policy, an attachment
209
+ and its name, `--page-size`, and every enumerated flag (`--type`, `--language`, `--visibility`,
210
+ `--execution-status`, `--order-by`, `--timestamps`, …). Exclusive flag pairs and a missing
211
+ required value are the same: exit 2, before anything is sent.
212
+
213
+ **Every ID is checked too.** The API's IDs are ULIDs — 26 upper-case characters — and it refuses
214
+ a malformed one with a 400, so the CLI refuses it first: exit 2 `INVALID_ID`, naming where the
215
+ value came from. That covers a scope flag, its `SR_CONNECT_CLI_`* variable, a session default, a
216
+ clone's `workspace.json`, every positional that is an ID, `--lock-id`, and every `…Id` key of an
217
+ `--input` body. Surrounding whitespace is trimmed rather than refused, so an ID pasted with a
218
+ newline is the ID you meant. What it does not cover is whether the ID _exists_ or is yours, which
219
+ is the API's 404 and 403, nor the opaque tokens the API hands back for paging — `--cursor` and
220
+ `--next-token` are the API's to define and are sent as given. Lower case is refused rather than
221
+ up-cased: the API stores IDs upper case, and quietly changing one would mean sending an ID you
222
+ never typed.
223
+
224
+ Three things are deliberately the API's, and they come back as exit 1:
225
+
226
+ - **Uniqueness.** Whether a name is already taken needs the list the API holds, and its refusal
227
+ names the clash. A local check could only ever disagree with the authority.
228
+ - **An email address's shape.** `feedback post --email` is checked for a length and for having
229
+ one `@` with something either side. Nothing finer: a guessed pattern is how a legal address gets
230
+ refused, and `"weird local part"@example.museum` is legal.
231
+ - **An NPM package name.** Same reason. Scopes and the registry's own history make the real rule
232
+ wider than any regex worth writing, so `package add` sends the name and reports
233
+ `Invalid package name format.` if the API disagrees.
234
+
235
+ Everything a **released environment** refuses is the API's too, and on purpose: the CLI does not
236
+ pre-check a flags-driven run against the environment's own state, so those are exit 1 with the
237
+ message naming the field. See [How commands work](#how-commands-work).
238
+
239
+ ## Workspace locks
240
+
241
+ A workspace has one editing lock, shared with the web application. Before every write the CLI takes it, or reuses the one this shell already holds, and renews it as writes continue. You see one extra spinner on a fresh workspace and nothing afterwards.
242
+
243
+ When someone else holds it, a terminal gets a question naming the holder and how they are editing, with three answers: cancel, take the lock, or continue without it. Taking it from a browser tab makes that tab lose edit control at once. Without a terminal the write stops with exit 1 `WORKSPACE_LOCKED` and a hint naming `workspace-lock take --force`. Two lock IDs from one account conflict exactly as two people do, so the refusal says which case it is: another session **of yours** through the API, your own browser tab, or somebody else. A lock that lapsed between two commands is taken again and the write re-sent once, with one dim line saying so. A lock taken for a write the API then refuses with a 4xx is given back in the same run, so a command that wrote nothing leaves nothing held; one the shell already had is kept, and so is a lock on a `409` the command retries with `--force`.
244
+
245
+ Only a 409 that is about the lock is recovered from. The API answers 409 for other things — a script an event listener still uses, an invocation that has already finished — and those reach the command as themselves, with their own code and hint.
246
+
247
+ `--lock-id` presents a lock taken elsewhere, for a pipeline step that cannot keep shell state; it is redacted from every recording and never written into the shell's record, so the next command in the shell presents its own lock again. A lock belongs to the account that took it, so an ID is not transferable between accounts: presenting another user's live lock is refused exactly like presenting one that holds nothing, on the write, the renewal and the release alike. Two pipeline steps sharing a lock ID have to share the API key too. `--no-lock` writes without one, and a write presenting no lock is **applied whether or not somebody else holds it** — measured, with another account's lock live: the same write is `WORKSPACE_LOCKED` with the lock the CLI would take for it, and a `204` with `--no-lock`. So the flag is not a way to work politely alongside somebody, it is a way to write straight past them; the lock stays where it was and its holder is never told. Reach for it in a pipeline step that cannot keep shell state, not to get around a conflict. A lock the shell remembers is recorded with the account that took it: a second account in the same terminal is told the recorded lock belongs to the first and takes its own, so the API reports the conflict rather than handing the lock over. `script trigger`, `replay-invocation` and `abort-invocation` never take a lock: they run a script rather than edit the workspace. `workspace delete` presents the lock like any other write and goes through on its own; it is refused only while somebody else holds one, with the holder named.
248
+
249
+ ## Local sync
250
+
251
+ When a command changes something a clone contains, and you are standing in that clone, the file on disk is updated to match: a script saved, renamed or deleted; a test payload written or moved; `ev-params.ts` regenerated after a parameter change; `scripts/api/**` and `package.json` regenerated after an API connection or package change; `README.md` after `readme update`. `workspace.json` records the checksum of every file it wrote, so the next push has nothing to re-send for them. Nothing is refetched, and the sync can never fail or slow the command it follows.
252
+
253
+ A local file that differs from what the clone last recorded is skipped with a warning rather than overwritten. When you edit a script, payload or README inline while its local copy has changed, the editor asks which version you mean. A clone that describes another workspace, environment or release gets one warning and is left alone. `environment target-release` and a deploy print a note that the clone needs re-cloning. `--no-local-sync` turns the sync off; it is a separate switch from `--no-local-workspace`, which governs reading scope from the clone rather than writing to it.
254
+
255
+ ## API call recording
256
+
257
+ Every request the CLI makes is appended to a per-shell log under the state directory, for troubleshooting. Request headers are never recorded; response headers are an allowlist; bodies are capped at 4 KB with anything named like a password, token, secret or lock ID replaced by `<redacted>`; a Generic connector's header values and a feedback attachment's bytes are blanked. Files older than 7 days are swept. `cli list-api-logs` reads the log back and `cli clear-api-logs` deletes it; neither needs credentials. `--no-record-api-calls` switches recording off.
258
+
259
+ ## Crash reports
260
+
261
+ When a run fails for a reason that is the CLI's or the server's, an unexpected error or a status of 500 or above, a markdown report is written under the state directory: version, command line with secrets removed, platform, the error and its stack, and this run's recorded API calls, capped at 512 KB. Usage errors, 4xx answers and cancellations never produce one.
262
+
263
+ On a terminal with stored credentials you are asked whether to send it, review it first, or keep it. Otherwise two yellow lines name the file and the `feedback post-crash-report --report <id>` command that sends it later. Reports are pruned after 7 days or past 20. `cli list-crash-reports`, `cli get-crash-report` and `cli clear-crash-reports` manage them locally. `--no-crash-reports` switches the whole thing off.
264
+
265
+ ## Settings
266
+
267
+ `cli settings` shows the five switches the CLI keeps for itself and, on a terminal, lets you change them for good. Only the offs are stored, in a file that survives `cli clear-session` and `auth logout`. A switch disabled by its environment variable cannot be toggled and is written as off. Under `--raw` the command only prints the table; it is the one place `--raw` suppresses a question, because the table is the answer a scripted run wants. All five are on until something turns them off: a flag for one run, an environment variable for a shell, or this command for good. Agentic feedback is on for a user who authenticates with `SR_CONNECT_CLI_USERNAME` and never ran `auth login`.
268
+
269
+ ## Concepts
270
+
271
+ What each resource is, and what it belongs to. Everything a _verb_ does is `--explain` on that verb; this is the part no per-verb answer can carry.
272
+
273
+ ### api-connection (ac)
274
+
275
+ An API connection is what a script imports as `./api/<path>` to talk to an app. Its path and its vendor API package belong to the workspace; the connector attached to it is environment-specific configuration. `create` is workspace-scoped, the other four are environment-scoped.
276
+
277
+ ### cli
278
+
279
+ The CLI's own state. Every verb but `set-session` works without credentials or an instance, which is what you want when authentication is the thing that broke.
280
+
281
+ ### connector (con)
282
+
283
+ A connector is an authorized account that an event listener or an API connection uses to reach an app. It belongs to a user and is looked up through a team, so this group's scope stops at `--team`. Connectors shared with you appear beside your own. `list` and `get` report each connector's `connectionType`, which is the app's own `connectionType` in `app list`; the API connection type a connector was created for is never reported back.
284
+
285
+ ### connector-sharing (cs)
286
+
287
+ Lets the owner grant other users `use` and `edit` permission on a connector. `edit` allows renaming it and replacing its credentials, never deleting it. `use` allows attaching it to event listeners and API connections, in every team you share with that user or in named teams only. Every verb is owner-only; the connector picker offers only connectors you own. The user is the positional argument; the connector is `--connector-id`.
288
+
289
+ ### environment (env)
290
+
291
+ Environments belong to a workspace and each runs either the workspace's current state (HEAD) or a release (non-HEAD). The environment is this group's resource, so `get`, `update` and `delete` take it as a positional argument; `-e` names the same value.
292
+
293
+ ### environment-parameter (ep)
294
+
295
+ Typed configuration values an environment holds, arranged in a tree of `FOLDER` parameters that is one level deep: only a `FOLDER` can be a parent, and a `FOLDER` cannot be given one. Types: `TEXT`, `PASSWORD`, `NUMBER`, `BOOLEAN`, `DATE`, `MULTILINE_TEXT`, `SINGLE_CHOICE`, `MULTIPLE_CHOICES`, `LIST`, `MAP`, `FOLDER`. There is no `get`; `list` is the only read. The type of a parameter cannot be changed. A parameter's default value is what it starts with in a new environment and in a copy of the workspace, both seeded from the default environment alone; the value itself is not carried over to either, and a default value is not read at runtime.
296
+
297
+ ### event-listener (el)
298
+
299
+ An event listener runs a script in response to an external event: an app's event type is bound to a script, optionally through a connector and an event queue. Its URL path, connector and enabled state are environment-specific configuration; the event type, the script, the queue and its grouping are shared by every environment and captured by a release.
300
+
301
+ ### event-listener-test-payload (tp)
302
+
303
+ A test payload is a sample event that a manual trigger of an event listener runs. The payloads are the same in every environment; which one is the default is user-specific and environment-specific configuration. Every verb takes `--event-listener-id <eventListenerId>` beside the scope flags.
304
+
305
+ ### event-queue (eq)
306
+
307
+ An event queue holds an event listener's incoming events and hands them to the script one at a time. A queue belongs to the whole workspace; only its enabled state is environment-specific configuration. Queues are gated per team: where a team cannot use them every verb here is refused, and `team get` reports `features.eventQueues`.
308
+
309
+ ### feedback
310
+
311
+ Feedback to the ScriptRunner Connect team. Both verbs are refused when the agentic-feedback switch is off and nobody can be asked. Nothing reads feedback back; the returned ID is the only handle.
312
+
313
+ ### local-workspace (lw)
314
+
315
+ A workspace as a directory on disk. See [Work from a local copy](#work-from-a-local-copy) for what a clone is for.
316
+
317
+ ### log
318
+
319
+ Audit logs and invocation logs are team-scoped, and so is an invocation's payload: `log get-invocation-payload` takes `--team` and no `-w`, its route being a team one. Console logs, HTTP logs and large messages belong to a single invocation and need its workspace; `--team` there only narrows the interactive browse list. The workspace resolves through the ordinary chain, so a `SR_CONNECT_CLI_WORKSPACE`, a clone's `workspace.json` or a session default supplies it. An invocation belongs to one workspace, so where the value was inherited rather than passed with `-w`, a not-found answer carries a `⚠` naming where it came from. Every verb here offers, on a terminal, to copy its output to a file before printing. Nothing here writes anything.
320
+
321
+ ### package
322
+
323
+ The NPM packages a workspace depends on. Workspace-scoped, no environment anywhere: a non-HEAD environment keeps the dependencies its release captured. The version is the only writable field; changing a name or type means removing and adding. A change here recompiles nothing until a script is next saved, and a note says so.
324
+
325
+ When a package is not written in TypeScript, add its companion `@types/<name>` package as well, where one exists, so the compiler has types for it.
326
+
327
+ ### readme
328
+
329
+ A workspace has one README, shared by every environment and addressed through the scope.
330
+
331
+ ### release
332
+
333
+ An immutable snapshot of a workspace, optionally deployed into environments. Releases cannot be edited or deleted; which release an environment runs is `environment target-release`.
334
+
335
+ ### scheduled-trigger (st)
336
+
337
+ Runs a script on a schedule. The schedule and the enabled state are environment-specific configuration and can be changed in a non-HEAD environment too; the script belongs to the trigger and is shared by every environment.
338
+
339
+ ### script
340
+
341
+ A script is one TypeScript file in a workspace, read through an environment. In a non-HEAD environment `list` and `get` answer what the release captured. Saving a script compiles and bundles the whole workspace, so an import that cannot be resolved fails the save, while TypeScript diagnostics are reported and do not block it. Changing the content of several scripts at once is what [local-workspace push](#local-workspace-push) is for; renames and deletions are clearer through `script update --name` and `script delete`, which name the script by ID.
342
+
343
+ ### temp-remote-workspace (trw)
344
+
345
+ A temporary copy of a workspace's current setup in a private SFTP space, reachable only with the returned credentials, for editing with any tool that speaks SFTP. The workspace is untouched until the copy is deleted.
346
+
347
+ Two switches gate it, and they fail differently. The **team's** is `features.remoteWorkspace` on `team get`; false there answers 403 with a hint naming the field. The **workspace's** own is `remoteWorkspaceEnabled` on `workspace get`; false there answers exit 1 `BAD_REQUEST` with `SFTP server is not enabled for the workspace: <id>.` and a hint. **Nothing in this API turns the workspace switch on** — not `workspace create`, not `workspace update` — so a workspace the web application has not enabled it for cannot use either verb here. Check both before you plan around them.
348
+
349
+ ### workspace
350
+
351
+ The unit everything else lives in. Team-scoped; the workspace is the resource, so `get`, `update` and `delete` take it as a positional argument and there is no `-w` here.
352
+
353
+ ### workspace-lock (wl)
354
+
355
+ The editing lock the CLI takes on its own before every write; see [Workspace locks](#workspace-locks). These verbs are for looking at it, holding it across commands that cannot keep shell state, and giving it back early. A lock is not a permission: a write presenting none is applied, whoever holds the lock. What the lock protects against is a _second session claiming to be the holder_ — which is what every write the CLI makes does, and why one gets `WORKSPACE_LOCKED` while the same write under `--no-lock` goes through.
356
+
357
+ ## How commands work
358
+
359
+ The rules below hold everywhere, so no verb repeats them.
360
+
361
+ - **Scope flags** are `-w, --workspace <workspaceId>`, `-e, --env <environmentId>` and `--team <teamId>`. Each can come from a flag, its `SR_CONNECT_CLI_`* variable, a clone's `workspace.json`, the session record or a picker, in that order. The `session default` token covers the clone too: a flag that reads the session record reads `workspace.json` under the same conditions, and a flag without the token (`release create -e`, a deploy target) reads neither. That includes filters: `log list-invocation-logs -w` run inside a clone is narrowed to that workspace, with a stderr note. Non-interactively, a missing required one is exit 2. Destructive verbs, the ones that take `--yes`, read the session record and a clone's `workspace.json` only when a human is there to see the question naming them; their flags say `session default on a TTY only`, and a scripted delete has to name its scope with flags or environment variables. Every workspace-scoped verb accepts `--team`, including the ones where it only narrows the list a workspace is chosen from.
362
+ - **Positional arguments** such as `<scriptId>` are picked interactively when omitted; non-interactively they are required. **The resource a verb acts on is its positional argument, and any other resource it needs is a flag** — `script get <scriptId>`, but `event-listener create --script-id <scriptId>`. `--explain` on the verb prints both, under `Arguments:` and `Scope flags:`. A positional argument that _is_ its group's scope resource reads the scope sources the matching flag would, and its description says so: `team get <teamId>`, `workspace get|update|delete <workspaceId>` and `environment get|update|delete <environmentId>` each take a session default and an `SR_CONNECT_CLI_`* variable. Every other positional argument takes neither, `environment target-release <releaseId>` included, the positional there being a release rather than the environment. Where the group's resource is also a scope flag, the three `environment` verbs accept both and the positional wins; `-e` there is marked `ignored when the positional argument is given`.
363
+ - `--input <file>` reads the whole request body as JSON from a file, or `-` for stdin, and supersedes every body flag. Local validation still applies to what it carries.
364
+ - **An update needs something to change.** Interactively, answers that keep every value send nothing and say so. Non-interactively, an update with no body flag and no `--input` is exit 2, and so is an `--input` body with no keys.
365
+ - `--explain` prints what the verb takes and sends nothing: the example `--input` body on stdout where there is one and `{}` where there is not, and on stderr a sentence per parameter under `Rules:` plus whatever the parameters do not cover under `Notes:`. It exits 0 without credentials, scope or a request, and every body flag, scope flag and positional is ignored, along with the scope environment variables, the session record and a clone's `workspace.json`. Two globals still apply: `--raw` picks the shape, and `--copy-output-to-file` mirrors the document the way it mirrors any other. An option the verb does not have is still a parse error, not an ignored flag. The body shows every key the verb accepts, keys that exclude each other included, so where a rule names such a pair drop one before sending; `--explain --raw > body.json`, edited, is then a file the verb accepts back through `--input`.
366
+ - `--yes` skips the confirmation a destructive verb asks for on a terminal; without a terminal it is required. Declining is exit 130.
367
+ - `--file <path>` accepts `-` for stdin wherever it appears.
368
+ - **HEAD and non-HEAD environments.** An environment either runs the workspace's current state (HEAD) or a release (non-HEAD). A non-HEAD environment is a snapshot: reads there answer from it, and writes to fields the release owns are refused. Interactive runs check first and ask only about what can change; flag-driven runs are refused by the API with a message naming the field.
369
+ - **A local copy** of the workspace is kept current after the writes listed in [Local sync](#local-sync).
370
+
371
+ ## Commands
372
+
373
+ One line per verb. What a verb takes — every parameter, its format, when it is refused — is `--explain` on the verb itself, which prints it without credentials, scope or a request:
374
+
375
+ ```sh
376
+ npx @sr-connect/cli event-listener update --explain
377
+ ```
378
+
379
+ <!-- commands:start -->
380
+
381
+ ### api-connection (ac)
382
+
383
+ - `api-connection create` — Create an API connection in a workspace
384
+ - `api-connection list` — List API connections in a workspace
385
+ - `api-connection get` — Get a single API connection as the environment sees it
386
+ - `api-connection update` — Update an API connection
387
+ - `api-connection delete` — Delete an API connection from a workspace
388
+
389
+ ### app
390
+
391
+ - `app list` — List apps with their connection types, API connection types, event listener types, event types and packages
392
+ - `app get` — Get a single app with every ID the other groups take
393
+
394
+ ### auth
395
+
396
+ - `auth login` — Verify and store API credentials in the OS keychain
397
+ - `auth status` — Show credential source, instance, and verify against the API
398
+ - `auth logout` — Remove stored credentials and every shell session's remembered scope
399
+
400
+ ### cli
401
+
402
+ - `cli get-readme` — Print the CLI's own README
403
+ - `cli settings` — View or change the CLI's own switches
404
+ - `cli set-session` — Remember team/workspace/environment as defaults for this shell session
405
+ - `cli clear-session` — Forget the session defaults for this shell session
406
+ - `cli list-api-logs` — List the API calls this CLI recorded
407
+ - `cli clear-api-logs` — Delete recorded API calls
408
+ - `cli list-crash-reports` — List the crash reports this CLI stored
409
+ - `cli get-crash-report` — Read one stored crash report
410
+ - `cli clear-crash-reports` — Delete stored crash reports
411
+
412
+ ### connector (con)
413
+
414
+ - `connector list` — List connectors
415
+ - `connector get` — Get a single connector
416
+ - `connector create` — Create a connector
417
+ - `connector update` — Rename a connector, or replace a Generic connector's configuration
418
+ - `connector delete` — Delete a connector
419
+
420
+ ### connector-sharing (cs)
421
+
422
+ - `connector-sharing list` — List everyone a connector is shared with
423
+ - `connector-sharing get` — Get a single user's permissions on a connector
424
+ - `connector-sharing list-assignable-users` — List the users a connector can still be shared with
425
+ - `connector-sharing set` — Share a connector with a user, or change what they may do with it
426
+ - `connector-sharing remove` — Remove a user's access to a connector
427
+
428
+ ### environment (env)
429
+
430
+ - `environment list` — List environments in a workspace
431
+ - `environment create` — Create an environment in a workspace
432
+ - `environment get` — Get a single environment of a workspace
433
+ - `environment update` — Rename an environment
434
+ - `environment delete` — Delete an environment from the workspace
435
+ - `environment target-release` — Change which release an environment runs, or move it back to HEAD
436
+
437
+ ### environment-parameter (ep)
438
+
439
+ - `environment-parameter list` — List parameters in a workspace environment
440
+ - `environment-parameter create` — Create an environment parameter
441
+ - `environment-parameter update` — Update an environment parameter
442
+ - `environment-parameter delete` — Delete an environment parameter
443
+
444
+ ### event-listener (el)
445
+
446
+ - `event-listener list` — List event listeners in a workspace
447
+ - `event-listener get` — Get a single event listener as the environment sees it
448
+ - `event-listener create` — Create an event listener in a workspace
449
+ - `event-listener update` — Update an event listener
450
+ - `event-listener delete` — Delete an event listener from a workspace
451
+
452
+ ### event-listener-test-payload (tp)
453
+
454
+ - `event-listener-test-payload list` — List an event listener's test payloads
455
+ - `event-listener-test-payload get` — Get a single test payload with its content
456
+ - `event-listener-test-payload create` — Create a test payload for an event listener
457
+ - `event-listener-test-payload update` — Replace a test payload's content and/or rename it
458
+ - `event-listener-test-payload set-default` — Make a test payload the one a manual trigger runs
459
+ - `event-listener-test-payload delete` — Delete a test payload
460
+
461
+ ### event-queue (eq)
462
+
463
+ - `event-queue list` — List event queues in a workspace
464
+ - `event-queue get` — Get a single event queue
465
+ - `event-queue create` — Create an event queue in a workspace
466
+ - `event-queue update` — Update an event queue
467
+ - `event-queue delete` — Delete an event queue from a workspace
468
+
469
+ ### feedback
470
+
471
+ - `feedback post` — Post feedback, optionally with files attached
472
+ - `feedback post-crash-report` — Post a crash report this CLI stored earlier
473
+
474
+ ### local-workspace (lw)
475
+
476
+ - `local-workspace clone` — Clone the workspace into a local directory
477
+ - `local-workspace push` — Push local scripts, a README and test payloads back to the workspace
478
+
479
+ ### log
480
+
481
+ - `log list-audit-logs` — List audit logs (paginated)
482
+ - `log list-invocation-logs` — List script invocation logs (paginated, filterable)
483
+ - `log get-invocation-payload` — Fetch the payload (the incoming event) of a script invocation
484
+ - `log list-console-logs` — List the console output (console.log/warn/error) of a script invocation
485
+ - `log list-http-logs` — List the HTTP calls a script invocation made
486
+ - `log get-large-log-message` — Fetch a single console log too large to be stored with the rest
487
+
488
+ ### package
489
+
490
+ - `package list` — List the packages a workspace depends on
491
+ - `package get` — Get a single package of a workspace
492
+ - `package add` — Add an NPM package to a workspace
493
+ - `package update` — Change the version of a package on a workspace
494
+ - `package remove` — Remove a package from a workspace
495
+ - `package list-npm-versions` — List the versions the NPM registry publishes for a package
496
+
497
+ ### readme
498
+
499
+ - `readme get` — Get the workspace's README
500
+ - `readme update` — Update the workspace's README
501
+
502
+ ### release
503
+
504
+ - `release list` — List releases of a workspace
505
+ - `release create` — Create a workspace release and optionally deploy it
506
+
507
+ ### scheduled-trigger (st)
508
+
509
+ - `scheduled-trigger create` — Create a scheduled trigger in a workspace
510
+ - `scheduled-trigger list` — List scheduled triggers in a workspace
511
+ - `scheduled-trigger get` — Get a single scheduled trigger as the environment sees it
512
+ - `scheduled-trigger update` — Update a scheduled trigger
513
+ - `scheduled-trigger delete` — Delete a scheduled trigger from a workspace
514
+
515
+ ### script
516
+
517
+ - `script create` — Create a script in a workspace
518
+ - `script update` — Update a script
519
+ - `script list` — List scripts in a workspace
520
+ - `script get` — Get a single script as the environment sees it
521
+ - `script delete` — Delete a script from a workspace
522
+ - `script trigger` — Run a script in a workspace
523
+ - `script replay-invocation` — Replay a past invocation through its own script, optionally with another payload
524
+ - `script abort-invocation` — Request that a queued or running invocation be aborted
525
+
526
+ ### team
527
+
528
+ - `team list` — List teams
529
+ - `team get` — Get a single team
530
+
531
+ ### temp-remote-workspace (trw)
532
+
533
+ - `temp-remote-workspace create` — Open a temporary remote workspace and retrieve its SFTP credentials
534
+ - `temp-remote-workspace delete` — Dispose a temporary remote workspace, optionally releasing and deploying its work
535
+
536
+ ### template
537
+
538
+ - `template list` — List published templates, most frequently used first
539
+
540
+ ### workspace
541
+
542
+ - `workspace list` — List workspaces
543
+ - `workspace create` — Create a workspace, optionally from another workspace or a published template
544
+ - `workspace get` — Get a single workspace
545
+ - `workspace update` — Update a workspace
546
+ - `workspace delete` — Delete a workspace
547
+
548
+ ### workspace-lock (wl)
549
+
550
+ - `workspace-lock check` — Check who holds the workspace lock (exit 4 when nobody does)
551
+ - `workspace-lock take` — Take the workspace lock, or renew one this shell already holds; kept for the rest of the shell session
552
+ - `workspace-lock release` — Release the workspace lock, rather than waiting for the lease to lapse
553
+
554
+ <!-- commands:end -->