CLI

The clarkcant command

Ask Clark, read and create conversations, stop work or call any route, from a terminal or a script.

Run it from a checkout

The CLI is the workspace package @clarkcant/cli in apps/cli. It is not published to npm yet, so run it from a ClarkCant checkout:

# The CLI is the workspace package @clarkcant/cli (apps/cli). It is not on npm yet.
git clone https://github.com/digitopvn/clarkcant.git
cd clarkcant
corepack enable && pnpm install

node apps/cli/src/main.ts ask "hello"
pnpm clarkcant ask "hello"

# Optional: a shell alias so the examples below work as written
alias clarkcant="node $PWD/apps/cli/src/main.ts"

Develop a widget

The package author command clark supports the widget lifecycle from a checkout. It needs no runtime account or provider:

node packages/widget-cli/src/cli.ts widget init ./my-widget --template form
node packages/widget-cli/src/cli.ts widget dev ./my-widget
node packages/widget-cli/src/cli.ts widget test ./my-widget
node packages/widget-cli/src/cli.ts widget pack ./my-widget

These are clark widget init/dev/test/pack when the workspace bin is on your PATH. Dev runs the widget in the isolated browser host on loopback. Its service panel simulates each declared capability as loading, ready, blocked or unhealthy, and lets you test offline, degraded and restart recovery with package fixtures. It does not start a real service or call a provider.

A binding to a capability that runs as a package job uses a job fixture in fixtures/dev-host-services.json instead of an outcome: its progress steps, the output it completes with and the error it fails with. A press then starts a simulated job, and the Simulated jobs list steps it with Next step, Complete or Fail; the widget's own cancel works too. Every ending says “(simulated by clark widget dev)”, nothing is written to disk, and clark widget test checks that exactly the job capabilities have a job fixture.

The semantic inspector shows the normalized proposal the runtime keeps, what was truncated or dropped, the delta, the context note and inspect_ui output. The composition panel sends declared events through their shared validators and records widget-emitted events. Invalid or undeclared events are refused; simulation stays local and cannot invoke capabilities. clark widget test checks fixture semantic limits and declared event schemas before pack builds the artifact and digest.

--template accepts blank, form, dashboard, pure-ui, ai-generator, ui-with-service, media-tool and connected-app today. pure-ui copies the reference text editor under your new package's own id, facet id and name, without the editor's tests, so you start from a working app that passes clark widget test. ai-generator and ui-with-service copy the reference image generator the same way: ai-generator keeps the provider, with the placeholder origin https://images.example.com to replace, and ui-with-service has a service that draws the image itself, declares no provider and only reads. media-tool copies the reference media render tool, a widget and a service, under your new package's own id, without the tool's tests. connected-app copies the reference connected app under your new package's own ids and name: a widget, a service whose capabilities name the scopes they need on one declared account connection, skills, the fake connector it is tested against, and the service's portable dev/service.test.mjs. Replace the provider, client id, scopes and endpoints with your provider's before you publish. The editor and media templates and the MCP App adapter are not implemented yet.

node packages/widget-cli/src/cli.ts widget init ./my-editor --template pure-ui

Package and publish a widget on npm

When a package has a package.json, clark widget pack builds its npm archive with pnpm pack into dist/<name>-<version>.tgz. It then extracts the archive with the same reader your node uses to install it, and refuses the archive unless it passes the conformance suite on its own, holds the same clarkcant.json and contains nothing credential-shaped, such as .npmrc, .env files, .git-credentials, private keys, node_modules or .git. A files list that leaves out something the widget needs is caught here, not on someone else's machine. clark widget init writes a package.json for every template. A package without one stays a local or git package, and pack builds no archive.

Pack refuses a package.json that breaks these rules, and names each one:

The generated package.json is named after the last part of your package id, and that name may already be taken on npm. Rename it, or use a scope you own such as @you/my-widget, before you publish. Pack needs pnpm (corepack enable pnpm). Adding a package.json to a package you already packed changes its author digest, so bump the version when you add it.

dist/artifact.json records three digests, and each answers one question:

clark widget publish prepares the directory entry and uploads nothing. The entry names the exact npm version and the archive's content digest. The command prints three outcomes separately (prepared: yes; published to npm: no; Marketplace submission: no) and the command that publishes the archive it checked:

node packages/widget-cli/src/cli.ts widget pack ./my-widget
node packages/widget-cli/src/cli.ts widget publish ./my-widget
npm publish ./my-widget/dist/my-widget-0.1.0.tgz

Publish that file rather than running npm publish in the package folder, so the registry serves exactly the bytes the entry names. A Marketplace then lists npm packages that carry the clarkcant keyword. --source local prepares an entry for the package's own folder instead, which needs no npm account.

Reference app: a text editor

examples/reference-apps/text-editor is a whole widget built only on the public widget contracts: one isolated UI facet, with no service, no permissions and no requested capabilities. You open a text file, edit it, save it and ask Clark to rewrite a selection, and the widget never learns where the file lives.

In a real install, Clark binds the rewrite button when it places the editor with that button, through its place_widget tool; without a binding the button stays disabled, with its reason shown. The editor also offers Clark the action replaceSelection (actions Clark asks a widget to perform), so a request typed in the composer, such as “make the second line shorter”, can replace the selected text through your execution policy. The editor refuses it when the selection no longer holds the text Clark read. The change is an unsaved edit, and saving stays yours. Saying the action's label while the editor is focused runs it through the same path and execution policy as asking Clark in the composer, on a page that can reach the editor's frame (digitopvn/clarkcant#444). When your policy asks first, the approval card appears in the conversation and you can answer it by click or out loud. A spoken answer decides only when every word is a yes word, such as “yes”, “ok” or “đồng ý”, or every word is a no word, such as “no”, “cancel” or “không”, apart from fillers like “please” or “nhé”. A question, a mix, or anything else, such as “not ok” or “chưa được”, gets the question again. Afterwards voice says what happened, with the widget's answer read as the widget's own words. A sentence that implies the action without saying its label, such as “make this shorter”, is not matched by voice yet; ask Clark instead.

Check the reference app from a checkout:

node packages/widget-cli/src/cli.ts widget test examples/reference-apps/text-editor
node packages/widget-cli/src/cli.ts widget pack examples/reference-apps/text-editor

Reference app: a spreadsheet

examples/reference-apps/spreadsheet is a second whole widget built only on the public widget contracts: one isolated UI facet and no service. It works on a file, keeps a large sheet within bounds, describes itself to Clark and applies a change Clark chose.

In a real install, Clark's place_widget tool places the sheet and binds both its offered format action and, when asked, the format button. The sheet offers Clark format ({ format: percent | number | plain, range? }, see actions Clark asks a widget to perform), so a request typed in the composer, such as “format this as a percentage”, formats the given range, or the selection when none is given, through your execution policy. The sheet refuses while it is busy or read-only, for an unknown format, and for a range it cannot read or that is past its bounds. “Undo format” steps back Clark's format like any other.

Check the reference app from a checkout:

node packages/widget-cli/src/cli.ts widget test examples/reference-apps/spreadsheet
node packages/widget-cli/src/cli.ts widget pack examples/reference-apps/spreadsheet

Reference app: an image generator

examples/reference-apps/image-generator is a package with an isolated UI facet and a service facet. A widget starts long work on its service, follows it as a job and gets an image back as a file, while the service reaches a provider with a key it never holds.

Clark places the image generator with its Tạo ảnh button through its place_widget tool, which binds the button to the package's own image.generate capability (digitopvn/clarkcant#445). A button binds only to a capability served by the package the widget comes from, and sends only inputs that capability declares. Until a provider key is stored, the button is disabled with your node's reason. No real image provider is wired up yet; that is digitopvn/clarkcant#321. An attached image's chip reads untitled.png, because a job's result file carries no name.

Check the reference app from a checkout, or start your own from it:

node packages/widget-cli/src/cli.ts widget test examples/reference-apps/image-generator
node packages/widget-cli/src/cli.ts widget pack examples/reference-apps/image-generator
node packages/widget-cli/src/cli.ts widget init ./my-generator --template ai-generator

Reference app: a media render tool

examples/reference-apps/media-render is a package with an isolated UI facet and a service facet. It renders a WAV clip you pick, with a gain change and a trim, as a job the widget follows and you can stop. The service reads the clip from your node a piece at a time and never gets a path or a handle to it (files a service reads).

Only 16-bit PCM WAV, mono or stereo, is rendered; there is no other codec. Clark places the widget with its Dựng button through place_widget, bound to the package's own render capability (digitopvn/clarkcant#445). Its browser tests need a container engine that runs Linux containers.

Check the reference app from a checkout, or start your own from it:

node packages/widget-cli/src/cli.ts widget test examples/reference-apps/media-render
node packages/widget-cli/src/cli.ts widget pack examples/reference-apps/media-render
node packages/widget-cli/src/cli.ts widget init ./my-render --template media-tool

Reference app: a connected app

examples/reference-apps/connected-app is a package that works on your account at a provider without ever holding it: a widget that lists tasks and renames one, a service that calls the provider, and skills that tell Clark how. Your node connects the account and signs the service's requests; the widget sees only a status (connecting an account).

No live provider ships yet; it is tracked in digitopvn/clarkcant#333. The desktop app opens only HTTPS addresses in the system browser, so you connect the loopback fake connector from the browser client; real providers use HTTPS. Clark places the widget with its list and rename buttons through place_widget, bound to the package's own capabilities (digitopvn/clarkcant#445).

Check the reference app from a checkout, try it against the fake connector, or start your own from it:

node packages/widget-cli/src/cli.ts widget test examples/reference-apps/connected-app
node packages/widget-cli/src/cli.ts widget pack examples/reference-apps/connected-app
node --test examples/reference-apps/connected-app/dev/service.test.mjs
node examples/reference-apps/connected-app/dev/fake-connector.mjs
node packages/widget-cli/src/cli.ts widget init ./my-tasks --template connected-app

Build a theme

The package author command clark also supports data-only theme facets. Run it from a checkout; no runtime account or provider is needed:

node packages/widget-cli/src/cli.ts theme init ./my-theme
node packages/widget-cli/src/cli.ts theme dev ./my-theme
node packages/widget-cli/src/cli.ts theme test ./my-theme
node packages/widget-cli/src/cli.ts theme pack ./my-theme

These are clark theme init/dev/test/pack when the workspace bin is on your PATH. Init writes a generalized package manifest and a checked JSON theme. Dev binds to 127.0.0.1:4319 (--port overrides it) and reloads edited data while retaining the local preview draft. Stop it with Ctrl-C. Theme Lab renders production conversation, composer, widgets, controls, Settings, modal, approval, error, status and Orb examples; its example interactions never operate your runtime. Switch scheme, normal/phone/compact width and reduced motion, and inspect compiled tokens and recipes.

Test audits arbitrary theme documents in both schemes: text/focus contrast, protected states and edges, bounded typography, reduced motion, contained regular assets, manifest and data-only execution. Themes accept no raw CSS, HTML, scripts, external resources or font URLs. Package symlinks are refused. Browser layout and keyboard checks remain explicitly requires-dev-host; a token audit does not certify a browser journey. Pack uses the existing immutable artifact format, includes theme document digests, records unverified checks and refuses changed bytes under the same version. Increase the version before packing an edit.

Connection

FlagEnvironmentDefault
--urlCLARKCANT_URLhttp://127.0.0.1:8765
--tokenCLARKCANT_TOKENRead from identity.json in the data dir, only for a node on this machine (localhost, 127.x, ::1); a remote --url needs --token, so the local token never leaves the machine
--data-dirCLARKCANT_DATA_DIR~/.clarkcant
--json–Print raw JSON

On the machine that runs the node, with the default data dir, it needs no flags. For a node elsewhere, set the URL and token:

export CLARKCANT_URL="https://clark.example.com"
export CLARKCANT_TOKEN="<token>"
clarkcant status

Commands

CommandWhat it does
clarkcant ask "<text>" [-c <conversationId>]Streams Clark's reply to stdout. Without -c it creates a conversation and prints its id on stderr. When the message joins a reply Clark is already writing (resolution: "steered"), it says so on stderr and exits 0; read the conversation for the answer.
clarkcant statusHealth and node.
clarkcant conversationsLists conversations.
clarkcant new [title]Creates a conversation.
clarkcant read <conversationId>Prints the conversation.
clarkcant stopEmergency stop.
clarkcant api <METHOD> <path> [jsonBody]Raw call to any REST route except a person's decision (approving a guarded action, deciding a package capability, confirming an app intent, reporting what the app did with an action the agent asked for, trusting a paired peer or issuing a grant, installing the update a notice names) or a table's CSV export, which answers 403 PERSON_ONLY.
clarkcant mcpstdio MCP server bridged to the node's /mcp (see MCP).
clarkcant discoverPrints /.well-known/clarkcant.json.
clarkcant instructions check [file|folder]Checks a project instructions file against the shared contract. It is the one command that runs offline, without a node.

Examples

clarkcant status
clarkcant discover

clarkcant ask "how do I connect Cursor to you?"      # new conversation; its id is printed on stderr
clarkcant ask "and Claude Desktop?" -c <conversationId>

clarkcant conversations
clarkcant new "Release notes"
clarkcant read <conversationId>

clarkcant api GET /node
clarkcant api POST /conversations '{ "title": "From the CLI" }'

clarkcant stop

Because the reply goes to stdout and the new conversation id goes to stderr, ask composes with pipes and files like any other command.