CLI
Overview
The @monospace/cli package provides the monospace command. It signs in to a Monospace instance (a running Monospace deployment), generates TypeScript types for the SDK, and scaffolds and builds data connector extensions.
This page describes @monospace/cli 0.1.0.
| Command | Description |
|---|---|
monospace login | Store a credential for an instance |
monospace logout | Remove a stored credential |
monospace whoami | Show the targeted instance and workspace, and which credential answers for it |
monospace sdk init | Create a monospace.config.ts |
monospace sdk generate | Generate TypeScript types from a workspace schema |
monospace extension create | Scaffold a data connector project |
monospace extension build | Bundle an extension and optionally install it |
Install the CLI
Add the CLI to your project as a development dependency:
npm install -D @monospace/cli
pnpm add -D @monospace/cli
yarn add -D @monospace/cli
bun add -D @monospace/cli
Run it through your package manager or from a package.json script:
npx @monospace/cli whoami
pnpm exec monospace whoami
yarn run monospace whoami
bun run monospace whoami
The CLI requires Node.js 22.12 or later, the minimum its command parser supports.
npx monospace. monospace is an unrelated package on the npm registry, and npx fetches it when the CLI is not installed locally. Use npx @monospace/cli.Use Global Flags
Every command accepts these flags:
| Flag | Effect |
|---|---|
--json | Print one JSON result on stdout. See Read JSON Output |
--no-input | Never prompt. A question the command needs answered fails it with cli.confirmation_required, naming the flag that answers it. Questions with a default for unattended runs, such as the directory and display name in extension create, take that default |
-h, --help | Show help for the command, with examples |
-V, --version | Print the CLI version |
The CLI rejects unknown flags and extra arguments with cli.parse_failed.
Results print to stdout. Prompts, notes, warnings, and errors print to stderr, so you can redirect a command's result without losing its questions.
Prompts are also off when stdin or stderr is not a terminal, or when the CI environment variable is set to anything other than an empty value, 0, or false in any letter case.
Environment Variables
| Variable | Effect |
|---|---|
MONOSPACE_URL | Instance URL. See Resolve the Target |
MONOSPACE_WORKSPACE | Workspace name. See Resolve the Target |
MONOSPACE_API_KEY | API key for this invocation. See Resolve Credentials |
CI | Turns prompts off |
NO_COLOR | A non-empty value turns color off. TERM=dumb does the same |
FORCE_COLOR | Turns color on, unless set to 0 or false. NO_COLOR and TERM=dumb take precedence |
The CLI reads a .env file in the working directory before it runs. Variables already set in the environment take precedence over the file. An empty flag or variable counts as unset.
Resolve the Target
Commands that contact an instance resolve its URL and, for commands that take --workspace, the workspace. Each value comes from the first of:
- The
--urlor--workspaceflag - The
MONOSPACE_URLorMONOSPACE_WORKSPACEenvironment variable urlorworkspaceinmonospace.config.ts, ormonospace.config.js, in the working directory
The CLI looks for the config file in the working directory only, not in its parents. login, logout, and whoami skip the config file when flags and environment variables supply every value they need.
An instance URL is the base URL of the instance, such as https://example.monospace.io. It must use http or https, and cannot contain a user name, password, query, or fragment. The CLI does not follow redirects; a redirected request fails with cli.request_redirected.
A workspace name becomes one segment of a request path, so it cannot be . or .., or contain /, \, ?, #, whitespace, or control characters.
Requests to an instance time out after 10 seconds.
cli.config_carries_credential when it sets apiKey, api_key, password, secret, or token. Store credentials with monospace login or MONOSPACE_API_KEY instead.Resolve Credentials
Commands that call an instance use the first credential they find:
- The
--api-keyflag, on commands that accept it - The
MONOSPACE_API_KEYenvironment variable - The credential
monospace loginstored for the instance
Stored credentials live in the operating system's keyring under the service name monospace-cli, one per instance URL. Instances at different paths on the same host keep separate credentials.
A stored credential is either an API key or a session from an email and password sign-in. When the instance answers a session's request with 401 or 403, the CLI refreshes the session once, stores the new tokens, and retries. When the instance rejects the refresh, the CLI removes the stored session; sign in again. A refresh that fails for any other reason leaves the session stored.
A command that needs a credential and finds none fails with cli.not_authenticated. An API key must be visible ASCII characters with no spaces; otherwise the command fails with cli.api_key_invalid.
Where the keyring is unavailable, such as on a server without a secret service, commands that need it fail with cli.credential_store_unavailable. Set MONOSPACE_API_KEY instead.
Sign In
monospace login stores a credential for an instance:
npx @monospace/cli login --url https://example.monospace.io
pnpm exec monospace login --url https://example.monospace.io
yarn run monospace login --url https://example.monospace.io
bun run monospace login --url https://example.monospace.io
| Flag | Description |
|---|---|
--url <url> | Instance to sign in to |
--api-key-stdin | Read an API key from standard input instead of prompting |
The command resolves the instance from --url, MONOSPACE_URL, or the config file. Without one, it prompts for the URL.
It then asks whether to sign in with an API key or with an email and password:
- API key -- stores the key. The CLI does not contact the instance, so run
monospace whoamito check the key. - Email and password -- signs in and stores the resulting session. A rejected email or password fails with
cli.login_rejected.
Signing in again replaces the credential stored for that instance.
To sign in without a terminal, pipe an API key in:
echo "$KEY" | npx @monospace/cli login --url https://example.monospace.io --api-key-stdin
login warns when MONOSPACE_API_KEY is set, because that variable takes precedence over the credential it stores.
Sign Out
monospace logout removes the credential stored for the targeted instance:
npx @monospace/cli logout --url https://example.monospace.io
pnpm exec monospace logout --url https://example.monospace.io
yarn run monospace logout --url https://example.monospace.io
bun run monospace logout --url https://example.monospace.io
| Flag | Description |
|---|---|
--url <url> | Instance to sign out of |
For a session, the CLI first asks the instance to revoke it. When the instance does not confirm the revocation, the CLI removes the local credential anyway and warns that the refresh token stays valid until it expires.
For an API key, the CLI removes only the local copy. The key keeps working until you revoke it in the instance.
When nothing is stored for the instance, the command prints Nothing to remove. and succeeds.
Check the Target and Credential
monospace whoami shows which instance and workspace the working directory targets, where each value came from, and which credential the CLI uses:
npx @monospace/cli whoami
pnpm exec monospace whoami
yarn run monospace whoami
bun run monospace whoami
| Flag | Description |
|---|---|
--url <url> | Instance to target |
--workspace <name> | Workspace to target |
--api-key <key> | API key to use for this invocation |
--local | Report the target and credential without contacting the instance |
The command needs an instance URL; without one, it fails with cli.target_missing. The workspace is optional.
When a credential is found, the command reads the signed-in user from the instance and reports its email address, or its ID when it has no email address. A rejected API key fails the command with cli.credential_rejected. With --local, it makes no request.
Without a credential, or when the keyring is unavailable, the command still succeeds and reports what it found. In JSON output, authenticated means a credential was found and verified means the instance was asked about it.
Create an SDK Config
monospace sdk init writes a monospace.config.ts for type generation:
npx @monospace/cli sdk init
pnpm exec monospace sdk init
yarn run monospace sdk init
bun run monospace sdk init
| Flag | Description |
|---|---|
--url <url> | Instance URL to write into the config |
--workspace <name> | Workspace to write into the config |
--dir <dir> | Output directory for the generated types |
-c, --config <path> | Config file to create, instead of ./monospace.config.ts |
--yes | Replace an existing config file without asking |
The command prompts for each value you don't supply. The URL and workspace also resolve from MONOSPACE_URL and MONOSPACE_WORKSPACE. The output directory prompt suggests ./src/generated/monospace. Without a terminal, pass all three values:
npx @monospace/cli sdk init --url https://example.monospace.io --workspace blog --dir ./src/generated/monospace
pnpm exec monospace sdk init --url https://example.monospace.io --workspace blog --dir ./src/generated/monospace
yarn run monospace sdk init --url https://example.monospace.io --workspace blog --dir ./src/generated/monospace
bun run monospace sdk init --url https://example.monospace.io --workspace blog --dir ./src/generated/monospace
A config path ending in .ts, .mts, or .cts gets a TypeScript file. Any other path gets a JavaScript file typed with a JSDoc comment:
import type { MonospaceConfig } from '@monospace/sdk/config';
export default {
url: "https://example.monospace.io",
workspace: "blog",
output: "./src/generated/monospace",
} satisfies MonospaceConfig;
When the config file already exists, the command asks before replacing it; without a terminal, pass --yes. Declining leaves the file unchanged and ends the command with outcome cancelled. The new file keeps the existing URL and workspace unless flags or environment variables supply others, and drops every other option the old file set.
Generate SDK Types
monospace sdk generate reads a workspace schema and writes the generated client to index.ts in the config's output directory:
npx @monospace/cli sdk generate
pnpm exec monospace sdk generate
yarn run monospace sdk generate
bun run monospace sdk generate
| Flag | Description |
|---|---|
--url <url> | Instance to read the schema from, overriding the config |
--workspace <name> | Workspace to read the schema from, overriding the config |
--api-key <key> | API key to use for this invocation |
-c, --config <path> | Config file to use, instead of searching the working directory |
The command requires a config file; without one, it fails with sdk.config_not_found. The config needs output, plus either input, or an instance URL and workspace from the config, flags, or environment variables:
| Option | Type | Description |
|---|---|---|
output | string | Output directory for the generated types. Required |
url | string | Instance URL. --url and MONOSPACE_URL take precedence |
workspace | string | Workspace whose schema to read. --workspace and MONOSPACE_WORKSPACE take precedence |
input | string | Path to a local OpenAPI document in JSON |
outputOptions | object | Options for the generated output. See Config Reference |
Without input, the command reads /api/{workspace}/openapi with the resolved credential. The credential needs the openApiSchema:read entitlement; see OpenAPI Specification. A missing workspace fails the command with sdk.workspace_missing.
With input, the command reads the local file, needs no credential, and warns that any URL or workspace from flags or environment variables is ignored. input takes precedence when the config also sets url and workspace. The file must be a JSON document with an openapi field. Relative input and output paths resolve against the working directory.
The command overwrites index.ts on every run. It refuses to write when index.ts is a symbolic link.
Scaffold a Data Connector
monospace extension create creates a data connector extension project. It needs no instance and no sign-in. Run it with npx before the project exists:
npx @monospace/cli extension create ./acme-store --id acme/store --name "Acme Store"
| Argument or flag | Description |
|---|---|
[directory] | Directory to create the project in. It must not exist yet, or be empty apart from .git |
--id <id> | Extension id, as namespace/name. Each part is lowercase letters and digits, optionally joined by single hyphens |
--name <name> | Display name shown in Studio. Defaults to one derived from the id |
The command prompts for each value you don't supply. Without a directory, it asks for one. When prompts are off, it uses the working directory, which must then be empty apart from .git. A named directory that has files makes the command ask for another.
The project contains these files:
| File | Contents |
|---|---|
extension.config.json | The extension config, with one data connector entry. See Extension Config |
package.json | build and typecheck scripts, @monospace/extension-kit as a dependency, and @monospace/cli, rolldown, and typescript as development dependencies |
tsconfig.json | Strict TypeScript settings for src |
src/data-connectors/<namespace>/index.ts | The connector. It returns two sample items from every read and ignores the query |
src/data-connectors/<namespace>/schema.ts | The connector's schema: one collection, items, with id and name fields and only an unfiltered readMany operation |
README.md | Build and install instructions |
.gitignore | Ignores dist and node_modules |
The entry id is <namespace>/data-connector, where <namespace> is the first part of --id.
The command does not install dependencies. It prints the install and build commands for the package manager it detects, falling back to npm.
Build an Extension
monospace extension build reads extension.config.json from the working directory and writes the built extension to ./dist. Run it from the project root:
npm run build
pnpm build
yarn build
bun run build
| Flag | Description |
|---|---|
--out-dir <path> | Write the build here instead of ./dist |
--install-to <path> | Copy the build into this extension install directory |
--watch | Rebuild whenever a source file or extension.config.json changes, until you stop it with Ctrl-C |
The build requires rolldown ^1.2.0 installed in the project, and uses the project's copy. A scaffolded project already declares it; otherwise the build fails with extension.rolldown_missing or extension.rolldown_unsupported.
The output directory holds manifest.json, one <entry id>.js file per entry, and a .monospace-build marker that records which extension owns the directory. Each file bundles the entry's code and its dependencies into a single ES module with a default export. A bundle that still imports a module fails with extension.built_not_self_contained; Node.js built-ins such as node:fs are not available to extensions. A bundle without a default export fails with extension.built_without_default_export.
The build does not type-check your code. Run the project's typecheck script for that.
Without --watch, the build writes to a staging directory and swaps it in only when every entry has built, so a build that fails to bundle or validate leaves the previous output in place. With --install-to, the install happens before the swap.
The CLI replaces an output directory only when it is missing, empty, or already holds a build of the same extension. Otherwise the build fails with extension.artifact_dir_not_ours. The output and install directories cannot contain the project or its sources, or each other.
Install into an Instance
--install-to names an instance's extension install directory. The CLI copies the build into a subdirectory named after the extension id, with / replaced by __, so acme/store installs to acme__store:
npm run build -- --install-to ../monospace/extensions
pnpm build --install-to ../monospace/extensions
yarn build --install-to ../monospace/extensions
bun run build --install-to ../monospace/extensions
Each install replaces the previous one whole, so files you added to that subdirectory are deleted. The CLI refuses a subdirectory that holds a different extension, with extension.install_dir_not_ours.
An instance's install directory is set by MONOSPACE_EXTENSIONS__INSTALL_LOCATION and defaults to ./extensions, relative to the instance's working directory. In the Docker image, the default resolves to /monospace/extensions.
An instance loads extensions when it starts. With MONOSPACE_EXTENSIONS__AUTO_RELOAD=true, it also reloads an extension when an install replaces it.
Watch for Changes
--watch builds, then rebuilds whenever a source file or extension.config.json changes:
npm run build -- --watch --install-to ../monospace/extensions
pnpm build --watch --install-to ../monospace/extensions
yarn build --watch --install-to ../monospace/extensions
bun run build --watch --install-to ../monospace/extensions
Watch mode writes into the output directory in place. A failed rebuild prints a warning and keeps watching. With --install-to, the CLI installs only after a rebuild in which every entry built. The JSON result prints once, when watching stops, and its builds field counts every rebuild, failed ones included.
Stopping with Ctrl-C ends the command with outcome cancelled when the last rebuild succeeded, and fails it with that rebuild's error when it did not.
Extension Config
extension.config.json describes the extension and its entries. It is strict JSON: comments, trailing commas, and repeated keys are rejected. The build also rejects unknown fields.
| Field | Required | Description |
|---|---|---|
id | Yes | Extension id, as namespace/name |
version | Yes | Exact SemVer version, such as 0.1.0 |
name | Yes | Display name |
entries | Yes | At least one entry |
description, icon, iconColor | No | Optional metadata |
$schema | No | Editor schema location. Left out of the built manifest |
Each entry takes these fields:
| Field | Required | Description |
|---|---|---|
type | Yes | dataConnector |
id | Yes | Entry id, as namespace/name. Unique within the extension; it names the built file |
version | Yes | Exact SemVer version |
name | Yes | Display name |
engine.entrypoint | Yes | Path to the entry's source file, relative to the project directory |
engine.permissions | No | Network hosts the entry needs, each as { "permission": "net:<host>[:<port>]", "reason": "..." }. reason is required and shown to whoever approves the data source |
description, icon, iconColor | No | Optional metadata |
A net: permission names a host name, optionally with a *. subdomain wildcard, an IPv4 address or CIDR range, or a bracketed IPv6 address, each with an optional port. For example: net:api.example.com:443, net:*.example.com, net:10.0.0.0/8.
Read JSON Output
With --json, a command prints exactly one JSON object on stdout when it finishes, including when it fails:
{
"schema": "monospace.sdk.generate/1",
"outcome": "failed",
"effectState": "none",
"data": null,
"error": {
"category": "configuration",
"code": "sdk.config_not_found",
"message": "Config file not found. Searched: monospace.config.ts, monospace.config.js",
"fix": "Run `monospace sdk init` to create one."
}
}
| Field | Description |
|---|---|
schema | Versioned name of the result shape. monospace.error/1 when the CLI cannot attribute a failure to a command |
outcome | ok, failed, or cancelled |
effectState | Whether the command changed anything: complete, none, or unknown |
data | The command's result, or null |
error | null, or an object with category, code, message, and, when the CLI has a suggestion, fix |
Check outcome, not the exit code, to tell a completed command from a cancelled one.
| Command | schema | data fields |
|---|---|---|
login | monospace.login/1 | instance, kind (api-key or session), store, url |
logout | monospace.logout/1 | removed: null, or instance, kind, and revoked |
whoami | monospace.whoami/2 | instance, workspace, authenticated, credential, identity, store, verified |
sdk init | monospace.sdk.init/1 | path, url, workspace, dir |
sdk generate | monospace.sdk.generate/1 | output, source, bytes |
extension create | monospace.extension.create/1 | id, entryId, name, dir, files, agent, next |
extension build | monospace.extension.build/1 | artifactDir, entries, installedTo, builds |
--json does not turn prompts off; pass --no-input in scripts. --help and --version print to stderr under --json, so stdout stays empty.
Handle Exit Codes
| Exit code | Meaning |
|---|---|
0 | Outcome ok or cancelled |
1 | Outcome failed |
The first Ctrl-C, or SIGTERM, asks the running command to stop. The command ends with outcome cancelled, except a watch session whose last rebuild failed, which ends with outcome failed. Cancelling does not undo changes, so check effectState. A second Ctrl-C or SIGTERM stops the CLI immediately with a non-zero exit code.
Running monospace without a command prints help and exits 0.
See Also
- SDK Installation -- install the SDK and generate a typed client
- SDK Quickstart -- generate types and query your data end to end
- Permissions -- the entitlements a CLI credential needs
- Environment Variables -- configure the instance itself