@sr-connect/cli
v0.1.4
Published
CLI for the ScriptRunner Connect
Keywords
Readme
@sr-connect/cli
A CLI to work with ScriptRunner Connect. 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.
Driving it from an agent? Install the companion skill, which teaches the product behind the commands. See For agents.
npx @sr-connect/cli # no install; lists the command groups
npm i -g @sr-connect/cli # then: sr-connect
npx @sr-connect/cli cli get-readme # prints this document from the installed copyNode 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. For daily use install it globally: npx spends most of a second resolving the package before the CLI starts, the installed sr-connect answers in a fraction of that, and a global install refers to itself as sr-connect in its own hints rather than telling you to run npx.
Getting started
Log in
npx @sr-connect/cli auth loginLogin asks three things. Which instance to talk to: EU, US, or the base URL of a dedicated instance — one running in your own AWS account and managed by the ScriptRunner Connect team. 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.
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.
Find your way around
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.
npx @sr-connect/cli # the groups
npx @sr-connect/cli script # the verbs in a group
npx @sr-connect/cli script get # runs it; anything missing is asked for
npx @sr-connect/cli script get --help # the flags, each ending in its conditionsA 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.
Twelve groups have a short alias, shown in each group's heading under Commands (el for event-listener, ac for api-connection, and so on). The alias covers every verb in the group.
Session defaults
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.
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.
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.
Work from a local copy
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.
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. The destructive verbs are the exception — the ones taking --yes read neither the record nor a session default unless a human is there to see the confirmation naming what is about to go, so a scripted script delete inside a clone still has to pass -w, -e and --team (their flags say session default on a TTY only, their --explain spells out what that leaves out, and the exit 2 names the rule). --no-local-workspace switches the behaviour off.
Commands that change something the clone contains also keep the clone current. See Local sync.
For agents
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.
There is a companion skill. sr-connect teaches an agent the product behind the commands: what a workspace, connector, event listener and scheduled trigger are, what the runtime can and cannot do, and how to drive this CLI to build any of it. Install it wherever your agent reads skills from. This document is orientation for the CLI, and --explain answers for a single verb; the skill is the part neither of them covers.
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--inputbody 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--explainis 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 (versionis--package-version,eventListenerTypeIdis--listener-type-id,waitForResponseis--wait).npx @sr-connect/cli event-listener update --explain # what it accepts, and when each thing is refused npx @sr-connect/cli log list-invocation-logs --explain # a verb with no body answers the same wayAdd
--rawby default, or setSR_CONNECT_CLI_RAW=1once. 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 exceptlog list-console-logs --expand-largeandlog list-http-logs --verbose. Two verbs are worth reading without it, and the reason is size rather than taste:log list-console-logsrenders 2 KB where the stored file is 5 KB of{values:[{type,value}]}wrappers, andlog list-http-logsrenders 160 bytes where the file is 1.3 KB. Both render what you would otherwise rebuild. Keep--raweverywhere 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.npx @sr-connect/cli script create --explain --raw > body.json # rules on stderr # edit body.json, then npx @sr-connect/cli script create -w <workspaceId> -e <environmentId> --input body.json --raw --agentSupply scope with flags or environment variables.
-w,-eand--team, orSR_CONNECT_CLI_WORKSPACE,SR_CONNECT_CLI_ENVIRONMENTandSR_CONNECT_CLI_TEAM. Aworkspace.jsonat or above the working directory also supplies all three, with a stderr note. Session defaults are read too, and announced.Credentials come from the environment.
SR_CONNECT_CLI_USERNAMEandSR_CONNECT_CLI_PASSWORDreplace a stored login entirely. They are not inputs toauth login; with them set, no login is needed.SR_CONNECT_CLI_INSTANCEnames the instance.Destructive verbs need
--yes. Without it, and without a terminal, they exit 2 having done nothing.workspace updatetakes one too, for its--teammove alone: an ordinary update never needs it, and a move without it is exit 2CONFIRMATION_REQUIRED. Four operations confirm only on a terminal and have no--yes:connector-sharing set,environment target-release,script replay-invocationandscript abort-invocation.--versionbelongs 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-versionand--package-version.A streamed run narrates, it does not record.
script trigger --stream-logsprints 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: readlog list-console-logswhen the order of two lines carries meaning.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.
Read this document with
cli get-readme. It needs no credentials. Under--rawit prints the markdown source. It is orientation — what a verb takes is--explainon the verb.Every request is recorded locally for troubleshooting, with secrets redacted.
cli list-api-logsreads the record back and needs no credentials.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--forceon 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.Push content, but rename and delete by ID.
local-workspace pushis the way to change the content of several scripts at once. For a rename or a deletion usescript update --nameandscript delete, which name the script by its ID, where a push has to infer a rename from content.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.Cache
app listfor the session. Every event listener, API connection and connector verb takes IDs from it, and the catalogue almost never changes.Create workspaces with
--language ts-strict. A workspace made from another workspace or a template inherits its language; check it and upgrade withworkspace update --language ts-strict.
Global switches
Accepted by every command. A flag beats its environment variable, and both beat a stored setting; off wins from any of the three.
| flag | env var | effect |
| ------------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --instance <instance> | SR_CONNECT_CLI_INSTANCE | eu, us, or a base URL. Overrides the stored instance |
| --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 |
| --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 |
| --no-prompts | SR_CONNECT_CLI_NO_PROMPTS=1 | Switches every prompt off without the declaration |
| --no-session | | Ignore this shell's session defaults |
| --no-local-workspace | SR_CONNECT_CLI_NO_LOCAL_WORKSPACE=1 | Do not take scope from a workspace.json at or above the working directory |
| --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 |
| --no-record-api-calls | SR_CONNECT_CLI_NO_RECORD_API_CALLS=1 | Do not record this run's requests |
| --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 |
| --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 |
| --no-local-sync | SR_CONNECT_CLI_NO_LOCAL_SYNC=1 | Do not update a local copy of the workspace a command changes |
| --no-update-check | SR_CONNECT_CLI_NO_UPDATE_CHECK=1 | Do not look NPM up for a newer version, and print no notice |
| --no-version-gate | SR_CONNECT_CLI_NO_VERSION_GATE=1 | Run even when the server declares this CLI version unsupported |
| --no-crash-reports | SR_CONNECT_CLI_NO_CRASH_REPORTS=1 | Do not write or offer a crash report |
| --no-agentic-feedback | SR_CONNECT_CLI_NO_AGENTIC_FEEDBACK=1 | feedback post and post-crash-report are refused unless a human is answering |
| -V, --version | | Print the CLI version |
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.
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.
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).
Where the CLI keeps its files:
| what | macOS and Linux | Windows | override |
| --------------------------------------------------------------------- | ---------------------------- | ---------------------------- | ------------------------------------------------------------------------------------ |
| The instance, and the credentials file with --insecure-storage | ~/.config/sr-connect/ | %APPDATA%\sr-connect\ | XDG_CONFIG_HOME replaces the root on any platform |
| 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 |
The API key itself is in the operating system's secure store unless --insecure-storage was used.
Instances
An instance is eu, us, or the base URL of a dedicated instance in your own AWS account, managed by the ScriptRunner Connect team (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.
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.
Output
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.
--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:
{ "error": { "code": "NOT_FOUND", "message": "…", "status": 404, "hint": "…" } }Exceptions, all listed here and noted again at the verb:
Nine verbs print a file rather than a document. Whether
--rawchanges anything is the column that matters, and byte-exact means exactly that: nothing added, nothing trimmed, so… > filereproduces what is stored. That is what a released environment's echo rule and a clone's checksums both depend on. The cost is the onecatpays: a file whose last byte is not a newline leaves the shell prompt mid-line.| verb | what
--rawselects | byte-exact | | ------------------------------------------------ | ------------------------------------------ | ------------------ | |log get-invocation-payload| the stored payload over the rendered one | yes, under--raw| |log list-console-logs| the stored file over the rendered console | yes, under--raw| |log list-http-logs| the stored file over the rendered table | yes, under--raw| |log get-large-log-message| the stored bytes (still JSON) | yes, under--raw| |readme get --content-only| the markdown source over the rendered form | yes, under--raw| |cli get-readme| the markdown source over the rendered form | yes, under--raw| |script get --content-only| nothing — same bytes either way | always | |event-listener-test-payload get --content-only| nothing — same bytes either way | always | |cli get-crash-report --content-only| nothing — same bytes either way | always |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-onlyprints markdown through the renderer, which is where the trailing newline you may notice comes from.A human answering prompts can produce several documents: paging through
log list-audit-logsorlog list-invocation-logs, and ascript createorupdatethat accepted the offer to run the script. Agents never reach either.Refused with
--raw:log list-console-logs --expand-largeandlog list-http-logs --verbose.--dry-runonconnector-sharing setandremoveprints an impact report as the document instead of the API's response.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-releaseis the one exception in both directions: it reads the environment first on the flags path too, so--headon 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.--explainprints 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.A group typed with no verb prints its help on stdout and exits 0, under
--rawas well:npx @sr-connect/cli team --rawlists 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--helpprints. An agent that reaches this has typed an incomplete command; a verb always answers a document.
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.
--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.
Exit codes
| code | meaning |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | Done. Also a save whose TypeScript diagnostics were reported, a push with nothing to send, and a list that came back empty |
| 1 | The API refused for a reason of its own, the run failed, a lock was lost, a push was partial |
| 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 |
| 3 | Not authenticated, or the credentials were rejected |
| 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 |
| 130 | Cancelled: Ctrl-C, or a confirmation declined |
The line between 1 and 2 is who refused, not how bad it was. Anything the CLI validates before
sending is exit 2 with a named code; every 400 from the API is exit 1 with BAD_REQUEST. So
script create --name "bad name!" is exit 2 INVALID_SCRIPT_NAME and a duplicate name is exit 1,
though both are your command to fix. A mistyped ID is exit 2 INVALID_ID for the same reason: the
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
Validated before sending. A 403 is exit 1 too: the API answers it
rather than saying whether the ID exists.
Two statuses are retried before you ever see them. A 429 (rate limited) and a 503 (the API
could not reach its database for that request) are resent up to three times, honouring
Retry-After, with one dim stderr line per wait naming which of the two it was. A 503 that
outlives all four attempts is exit 1 SERVICE_UNAVAILABLE, and its hint says what the status
guarantees: the request never ran and nothing changed, so the same command is safe to send again.
A 500 is different and is not retried — it is reported as a crash you can send with
feedback post-crash-report.
An empty list is exit 0, not 4. script list in an empty workspace prints {"scripts":[]} and
succeeds, and so does a log list-invocation-logs whose filters match nothing. Exit 4 is for a
single resource that is not there.
Validated before sending
A rule this document or --help states, the CLI checks itself, and a failure is exit 2 with a
named code and no request made. That covers script, connector, event queue, test payload,
environment and workspace names, the API connection path, an event listener's URL path, a
parameter key, a CRON expression, a release version and label, the eviction policy, an attachment
and its name, --page-size, and every enumerated flag (--type, --language, --visibility,
--execution-status, --order-by, --timestamps, …). Exclusive flag pairs and a missing
required value are the same: exit 2, before anything is sent.
Every ID is checked too. The API's IDs are ULIDs — 26 upper-case characters — and it refuses
a malformed one with a 400, so the CLI refuses it first: exit 2 INVALID_ID, naming where the
value came from. That covers a scope flag, its SR_CONNECT_CLI_* variable, a session default, a
clone's workspace.json, every positional that is an ID, --lock-id, and every …Id key of an
--input body. Surrounding whitespace is trimmed rather than refused, so an ID pasted with a
newline is the ID you meant. What it does not cover is whether the ID exists or is yours, which
is the API's 404 and 403, nor the opaque tokens the API hands back for paging — --cursor and
--next-token are the API's to define and are sent as given. Lower case is refused rather than
up-cased: the API stores IDs upper case, and quietly changing one would mean sending an ID you
never typed.
Three things are deliberately the API's, and they come back as exit 1:
- Uniqueness. Whether a name is already taken needs the list the API holds, and its refusal names the clash. A local check could only ever disagree with the authority.
- An email address's shape.
feedback post --emailis checked for a length and for having one@with something either side. Nothing finer: a guessed pattern is how a legal address gets refused, and"weird local part"@example.museumis legal. - An NPM package name. Same reason. Scopes and the registry's own history make the real rule
wider than any regex worth writing, so
package addsends the name and reportsInvalid package name format.if the API disagrees.
Everything a released environment refuses is the API's too, and on purpose: the CLI does not pre-check a flags-driven run against the environment's own state, so those are exit 1 with the message naming the field. See How commands work.
Workspace locks
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.
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.
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.
--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.
Local sync
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.
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.
API call recording
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.
Crash reports
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.
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.
Settings
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.
Concepts
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.
api-connection (ac)
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.
cli
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.
connector (con)
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.
connector-sharing (cs)
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.
environment (env)
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.
environment-parameter (ep)
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.
event-listener (el)
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.
event-listener-test-payload (tp)
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.
event-queue (eq)
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.
feedback
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.
local-workspace (lw)
A workspace as a directory on disk. See Work from a local copy for what a clone is for.
log
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.
package
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.
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.
readme
A workspace has one README, shared by every environment and addressed through the scope.
release
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.
scheduled-trigger (st)
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.
script
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 is for; renames and deletions are clearer through script update --name and script delete, which name the script by ID.
temp-remote-workspace (trw)
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.
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.
workspace
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.
workspace-lock (wl)
The editing lock the CLI takes on its own before every write; see 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.
How commands work
The rules below hold everywhere, so no verb repeats them.
- Scope flags are
-w, --workspace <workspaceId>,-e, --env <environmentId>and--team <teamId>. Each can come from a flag, itsSR_CONNECT_CLI_* variable, a clone'sworkspace.json, the session record or a picker, in that order. Thesession defaulttoken covers the clone too: a flag that reads the session record readsworkspace.jsonunder the same conditions, and a flag without the token (release create -e, a deploy target) reads neither. That includes filters:log list-invocation-logs -wrun 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'sworkspace.jsononly when a human is there to see the question naming them; their flags saysession 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. - 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>, butevent-listener create --script-id <scriptId>.--explainon the verb prints both, underArguments:andScope 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>andenvironment get|update|delete <environmentId>each take a session default and anSR_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 threeenvironmentverbs accept both and the positional wins;-ethere is markedignored when the positional argument is given. --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.- 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
--inputis exit 2, and so is an--inputbody with no keys. --explainprints what the verb takes and sends nothing: the example--inputbody on stdout where there is one and{}where there is not, and on stderr a sentence per parameter underRules:plus whatever the parameters do not cover underNotes:. 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'sworkspace.json. Two globals still apply:--rawpicks the shape, and--copy-output-to-filemirrors 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.--yesskips the confirmation a destructive verb asks for on a terminal; without a terminal it is required. Declining is exit 130.--file <path>accepts-for stdin wherever it appears.- 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.
- A local copy of the workspace is kept current after the writes listed in Local sync.
Commands
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:
npx @sr-connect/cli event-listener update --explainapi-connection (ac)
api-connection create— Create an API connection in a workspaceapi-connection list— List API connections in a workspaceapi-connection get— Get a single API connection as the environment sees itapi-connection update— Update an API connectionapi-connection delete— Delete an API connection from a workspace
app
app list— List apps with their connection types, API connection types, event listener types, event types and packagesapp get— Get a single app with every ID the other groups take
auth
auth login— Verify and store API credentials in the OS keychainauth status— Show credential source, instance, and verify against the APIauth logout— Remove stored credentials and every shell session's remembered scope
cli
cli get-readme— Print the CLI's own READMEcli check-updates— Check the NPM registry for a newer version of this CLIcli settings— View or change the CLI's own switchescli set-session— Remember team/workspace/environment as defaults for this shell sessioncli clear-session— Forget the session defaults for this shell sessioncli list-api-logs— List the API calls this CLI recordedcli clear-api-logs— Delete recorded API callscli list-crash-reports— List the crash reports this CLI storedcli get-crash-report— Read one stored crash reportcli clear-crash-reports— Delete stored crash reports
connector (con)
connector list— List connectorsconnector get— Get a single connectorconnector create— Create a connectorconnector update— Rename a connector, or replace a Generic connector's configurationconnector delete— Delete a connector
connector-sharing (cs)
connector-sharing list— List everyone a connector is shared withconnector-sharing get— Get a single user's permissions on a connectorconnector-sharing list-assignable-users— List the users a connector can still be shared withconnector-sharing set— Share a connector with a user, or change what they may do with itconnector-sharing remove— Remove a user's access to a connector
environment (env)
environment list— List environments in a workspaceenvironment create— Create an environment in a workspaceenvironment get— Get a single environment of a workspaceenvironment update— Rename an environmentenvironment delete— Delete an environment from the workspaceenvironment target-release— Change which release an environment runs, or move it back to HEAD
environment-parameter (ep)
environment-parameter list— List parameters in a workspace environmentenvironment-parameter create— Create an environment parameterenvironment-parameter update— Update an environment parameterenvironment-parameter delete— Delete an environment parameter
event-listener (el)
event-listener list— List event listeners in a workspaceevent-listener get— Get a single event listener as the environment sees itevent-listener create— Create an event listener in a workspaceevent-listener update— Update an event listenerevent-listener delete— Delete an event listener from a workspace
event-listener-test-payload (tp)
event-listener-test-payload list— List an event listener's test payloadsevent-listener-test-payload get— Get a single test payload with its contentevent-listener-test-payload create— Create a test payload for an event listenerevent-listener-test-payload update— Replace a test payload's content and/or rename itevent-listener-test-payload set-default— Make a test payload the one a manual trigger runsevent-listener-test-payload delete— Delete a test payload
event-queue (eq)
event-queue list— List event queues in a workspaceevent-queue get— Get a single event queueevent-queue create— Create an event queue in a workspaceevent-queue update— Update an event queueevent-queue delete— Delete an event queue from a workspace
feedback
feedback post— Post feedback, optionally with files attachedfeedback post-crash-report— Post a crash report this CLI stored earlier
local-workspace (lw)
local-workspace clone— Clone the workspace into a local directorylocal-workspace push— Push local scripts, a README and test payloads back to the workspace
log
log list-audit-logs— List audit logs (paginated)log list-invocation-logs— List script invocation logs (paginated, filterable)log get-invocation-payload— Fetch the payload (the incoming event) of a script invocationlog list-console-logs— List the console output (console.log/warn/error) of a script invocationlog list-http-logs— List the HTTP calls a script invocation madelog get-large-log-message— Fetch a single console log too large to be stored with the rest
package
package list— List the packages a workspace depends onpackage get— Get a single package of a workspacepackage add— Add an NPM package to a workspacepackage update— Change the version of a package on a workspacepackage remove— Remove a package from a workspacepackage list-npm-versions— List the versions the NPM registry publishes for a package
readme
readme get— Get the workspace's READMEreadme update— Update the workspace's README
release
release list— List releases of a workspacerelease create— Create a workspace release and optionally deploy it
scheduled-trigger (st)
scheduled-trigger create— Create a scheduled trigger in a workspacescheduled-trigger list— List scheduled triggers in a workspacescheduled-trigger get— Get a single scheduled trigger as the environment sees itscheduled-trigger update— Update a scheduled triggerscheduled-trigger delete— Delete a scheduled trigger from a workspace
script
script create— Create a script in a workspacescript update— Update a scriptscript list— List scripts in a workspacescript get— Get a single script as the environment sees itscript delete— Delete a script from a workspacescript trigger— Run a script in a workspacescript replay-invocation— Replay a past invocation through its own script, optionally with another payloadscript abort-invocation— Request that a queued or running invocation be aborted
team
team list— List teamsteam get— Get a single team
temp-remote-workspace (trw)
temp-remote-workspace create— Open a temporary remote workspace and retrieve its SFTP credentialstemp-remote-workspace delete— Dispose a temporary remote workspace, optionally releasing and deploying its work
template
template list— List published templates, most frequently used first
workspace
workspace list— List workspacesworkspace create— Create a workspace, optionally from another workspace or a published templateworkspace get— Get a single workspaceworkspace update— Update a workspaceworkspace delete— Delete a workspaceworkspace check-package-versions— Check the packages a workspace depends on against the versions NPM publishes
workspace-lock (wl)
workspace-lock check— Check who holds the workspace lock (exit 4 when nobody does)workspace-lock take— Take the workspace lock, or renew one this shell already holds; kept for the rest of the shell sessionworkspace-lock release— Release the workspace lock, rather than waiting for the lease to lapse
