shiftlabs Start free
Menu CLI reference

CLI reference

The shiftlabs command deploys workflows and agents from your machine or CI, and follows their runs. It needs Node 22.18 or later and an API key.

Install

Install the shiftlabs package in your project, or run it with npx. The package also gives import { workflow } from "shiftlabs" its types.

shellterminal
npm install --save-dev shiftlabs
export SHIFTLABS_API_KEY=slk_…   # developer: deploy and run; viewer: read only
npx shiftlabs whoami
  • Node 22.18 or later. Bun runs it too.
  • SHIFTLABS_API_KEY is an API key. init needs none. The CLI reads the key only from the environment.
  • --api-url, or SHIFTLABS_API_URL, sets another API origin. The default is https://api.shiftlabs.org.
  • Every command takes --json to print JSON. A failure prints a JSON error instead. See Errors.
  • The CLI never asks a question or waits for input, so it runs the same in CI and from an AI coding tool.

Commands

shiftlabs --help lists the same commands and options.

Commands
CommandWhat it does
initLists the agent templates. Writes nothing.
init <dir>Writes AGENTS.md and CLAUDE.md for coding agents into dir (. for the current directory), and lists the agent templates. See Coding agents.
init --template <name> [dir]Writes a template's shiftlabs.config.ts and workflows into dir, by default the current directory, with AGENTS.md and CLAUDE.md. See Templates.
deployDeploys every workflow and agent in shiftlabs.config.ts as one release. See Projects.
deploy <file>...Deploys workflows from .ts, .js or .py files, each current at once, with the files and packages they import. The file name is the workflow name.
releasesLists recent releases, newest first.
rollback <release>Makes an earlier release's versions and schedules current again, as a new release.
run <workflow>Starts a run of the current version now.
runsLists recent runs, newest first.
logs <run-id>Prints a run's log lines.
whoamiShows the workspace, the key's role and the plan.
typesWrites the workspace's secret and var names, never values, its agent names and each workflow's payload type as editor types. See Editor types.
Options
OptionFor
--config <file>deploy: the project file. Default shiftlabs.config.ts, then .mts, .js and .mjs.
--out <dir>types: where to write. Default: the folder of shiftlabs.config.ts, else the current directory.
--template <name>init: the template to start from.
--name <workflow>deploy: the workflow name, for a single file.
--payload <json>run: the run's JSON payload. Default {}.
--workflow <name>runs: only this workflow.
--status <status>runs: queued, running, sleeping, completed or failed.
--limit <n>runs, logs, releases: how many. Default 20 runs, 500 lines, 20 releases.
--jsonPrints JSON instead of text, errors included.
--api-url <url>Default $SHIFTLABS_API_URL, else https://api.shiftlabs.org.
-h, --help · -v, --versionHelp and the version.
  • deploy <file> deploys .ts and .js files as TypeScript and .py files as Python. Underscores in a file name become hyphens: nightly_report.py deploys as nightly-report.
  • A deploy with new Python packages or npm packages waits for them to build, up to 10 minutes.
  • deploy sends the files a workflow imports with it. See Files.
  • run sends one Idempotency-Key, and on a network error, a 30-second timeout or a 5xx tries again with it, 3 attempts at most. A retry never starts a second run.
  • Developer keys deploy, release, roll back and run. Viewer keys read, and can run types.

Errors

With --json, a failed command prints one JSON object on stdout. Without it, the message goes to stderr. The exit code is the same either way, and tells the kind of failure.

jsonstdout
{
  "error": {
    "code": "limit",
    "message": "POST /v1/workflows/refund-check/runs -> 429: quota exceeded",
    "hint": "Try again in 3600 seconds.",
    "status": 429,
    "response": { "error": "quota exceeded", "limit": "runsPerDay", "max": 1000 }
  }
}
  • code and message are always there. hint says what to do next, when there is something to say.
  • status and response are there when the API answered: its HTTP status and its JSON body.
Error codes
CodeExitMeans
usage2A command, option or argument is wrong.
config3Something on your side needs fixing: the project file, a workflow file, the API URL, or a CLI too old for the API.
auth4SHIFTLABS_API_KEY is not set, or the API did not accept it.
forbidden5The key's role or scope does not allow this, or the workspace is suspended.
not_found6The workflow, run, release or other item does not exist.
conflict7Something is in the way. init does not overwrite files.
invalid8The API refused the request, such as code that does not build.
limit9A plan limit or a rate limit. The hint says when to try again, if the API said.
network10The API could not be reached, or did not answer in time.
server11The API failed. Try again shortly.
internal1A bug in the CLI.

Projects

shiftlabs deploy with no files reads shiftlabs.config.ts. It lists the project's workflows, agents and schedules, and deploys them as one release.

shiftlabs.config.ts
import { agent, file, project, schedule, workflowTool } from "shiftlabs";

export default project({
  workflows: {
    "lookup-invoice": "./workflows/lookup-invoice.ts",
    "nightly-report": "./workflows/nightly_report.py",
  },
  agents: {
    billing: agent({
      title: "Billing assistant",
      instructions: file("./agents/billing.md"),
      model: "@cf/zai-org/glm-4.7-flash",
      tools: [workflowTool("lookup-invoice", { description: "Look up an invoice by its id." })],
    }),
  },
  schedules: {
    nightly: schedule("nightly-report", { cron: "0 3 * * *", timezone: "America/Detroit" }),
    "monday-summary": schedule({ agent: "billing", message: "Summarize last week's refunds.", cron: "0 9 * * 1" }),
  },
});
  1. Stage

    Each workflow is deployed with ?stage=1: built and stored, not yet current.

  2. Upload code

    Each agent with code uploads that file. Every code tool in it joins the agent.

  3. Release

    One POST /v1/releases makes the workflow versions, agent definitions and schedules current together. It prints the release number and what changed.

  • If a step fails, nothing that runs changes. Staged versions and uploads that no release names are dropped after a day.
  • With nothing changed, deploy prints Nothing to release: every workflow, agent and schedule is already current. and records no release. CI can deploy on every push.
  • Paths are relative to the config file. The config must export default project({...}). If it imports shiftlabs, install the package first.
  • An agent's code is the path of its tools file, whose default export is tools({...}). The agent's tools takes workflowTool(...) only. See Code tools.
  • An agent's instructions: file("./x.md") sends the file's text, up to 8,000 characters.
  • A Standard JSON Schema in a workflow tool's parameters, such as a zod 4.2+ schema, is sent as its JSON Schema.
  • A key that deploys agents needs the agent.deploy capability, which developers hold.
  • schedules replaces every schedule in the workspace, including ones edited in the dashboard. schedules: {} removes them all; a project without schedules leaves them as they are. See Schedules.

Files and packages

A workflow's file can import other files and, in TypeScript, npm packages. deploy sends them with it, as one deploy of several files. A workflow that imports nothing of its own is sent as its text, as before.

  • TypeScript: deploy follows each import, export … from, import() and require() with a relative path, such as ./money.ts, ../lib/money or ./rates.json, through the files they import. It sends .ts, .mts, .js, .mjs and .json files.
  • The files must sit in the folder of the nearest package.json, looking up from the workflow's folder, or below it. Without a package.json, they sit in the workflow's folder or below it.
  • When the code imports a package, deploy sends that package.json and the part of its package-lock.json the imports reach. The platform installs the packages the workflow imports at the versions the lockfile pins, and deploy waits while a new set installs. Node.js built-ins such as buffer, and imports of types only, need neither file. See Files and npm packages.
  • Without a package-lock.json, deploy warns and sends the files without package.json, so no package installs, and the deploy fails if the code needs one. With a pnpm-lock.yaml, bun.lock or yarn.lock, run npm install --package-lock-only to write a package-lock.json beside it.
  • An import of ./money.js that means money.ts stops the deploy. Import it as ./money.ts or ./money.
  • Python: deploy sends the modules the workflow imports from its folder and below, such as import helpers or from lib.util import double.

Editor types

shiftlabs types reads the workspace's secrets, vars, agents and workflows through the API and writes their names, never values, and each workflow's payload type to two files next to shiftlabs.config.ts. Your editor then completes secrets. and vars., and flags a misspelled name before you deploy. In an app, shiftlabs/client type-checks agent names and payloads with the same file. Run it again after adding or removing a secret or var, or deploying an agent or a workflow.

shellterminal
npx shiftlabs types
# Wrote shiftlabs-env.d.ts and shiftlabs_types.pyi: 1 secret, 2 vars, 1 agent and 3 workflows.
npx tsc --noEmit
# workflows/refund.ts(4,30): error TS2551: Property 'STRIPE_KYE' does not exist on type 'Readonly<Secrets>'. Did you mean 'STRIPE_KEY'?
  • shiftlabs-env.d.ts

    TypeScript. It fills the Secrets and Vars interfaces of "shiftlabs", and AgentNames and WorkflowPayloads of "shiftlabs/client". Your tsconfig.json must include it; the default include covers the whole folder.

  • shiftlabs_types.pyi

    Python. It takes effect with the shiftlabs Python package. The package reads Secrets and Vars from this file. Each name is an attribute, and ctx.secrets["NAME"] takes the names too. Annotate the context, as in async def refund_check(ctx: Context), so the editor knows what ctx is. The file also lists the agent and workflow names as AgentName and WorkflowName, which shiftlabs.client takes.

  • secrets lists only secrets with hosts, since only they have a stand-in. The command names the others. reveal() and sign() take any secret's name.
  • A var set only for some workflows is optional: string | undefined in TypeScript, str | None in Python.
  • In TypeScript, until the workspace has a secret with hosts, any secrets.NAME type-checks, and the same for vars. In Python, the file for an empty workspace has no names, so the editor flags every one.
  • A workflow's payload type comes from its payload schema. A workflow without one takes any payload. The built-in helper isn't among the agents.
  • --out <dir> writes to another folder, such as an app's that uses the client. types overwrites only files it wrote.
  • Each part needs a capability: secrets secret.view, vars var.view, workflow names and payload types workflow.view, agent names agent.view. A viewer key has all four. Without workflow.view or agent.view, that part is left empty, so any name type-checks there, and types says which capability the key lacks.
  • With --json it prints {"files", "secrets", "vars", "workflowVars", "withoutHosts", "agents", "workflows", "lacking"}. lacking lists the capabilities the key missed.

Versions

The shiftlabs package has the same version as the SDK the platform runs, so its types match how your code runs. The CLI and the API check each other's version.

  • Each request names the CLI's version in its User-Agent, such as shiftlabs-cli/0.3.0.
  • When the API runs a newer version, the command prints one line to stderr with that version and the command to update. It still runs.
  • The API refuses a CLI older than the oldest version it accepts. The command fails with 426 {"error": "cli too old"}, and the answer's hint says what to install. See Versioning.
  • A release made with shiftlabs deploy or shiftlabs rollback records the CLI's version as cliVersion. The dashboard's Releases screen shows it.
  • The package's CHANGELOG.md lists what each version changed. Before 1.0, a version can rename or reshape what earlier ones had.

Coding agents

shiftlabs init --template <name> and shiftlabs init <dir> write an AGENTS.md that coding agents such as Codex, Cursor and Claude Code read before they change the project. For a project you already have, run npx shiftlabs init . in it. The file covers the project's layout, the commands, and the rules that most often trip people up: code outside a step runs again, secrets are stand-ins, code tools have no network, and a deploy is one release. It links to these docs and their llms.txt. CLAUDE.md holds one line, @AGENTS.md, so Claude Code reads the same file.

  • init never overwrites a file. If AGENTS.md or CLAUDE.md exists, it leaves it as it was and says so. Add the line @AGENTS.md to your own CLAUDE.md.
  • If the template's shiftlabs.config.ts or a workflow file exists, init --template writes nothing.