The public extension API is a command directory.
DEFAULT TO NUSHELL. USE BABASHKA/CLOJURE WHEN NU GETS AWKWARD. USE JAVASCRIPT ONLY WHEN THE COMMAND NEEDS AN NPM SDK, A NODE-ONLY PACKAGE, OR A NODE-SPECIFIC RUNTIME SURFACE.
Do not choose JavaScript just because JSON objects are convenient there. Use Nu for command orchestration, JSON, REST, filesystem discovery, and process composition. Use Babashka/Clojure when the logic wants richer data transformations or local state handling. Use command-local JavaScript only for SDK/package/runtime requirements that Nu or Babashka cannot reasonably cover.
my-command/
run executable public command
desc help text; first line appears in command lists
spec.yaml optional carapace completion spec
carapace-complete optional dynamic completion script
compgen optional shell completion script
inner/ private helper scripts, callable only through `strap inner`
hide hide from default command list while remaining callable
wd optional executable that prints command cwd
Commands receive:
STRAP_ROOTSTRAP_CONFIGSTRAP_GLOBALSTRAP_PROJECTSTRAP_SESSIONwhen an active session existsSTRAP_WORKSTRAP_WORKSPACESTRAP_CMD_NAMESTRAP_CMD_DIR
Command safety is not described by command-owned metadata. Use launch profiles, sandboxing, and restricted tool/jsmcp configuration for restricted modes.
Project and user commands are capsules. A capsule command can be replaced, removed, or rewritten in another language without changing other command internals.
A capsule command may use:
- Its own command directory through
STRAP_CMD_DIR. - The public Strap environment variables listed above.
- Stdin/stdout JSON protocols.
- Standard external runtimes and tools such as
nu,bash,node,jq,sqlite, orjj. - Other Strap commands through the public process boundary, for example
strap state init | strap agents apply chat-concise.
A capsule command must not use:
#strap/*imports.- Repo-local implementation imports from another command's private source tree.
- Direct execution of repo-local JS implementation entrypoints outside the owning command capsule.
- Another command's private files except through
strap <command>orstrap inner <command> <helper>. - Ordered
load-fileplusin-nsto spread one namespace across files. Babashka/Clojure commands with multiple files should add the command-localsrcdirectory to the classpath and use normalns/requireforms. - Captured stdout as an internal function call. Split pure command-local functions from CLI printers, and call the pure function internally.
strap project-check enforces the import/direct-JS parts of this rule for project and user command overlays, excluding the project-* validation commands themselves.
If two commands need the same behavior, prefer a small JSON-in/JSON-out command API over a shared library or copy-pasted implementation. Root discovery, model resolution, tool discovery, and similar project primitives should have one command contract that other commands call through strap <command>, not private source imports and not parallel reimplementations.
File size is part of the capsule contract. A source file, run script, or helper file has a hard cap of 150 lines; prefer smaller files, usually 50-100 lines. If a file approaches the cap, split it into command-local modules under that command directory instead of adding more branches to one large script. JSON data files are exempt from this line cap because readability matters more than vertical compactness there. strap project-check enforces the cap for command capsules; the temporary exceptions in .strap/config/line-cap-exceptions.txt are existing refactor debt and should shrink over time, not grow.
JSON files should be checked in as readable, pretty-printed documents, not minified one-line blobs. If a JSON file becomes too large to read comfortably, split it by responsibility rather than squashing it.
Public, Hidden, And Inner Commands
Use the smallest surface that matches the job:
| Surface | How to define it | How to call it | Use when |
|---|---|---|---|
| Public command | command directory with run and no hide |
strap <command> |
Humans or agents should discover and use it directly. |
| Hidden command | command directory with run and hide |
strap <command> |
It needs command identity but should not appear in the normal command list. |
| Inner helper | executable under command/inner/ |
strap inner <command> <helper> |
It is private to one command's implementation. |
Examples:
strap provideris public;provider-chatgptis hidden behindstrap provider chatgpt ....strap zkis public;strap inner zk index ...is a private implementation boundary for its derived index.- REST-only provider commands are Nu capsules. Provider commands that need richer local auth logic can use Babashka. Use a provider-local Node package only when SDKs or package dependencies are required.
Create a temporary command under the active session overlay, or under .strap-user/commands when STRAP_SESSION is unset:
strap commands new my-command
strap edit my-command
strap help my-commandList commands for humans or agents:
strap commands list
strap commands list --json
strap commands manifest zk
strap commands validate --jsonPrivate helpers go in inner/ and must be called through the runner, never by executing the file path directly:
strap inner my-command helper-name arg1 arg2Run a command against a specific session without exporting STRAP_SESSION in the parent shell:
strap with-session my-session strap history log| Command | Purpose |
|---|---|
agent |
Fork and fold agent state. |
agents |
List, show, apply, and import markdown agent profiles. |
artifact |
Inspect and manage layered command/tool/profile artifacts. |
carapace |
Install and serve shell completion integration. |
commands |
List, inspect, validate, and scaffold command directories. |
context |
Transform extracted context, including quoting and summarization. |
embed |
Embed text through the selected provider. |
edit |
Edit or create command files. |
history |
Manage jj-backed active-session history. |
llm |
Compile, call, or complete model-profile requests from canonical state. |
loop |
Run the Nushell model/tool loop. |
mcp |
Run bundled stdio MCP servers. |
model |
List, show, select, and fork model profiles. |
one-shot |
Run an agent once until its first final answer. |
paths |
Print resolved root/config/global/project/session/work/workspace paths. |
provider |
Run provider-specific operations and authentication. |
run-calls |
Execute pending tool calls in canonical state. |
sandbox |
Run a command through a bubblewrap sandbox profile. |
session |
Create, inspect, update, save, list, and trace user-local project sessions. |
skills |
List, show, apply, and import skill instruction bundles. |
state |
Initialize and transform canonical strap.state.v0.2 JSON. |
status |
Show roots, layers, and overlayed artifact status. |
work |
Initialize or inspect .strap project artifacts and .strap-user local work. |
zk |
Use the shared markdown-source zettelkasten with a derived SQLite/FTS/vector index. |
Run strap help <command> for command-local usage text.
Commands, tools, model profiles, agent profiles, and skill bundles can be inspected and moved through the session/user/project/global/root overlay lifecycle:
strap status
strap artifact status command
strap artifact status agent
strap artifact status skill
strap artifact status model
strap artifact workon tool json_echo
strap artifact promote tool json_echo
strap artifact promote tool json_echo --from project --to global
strap artifact discard tool json_echoworkon creates a session-layer working copy when STRAP_SESSION is set, otherwise a user-layer copy. promote moves one step down by default: session to user, user to project, project to global, and global to root. discard removes the session-layer copy first, then the user-layer copy.
Agent profiles are markdown files with optional frontmatter:
strap agents list
strap agents show chat-concise
open state.json | to json | ^strap agents apply chat-concise | save -f next.json
strap agents import-opencode ~/.config/opencode/agentsapply updates the selected actor, defaulting to assistant, with the profile description, instructions, and permission metadata.
Skills are skills/name/SKILL.md instruction bundles:
strap skills list
strap skills show concise
open state.json | to json | ^strap skills apply concise | save -f next.json
strap skills import-opencode ~/.config/opencode/skillsapply attaches the skill to the selected actor, defaulting to assistant, without replacing the active agent profile.
Model profiles are layered models/name.json artifacts. Runtime commands default to --model current.
strap model list
strap model show current
strap model use gpt-5.5-chatgpt
strap model fork current next-chatgpt --set-model-id gpt-nextcurrent.json is a Linux symlink to the selected profile in the active overlay.
Nu is an implementation language for command capsules, not a separate public command layer. There is no grouped native use strap module. Use the process boundary from Nu the same way shell callers do:
strap state init
| to json
| ^strap agents apply chat-concise
| from json
| to json
| ^strap skills apply concise
| from jsonEvery capsule exposes executable run with a shebang; this is the process entrypoint used by strap <command>. For Nu-native commands, run.nu is command-local implementation code. Additional .nu files may exist only as modules used by run.nu.
The boundary split is strict: run.nu accepts and returns native Nushell data. It must not read stdin, write stdout, or expose command-boundary text JSON flags. The executable run owns process concerns: parsing stdin text JSON when the command logically takes input, ignoring stdin when it does not, printing text JSON or plain text, and delegating to run.nu for the actual work.
Do not add stdin/file-switch compatibility flags for command input. Data comes in through stdin and goes out through stdout at command boundaries.
When a command is not Nu-native, omit run.nu. Do not add main.nu; that entrypoint shape is retired.
State lens operations are over top-level events: they return full state with the selected events replaced. Provide an external JSON filter after --, or plain command words that default to strap <command> ....
strap state with-events -- jq 'map(select(.from == "user"))' < state.json > next.json
strap state map-events -- jq '. + {"reviewed": true}' < state.json > next.json
strap state with-extract bm_start bm_end -- jq 'map(.text |= ascii_upcase)' < state.json > next.jsonCanonical state does not require permanent IDs on every event. When an agent needs a stable handle, it can place optional inline bookmarks on visible messages/scopes:
strap state bookmark add --text "unique substring" --label fold-start < state.json > next.json
strap state bookmark list < next.json
strap state extract --from bm_start --to bm_end < next.json > context.json
strap state fold --from bm_start --to bm_end --summary "summary" < next.json > folded.jsonThe text match must be unique. If it is ambiguous, provide a longer substring. A range is always two single-node bookmarks.
The current command contract is intentionally small and file-oriented. The next milestone is to freeze a v0 contract for:
- required files and executable bits;
- manifest shape;
- validation semantics;
- completion hooks;
inner/helper behavior.