Extensions

CLI

Reference for the monospace extension create and monospace extension build commands, their flags, output and exit codes.
Preview
Extensions are in preview and can change in breaking ways between releases. Use them in non-production environments only. See Stability. Written for Monospace 1.0.0, @monospace/cli 0.2 and @monospace/extension-kit 0.3.

Overview

The @monospace/cli package provides the monospace binary. Two commands work with extensions:

CommandDoes
monospace extension create [directory]Scaffolds an extension project from a template. The only template today scaffolds a data connector extension with one entry
monospace extension buildBuilds the project into a manifest and one JavaScript file per entry, and optionally installs it

Neither command needs a login or a running instance (a Monospace deployment). Both run locally against your files. The CLI needs Node.js 22.12 or later.

Add the CLI to an extension project as a dev dependency:

npm install -D @monospace/cli

A project that monospace extension create writes already lists it. To create that project, run the CLI without installing it; see Create an Extension.

Use Global Flags

These flags work with every command.

FlagEffect
--jsonPrint one JSON result on stdout. See Read JSON Output
--no-inputNever prompt. A question that would have been asked fails the command, unless it has an answer for unattended runs (e.g., create's directory)
-h, --helpShow help for the command
-V, --versionShow the CLI version

Without --json, results print to stdout, and prompts, warnings and errors go to stderr. With --json, stdout carries only the JSON result.

--json doesn't turn prompts off, so pass --no-input in scripts. Prompts are also off when stdin or stderr isn't a terminal, or when the CI environment variable is set.

The CLI reads a .env file in the working directory. Variables already set in your environment take precedence over it, and a CI entry there also turns prompts off.

Create an Extension

monospace extension create writes a new extension project. Its template contains one data connector entry, and it's the only template today.

The project doesn't exist yet, so run the CLI without installing it. Each package manager fetches @monospace/cli and runs its monospace binary:

npx @monospace/cli@0.2 extension create ./artic --id example/artic --name "Art Institute of Chicago"
Argument or flagDescription
directoryDirectory to create the project in. It must not exist, or be empty apart from .git. When it has other files, the command asks for another directory
--id <id>Extension id, as namespace/name. Prompted for when omitted. See Id Format
--name <name>Display name. Defaults to a name derived from the id (example/artic gives Example Artic). When omitted, the command asks for it with that name filled in; without prompts, it uses that name. Must contain a visible character and no control characters

Without directory, the command asks for one first, and offers the working directory when it's empty. Without prompts, it uses the working directory, which must then be empty. A directory you type at the prompt as ~, or starting with ~/, expands to your home directory.

With a new or empty directory, --id and --name, the command asks nothing. Without prompts, a missing --id, or a directory with other files, fails the command with cli.confirmation_required.

Scaffolded Files

The data connector template writes these files and nothing else:

FileContents
extension.config.jsonThe extension config, version 0.1.0, with an editor $schema and one data connector entry. The entry has no icon, no engine.permissions and no engine.capabilities
package.jsonA private ES module package named after the id (example/artic becomes example-artic). Its build script runs monospace extension build, and its typecheck script runs tsc --noEmit. It depends on @monospace/extension-kit, with @monospace/cli, rolldown and typescript as dev dependencies
tsconfig.jsonstrict, noEmit and verbatimModuleSyntax, with an ES2023 target and bundler module resolution, covering src
src/data-connectors/<namespace>/index.tsThe data connector entry's entrypoint. setup returns introspect, a query that answers every read with two fixed items (single data objects), and a testConnection that answers { ok: true }
src/data-connectors/<namespace>/schema.tsThe connector's schema: one collection, items, with the string fields id and name, a primary index on id, and readMany with output: fields.all()
README.mdBuild, type-check and install instructions, including the install location and automatic reloading settings
.gitignoreIgnores dist and node_modules

The scaffold typechecks and builds as written. Installed and added as a data source, it serves the two items from its items collection.

The scaffold declares only readMany, with no filter, sort or paging, and its query ignores the request. Declare more only together with the code that answers it.

The template's entry id is <namespace>/data-connector, where <namespace> is the first segment of --id. --id example/artic produces the entry example/data-connector. Two projects with the same namespace get the same entry id, so rename the entry before you install both. The Data Connector Quickstart renames it to example/artic-connector.

The command doesn't install dependencies. It prints what it created, the extension id, entry id, directory and file list, then the next two commands:

output
Created the Art Institute of Chicago extension

Extension  example/artic
Entry      example/data-connector
Directory  /home/you/artic
  - .gitignore
  - README.md
  - extension.config.json
  - package.json
  - src/data-connectors/example/index.ts
  - src/data-connectors/example/schema.ts
  - tsconfig.json

Next steps
  1. cd ./artic && npm i
  2. npm run build

The first step is written for a POSIX shell (cd … && …, with POSIX quoting), not for cmd.exe or PowerShell. The install and build commands match your package manager. The CLI looks in the working directory and its parents for a lockfile, or a packageManager or devEngines.packageManager field in package.json. Without one, it uses the package manager that launched it, and falls 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
FlagDescription
--out-dir <path>Write the build here instead of ./dist
--install-to <path>After a successful build, copy it into this instance install directory
--watchRebuild whenever a source file or extension.config.json changes. Stop with Ctrl-C

The build requires rolldown ^1.2.0 in the project's dependencies. The CLI uses the project's copy, not its own.

Build Steps

  1. Parse and validate extension.config.json against the extension config schema.
  2. Build each entry's code as its entry type defines. See Data Connector Bundles.
  3. Write manifest.json.
  4. Swap the new build in place of the old output directory.

Without --watch, every entry builds into a staging directory first, so a failed build leaves the previous output untouched.

The build doesn't type-check your TypeScript or run your entry code. A successful build or install can still fail to load. Run the scaffolded typecheck script for type errors.

Data Connector Bundles

For each data connector entry, the build:

  1. Bundles engine.entrypoint into one ES module: platform neutral, no code splitting, no minification, no source maps.
  2. Checks the bundle: it must be a single JavaScript chunk, have a default export, and contain no import, export ... from, dynamic import() or direct require(...) call. The check reads the code without running it and doesn't catch every form (e.g., require(...args)).
  3. Writes the bundle as <entry id>.js.

The build checks that a default export exists, not that it has a setup function. To run a data connector, see Test a Data Connector.

Output Directory

A build of the Data Connector Quickstart project, whose entry id is example/artic-connector, writes this layout:

dist/
dist/
├── .monospace-build
├── manifest.json
└── example/
    └── artic-connector.js

.monospace-build marks the directory as owned by the extension. The CLI replaces an output directory only when it is missing, empty, or already holds the same extension id, read from its manifest.json or, without a readable manifest, from .monospace-build. It refuses a directory that contains the project, the working directory, any entrypoint source, or any other source file the bundler reports the entrypoints import.

Install a Build

--install-to copies a successful build into an instance's install directory. See Choose the Install Location for where that directory is.

npm run build -- --install-to ../monospace/extensions

The CLI writes the build to <install-to>/<namespace>__<name>/, named after the extension id. example/artic installs to example__artic/. The .monospace-build marker isn't copied.

RuleBehavior
Target holds another extension, or files but neither an extension manifest nor a .monospace-build markerRefused with extension.install_dir_not_ours
Target and --out-dir inside each otherRefused with extension.install_overlaps_artifact
Target is a symlinkThe CLI updates the directory it points to. The instance doesn't load a symlinked extension directory, so install into a real directory
Existing install of the same extensionReplaced whole, never merged. Files you added there are deleted
Copy failsThe previous install stays in place

Without --watch, the CLI installs before it replaces the output directory. If replacing --out-dir then fails, the command fails with the new build already installed.

After you rename the extension id, remove the old output directory or pass a new --out-dir. The build refuses an output directory that holds another extension id.

The renamed build installs to a new directory. The CLI never removes the old install, so delete it yourself.

A running instance picks up the install on its next start. With MONOSPACE_EXTENSIONS__AUTO_RELOAD on, it reloads the install automatically. Auto-reload is off by default, and the install directory must exist when the instance starts.

Without --json, the CLI prints Restart the engine to load it, unless it runs with MONOSPACE_EXTENSIONS__AUTO_RELOAD=true. after a build that installed. A build without --install-to ends with To load it, build with --install-to pointing at an engine's extensions directory. instead. In watch mode, it prints one of them once, when you stop a session whose last build succeeded.

Watch for Changes

--watch builds, then rebuilds when a source file or extension.config.json changes:

npm run build -- --watch --install-to ../monospace/extensions
  • Each rebuild writes in place into the output directory. Without --json, a successful rebuild prints Built 1 entry. or Built 2 entries., and so on.
  • A failed rebuild prints a warning and keeps the last good file for the failed entry.
  • With --install-to, the CLI installs only when every entry in the cycle built. A good entry never installs next to a broken one.
  • After each successful rebuild, the CLI deletes every file in the output directory except manifest.json, .monospace-build and the current entry files.

When preparing a cycle fails (e.g., an invalid extension.config.json or a missing Rolldown), watch retries only after the next change to extension.config.json. Installing a missing dependency doesn't trigger a retry: save the config again or restart the command.

Watch mode doesn't reload the instance. Combine it with MONOSPACE_EXTENSIONS__AUTO_RELOAD to see changes without a restart.

Read JSON Output

With --json, each command prints exactly one JSON object on stdout when it finishes, including on failure:

response.json
{
  "data": {
    "artifactDir": "/home/you/artic/dist",
    "builds": 1,
    "entries": [{ "id": "example/artic-connector", "output": "example/artic-connector.js" }],
    "installedTo": null
  },
  "effectState": "complete",
  "error": null,
  "outcome": "ok",
  "schema": "monospace.extension.build/1"
}
FieldValues
schemamonospace.extension.create/1 or monospace.extension.build/1. monospace.error/1 when the CLI fails before it identifies a command, or when a command stops before finishing
outcomeok, failed or cancelled
effectStatecomplete, none (the failing step changed nothing) or unknown (files may have been written). In watch mode, a failure can report none even though earlier rebuilds wrote files
dataThe command's result, or null
errornull, or { category, code, message, fix? }. fix is present only when the CLI has a suggestion

The data fields depend on the command:

Commanddata fields
createid, entryId, name, dir, files, agent (the detected package manager), next (the install and build commands)
buildartifactDir, entries, installedTo, builds (the number of build cycles, failed ones included)

Check outcome, not data, to detect failure. A failed watch session still reports the last successful build in data. Without any successful build, entries is empty and installedTo is null.

--help and --version print to stderr under --json, so stdout stays empty.

Handle Exit Codes

Exit codeWhen
0Success; a command cancelled by SIGINT or SIGTERM (outcome cancelled: effects may have occurred); or a watch session stopped with Ctrl-C after a cycle that built
1Any error, including a watch session whose last cycle failed, and a second SIGINT or SIGTERM before the command finished
130, 143A second SIGINT (130) or SIGTERM (143) after the command finished but before the process exited

Exit code 0 alone doesn't prove a build completed: check outcome under --json. A second signal stops the command at once. If the command hadn't finished, it reports cli.unfinished; under --json, that result uses the monospace.error/1 schema.

--watch runs until you stop it, or until it can no longer watch the files. Stopped with Ctrl-C, it exits 0 with outcome cancelled when the last cycle built, and 1 with that cycle's error when it failed. When it loses the watch, it ends on its own and exits 1 with that error.

Fix Common Errors

Every error has a code, shown in --json output. Most errors also print a fix. This table covers the config, bundle, output and create errors:

CodeCauseFix
extension.config_missingNo extension.config.json in the working directoryRun the build from the project root
extension.config_invalidThe config breaks the schema: bad id or version, unknown field, missing fieldCorrect the field the message names
extension.config_unparseableInvalid JSON, including comments and trailing commasFix the JSON syntax
extension.config_duplicate_keyA key appears twice in one objectRemove the repeated key
extension.entry_id_duplicatedTwo entries share an idGive each entry its own id
extension.rolldown_missingrolldown isn't installed in the project. When package.json declares it, the message reads "This project declares rolldown, but its dependencies are not installed."Install the project's dependencies, or add rolldown@^1.2.0 as a dev dependency
extension.rolldown_unsupportedThe project's rolldown isn't ^1.2.0Install a supported version
extension.build_failedRolldown couldn't bundle the source. The message names the first error, with its file, line and column when Rolldown reports them. With several errors, it says how many more there areFix the source error it names
extension.built_not_self_containedA data connector bundle still imports a module, often a Node built-in such as node:fsImport only code that bundles; Node built-ins aren't available
extension.built_without_default_exportA data connector entrypoint has no default export, or the output is CommonJSExport the entry's code (e.g., the connector) as the module default
extension.built_splitA data connector bundle isn't exactly one JavaScript chunkRemove code splitting or plugins that emit extra files
extension.artifact_dir_not_ours--out-dir holds another extension, or files but neither an extension manifest nor a .monospace-build markerChoose a new or empty directory
extension.output_holds_sources--out-dir, or the <install-to>/<namespace>__<name> directory, contains the project, the working directory, an entrypoint source, or a source file an entrypoint importsChoose a directory that contains none of them, such as ./dist
extension.create_dir_not_emptyThe create target gained files while the command asked its questionsCreate the extension in a new directory
extension.create_dir_unusableThe create target isn't a directory, can't be read, or a file can't be written into itPoint the command at a directory, or at a path that doesn't exist yet
extension.create_id_invalid--id isn't a valid extension idPass an id in id format, such as --id acme/store
extension.create_name_invalid--name has no visible character, or contains a control characterPass visible text on one line, such as --name "Acme Store"
cli.confirmation_requiredThe command needed an answer and prompts are offPass the argument or flag the message names

See Also

MonospaceThe governed API layer for every app, person, and agent.

Copyright © 2026 Monospace Inc.