terse CLI has setup commands (terse install, terse update), one scaffold command (terse init), and a shared workflow CLI.
terse install is the recommended first step on a new machine. terse init scaffolds a new TypeScript project. The rest of the CLI auto-detects a Terse TypeScript project from the current directory and loads workflows from the default entry file unless you pass --entry-file.
The CLI scaffolds, generates helpers for, and runs TypeScript workflows on Node.js only.
terse test and terse deploy discover every workflow registered with createJob() in the entry file’s module graph, including files reached by side-effect imports. Keep workflow name values unique.
Getting started
terse install
One-command setup for a fresh machine.
- Offers to install the Terse CLI globally (
npm install -g terse-cli) ifterseis not already on yourPATH - Installs the official Terse agent skills (
terse-create,terse-improve,terse-self-host) into every coding agent detected on your machine via the skills CLI - If
~/.claudeexists, optionally (prompted, never silent) addsBash(terse:*)topermissions.allowin~/.claude/settings.jsonso Claude Code can run terse commands without prompting - Runs
terse auth loginif you are not already logged in
.claude-plugin/marketplace.json on main at runtime, so skill renames and additions propagate to older CLIs without a new CLI release. If the manifest is unreachable, the command fails with an error (there is no baked-in fallback list).
Options:
terse update
Updates the globally installed CLI and re-syncs the Terse agent skills.
terse is not installed globally, the command skips the CLI self-update and tells you to run terse install.
terse init [project-name]
Scaffolds a new TypeScript project and runs the full setup flow: install dependencies, authenticate, create a Terse project, review integrations, and generate helpers.
project-name, the CLI scaffolds into the current directory. If you run terse init in an existing npm project, the CLI tells you to use terse attach instead.
The flow, in order:
- Creates the target directory and scaffolds
package.json,tsconfig.json,src/terse.jobs.ts,src/jobs/sample-job.ts,.env.example, and.gitignore - Detects
pnpmornpmand installs dependencies - Runs
terse auth login - Creates a Terse project and writes
terse.config.json - Reviews your current integrations
- Runs
terse generate
terse attach
Links an existing repo to Terse with a self-hosted data plane (the Hybrid deployment).
terse attach when you already have an application repo and want the control plane to sync workflows to a data plane you operate instead of uploading source to be run on the Terse Cloud data plane.
What it does:
- Authenticates with the control plane, prompting you to pick which organization owns this project when your account belongs to more than one
- Prints the
TERSE_API_KEYfor the chosen organization so you can add it to your data plane environment - Creates a Terse project if the repo is not already linked
- Writes
terse.config.jsonwith the self-hosted data plane enabled - Reviews existing integrations and, in an interactive terminal, lets you connect more
- Runs
terse generatewhen the current directory matches a supported project layout
Before you run
terse deploy, set remoteServerUrl in terse.config.json to the URL where your Terse SDK server is running.
--entry-file with terse test and terse deploy.
terse test [job-name]
Fetches sample events and runs a local workflow against one selected event.
job-name.
terse test is the interactive picker and requires a terminal. In non-interactive contexts (CI, agents, scripts), use the terse test list / terse test show / terse test run subcommands below.
Options:
Loads your local workflow, lets you pick a real or synthesized sample event, and runs the handler in-process.
If your workflow uses a webhook trigger but no deliveries are stored yet, the CLI prints a
curl example against your webhook URL instead of opening an empty picker. Run terse deploy first so the CLI prints the URL, POST a test payload to it, then run terse test again.
terse test list [job-name]
Lists sample events for a workflow and assigns each one a content-addressed id.
--json, the output includes webhookEndpoints (URLs you can POST to) when your workflow has a webhook trigger, useful for scripting alongside stored samples.
Options:
Sample-event ids are deterministic content hashes, so the same payload keeps the same id across runs.
terse test show <id> [job-name]
Shows the contents of one sample event.
The CLI refetches the workflow’s current sample events and resolves the id against them. With
--json, the event is printed as a trigger event fixture: save it to a file, edit it, and replay it with terse test run --event-file.
terse test run [job-name]
Runs a workflow against an explicit sample event.
--id, --event, or --event-file.
Options:
Test runs read and write an isolated copy of the job’s state that persists across
terse test invocations, so dedupe and cooldown logic behaves the same way it will in production. Pass --fresh-state to reset that test state before the run, for example to re-test a filter that already fired. --fresh-state never touches the state of the deployed job, and is not yet supported for projects with a remote data plane.
Trigger event fixtures
--event and --event-file take a flat trigger event fixture: the same fields your handler reads, and nothing else. For a webhook trigger:
body, headers, method).
The prompt-facing text returned by event.formatForAgentRunner() and the log line returned by event.debugLog() are derived from the fixture exactly the way production derives them from a live event. To test a custom rendering, add a top-level formattedContent or debugLog string to the fixture and it overrides the derived value.
event.triggeredAt defaults to the time you run the test. Add a top-level triggeredAt ISO 8601 timestamp (for example "2026-07-01T09:00:00.000Z") to pin it, so date windows derived from it stay the same on every run.
terse test show <id> --json prints any stored sample event in this shape, so a recorded event is always a valid starting point for a hand-edited fixture.
terse deploy
Packages your local project and syncs workflows to Terse. Learn more about how deployment works.
What happens:
- New workflows are created on the platform
- Existing workflows are updated
- Workflows removed from your code are removed from Terse
- For each workflow that includes a webhook trigger, the CLI prints a Webhook URL line after deploy so you can wire up external systems without opening the app
- For managed projects (Terse Cloud data plane) with a local
.env, the CLI offers to upload any variables not already stored as project secrets before the deploy runs. Useterse secretsfor finer-grained control. - For durable workflows (
durable: true), Terse builds the workflow bundle on the server while preparing your source image. The deploy upload stays limited to your source code.
- Managed (Terse Cloud data plane): the CLI zips the current project directory, uploads it, and the control plane builds a source image (including the durable workflow bundle when needed) before running workflows on Terse Cloud sandboxes
- Self-hosted data plane: the CLI reads
remoteServerUrlfromterse.config.jsonand configures the control plane to call your own server instead of uploading source. On the first deploy with a self-hosted data plane, the CLI prints a newTERSE_API_KEYandTERSE_SIGNING_SECRET— save these into your data plane environment, they will not be shown again.
terse build
Builds the durable workflow bundle for every durable: true job into .terse/wf.
terse deploy — on managed deploys, Terse builds it on the server. Run terse build locally to inspect or debug a durable build error before deploying.
Build with workspace context
terse integrate
Interactive integration management from the terminal.
terse generate after any changes.
Connection types:
terse integrate list
Lists integrations and their connection status.
terse integrate describe <type>
Shows the current status, installation type, required fields, any setup URL, and a summary of the tools the integration exposes to jobs (name, description, read-only flag, approval support).
connect command. For a tool’s input/output schemas, use terse integrate tool <type> <tool-name>.
Options:
terse integrate tool <type> [tool-name]
With only an integration type, lists every tool that integration exposes to jobs, with descriptions. With a tool name, shows that tool’s full details: description, read-only flag, approval support, input schema, and output schema.
terse integrate tool run <tool>
Invokes a read-only toolbox tool ad hoc and prints its JSON result.
terse integrate connections <type>
Lists an integration’s connections with their IDs and which one this project pins.
--json, the output is a { id, name, pinned } object per connection.
Options:
terse integrate use <type> [connection-id]
Pins which connection this project generates against, then reruns terse generate so src/terse.generated.ts always matches the pin. Without a pin, generation uses the integration’s oldest connection.
connections map in terse.config.json and committed with the project, so every machine and CI generates against the same connection. terse integrate and terse integrate list show a pinned connection with a (pinned) marker; a pin whose connection no longer exists shows (pinned, missing) and fails terse generate with a re-pin hint.
In non-interactive terminals, omitting connection-id fails unless the integration has exactly one connection, which is auto-pinned. Use terse integrate connections <type> to find the id first.
Options:
terse integrate connect <type>
Connects or refreshes one integration without using the interactive picker.
Use
--fields-stdin for secrets so passwords and tokens do not end up in shell history.
For form-based integrations, connect submits the provided field values immediately. For OAuth integrations, connect opens the authorization URL in your default browser and exits 2 with a handoff payload ({ "handoff": "oauth", "url", "waitCommand" } in --json mode, or an ACTION REQUIRED line otherwise). Run the printed waitCommand (e.g. terse integrate wait slack) to block until authorization completes.
terse integrate disconnect <type>
Disconnects one integration.
terse integrate wait <type>
Polls until an OAuth integration finishes connecting.
terse integrate connect <type> for OAuth integrations such as Slack or GitHub.
Options:
The
list, describe, tool, tool run, connections, connect, disconnect, and wait subcommands do not rerun code generation for you. After a connection change, run terse generate. terse integrate use is the exception: it reruns generation as part of pinning.
terse secrets
Manages project secrets for projects using the Terse Cloud data plane. Secrets are stored on the control plane and injected into Terse Cloud sandbox runs as environment variables. Projects with a self-hosted data plane manage their own runtime env vars and these commands return an error there.
terse secrets list
Lists secret names (not values) stored on the linked Terse project.
terse secrets add <NAME>
Adds or updates one secret. Prompts for the value as a hidden input by default, or reads it from stdin so secrets don’t end up in shell history. Also writes the value to the project’s local .env file, so terse test runs use the same value that deployed runs will see.
terse secrets remove <NAME>
Removes one secret. Prompts for confirmation in a terminal; pass --yes in scripts. Also deletes the matching line from the project’s local .env file, so a job that would fail on the next deploy doesn’t keep passing locally.
terse secrets import <file>
Imports secrets from a .env-format file. Skips entries that already exist on the server unless you pass --overwrite.
terse generate
Fetches your active integrations, their workspace resources, and the current tool definitions from Terse, then writes generated helpers for your local project.
terse generate writes src/terse.generated.ts.
What the generated file contains:
toolbox.* calls a tool directly with no TerseAgent and is not limited by skills. agent.tools.* calls the same tool but is limited to skills the agent was created with. See the Triggers and Skills reference for every helper.
Each integration generates typed helpers from real workspace data such as repositories, Slack channels and members, lists, projects, and tool definitions, so your workflows reference actual resources instead of raw ids.
TypeScript projects also get Triggers.webhook from src/terse.generated.ts. Pass a type argument to Triggers.webhook.onRequest<YourBodyType>() so onTrigger and filter infer WebhookTrigger<YourBodyType> for event.body. See Webhook trigger.
Run terse generate again after you connect or disconnect an integration, after resources change inside a connected workspace, or after you upgrade terse-cli.
Improvements
Terse surfaces suggested code patches for your deployed agents based on past runs. These commands let you browse and apply them locally.terse list improvements
Lists pending improvements grouped by agent.
terse apply [improvement-id]
Downloads the suggested patch and runs git apply against your working tree, then marks the improvement as applied on the server. With no id, the CLI prompts you to pick from pending improvements. If a clean apply fails, the CLI tries git apply --3way; if that also fails, you can opt into git apply --reject to land hunks that do apply cleanly and write .rej files for the rest.
Memory
If a job uses the built-in memory skill, it keeps a persistent/memories directory that carries over between runs. These commands let you inspect and manage that memory from the terminal without opening the app.
Memory is isolated per project and per job. Every command resolves the job by --job <name>, or auto-selects when the project has only one workflow. Pass --test to target the isolated memory used by terse test runs instead of the production memory a deployed job writes to.
terse memory list
Lists the memory files for a job with their sizes.
terse memory get <path>
Reads one memory file. Prints to stdout by default, or writes to a local file with --out.
path is relative to the job’s memory root. The command exits with an error if the file does not exist.
Options:
terse memory put <path>
Creates or replaces a memory file. Reads the contents from --file, or from stdin when no file is given.
terse memory rm <path>
Deletes one memory file. Prompts for confirmation in a terminal; pass --yes in scripts.
State
If a job declares typed state, these commands let you inspect it from the terminal and reset the copy used byterse test runs.
Deployed runs and terse test runs each have their own state, so testing never pollutes production. Every command resolves the job by --job <name>, or auto-selects when the project has only one workflow. Commands default to the deployed state, which is read-only from the CLI; pass --test to target the test state. Test state persists across terse test invocations until you reset it.
terse state list
Lists the state keys for a job with their sizes.
terse state get <key>
Prints one state value as JSON. key is a key declared in the job’s states.
terse state rm <key>
Deletes one test state key. Deployed state is read-only, so --test is required. Prompts for confirmation in a terminal; pass --yes in scripts.
terse state reset
Deletes all test state for a job, the same reset terse test run --fresh-state performs before a run. Deployed state is read-only, so --test is required.
Observability
terse listen [job-name]
Streams live trigger events for a deployed workflow and executes the matching local workflow on your machine.
- Opens an authenticated SSE stream to the backend for your project/workflow
- Prints each forwarded event as it arrives
- Runs your current local workflow code against that serialized trigger
- You’re authenticated (
terse auth login) orTERSE_API_KEYis set - The workflow is deployed in this project (
terse deploy) terse.config.jsonexists with a validprojectId
Common errors:
- 401/403 Not authenticated: run
terse auth login - 404 Workflow not deployed: run
terse deployfirst
terse replay [run-id]
Fetches the stored trigger event for one past run and re-executes the matching local workflow on your machine with verbose output.
terse history [job-name]
Lists past runs for a deployed workflow or fetches the full chat history for a single run.
job-name, the CLI matches your local workflow name to the deployed workflow on Terse. When you pass --run-id, the CLI skips workflow lookup and fetches that run directly.
Options:
Use
--triggers when you want the serialized input event for each run (recommended when debugging with the terse-improve agent skill). Use --events when you want the full stored conversation, including the trigger payload.
terse dashboard
Opens the Terse web app in your default browser.
TERSE_FRONTEND_URL is set, the CLI opens that URL instead of production.
Authentication
terse auth login
Authenticates with Terse using a device authorization flow and saves your API key to a user-level config file.
terse auth logout
Removes saved API keys from your user config.
terse auth status
Shows the user and organization the saved API key belongs to.
terse auth org list
Lists organizations your account belongs to and marks the active one.
terse auth org switch [org-id]
Switches the active organization for this CLI session. Tokens for each organization you’ve used are cached locally, so re-switching is instant after the first time.
Help
terse docs
Opens the Terse documentation site in your default browser.
TERSE_DOCS_URL is set, the CLI opens that URL instead of the public docs.
terse completion install
Installs shell tab completion for the terse binary. Detects bash, zsh, or fish from your $SHELL.
terse completion uninstall
Removes the tab-completion entries written by terse completion install.
API key resolution
Commands that call the Terse API resolve credentials in this order:TERSE_API_KEYin the current process environment (including.envloaded from the project root)- The API key saved by
terse auth login
.env and you don’t need to set anything in the sandbox runtime.
After terse init or terse auth login, you usually don’t need a project .env file for the CLI either, because authentication is stored per user on your machine.
For a self-hosted data plane, your own server needs TERSE_API_KEY in its environment so it can authenticate with the control plane at runtime. terse attach prints the per-organization key, and terse deploy prints a project-scoped key and TERSE_SIGNING_SECRET the first time you deploy against a self-hosted data plane — copy these into your data plane secret store. terse secrets is for your own application secrets (database URLs, third-party API keys); it intentionally refuses any name that starts with TERSE_.
To point the CLI at a self-hosted control plane, set TERSE_BACKEND_URL (default https://api.useterse.ai) in your shell before running any terse command.