Skip to content

Commands

cross.stream commands use Nushell expressions to define reusable operations that can be called on-demand with arguments. Unlike handlers which maintain state between invocations, or generators which run continuously, commands are stateless and execute independently each time they are called.

Defining Commands

To create a command, append a definition string with the topic <command-name>.define:

Terminal window
r#'{
# Required: Command closure
run: {|frame|
# frame.topic - always <command>.call
# frame.hash - contains input content if present
# frame.meta.args - contains call arguments
let input = if ($frame.hash != null) { .cas $frame.hash } else { null }
let n = $frame.meta.args.n
1..($n) | each {$"($in): ($input)"}
}
# Optional: Module definitions
modules: {
"my-util": "export def format [x] { $\"formatted: ($x)\" }"
}
# Optional: Control output frame behavior
return_options: {
suffix: ".output" # Output topic suffix (default: ".response")
ttl: "head:1" # Keep only most recent frame
}
}'# | .append repeat.define

The return_options field controls the suffix and TTL for the .response frame produced by the command. TTL only applies to this .response frame—.error events never expire.

The command definition requires:

  • run: A closure that receives the call frame and can return a pipeline of results

All values produced by the closure’s output pipeline are collected into a single .response event automatically.

Calling Commands

Commands are called by appending to <command-name>.call with input content and arguments:

Terminal window
# Call the repeat command with input and args
"foo" | .append repeat.call --meta {args: {n: 3}}

Lifecycle Events

Commands emit events to track their execution:

EventDescription
<command>.responseCollected result of the command pipeline
<command>.errorError occurred during command execution

All events include:

  • command_id: ID of the command definition
  • frame_id: ID of this specific invocation

Error Handling

If a command encounters an error during execution, it will:

  1. Emit a <command>.error frame with:
    • The error message
    • Reference to both command_id and frame_id
  2. Stop processing the current invocation

Unlike generators, commands do not automatically restart on error - each invocation is independent.

Modules

Commands can use custom Nushell modules:

Terminal window
r#'{
run: {|frame|
my-math double ($frame.meta.args.number)
}
modules: {
"my-math": "export def double [x] { $x * 2 }"
}
}'# | .append calculator.define

This allows you to modularize your commands and reuse code across different commands.

Contexts

Command definitions and calls are scoped by context. Defining the same command name in two different contexts creates two independent commands. Calls are processed only when a matching definition exists in the same context; otherwise the call is ignored.

Built-in Store Commands

When the run closure executes it can use several helper commands provided by cross.stream:

  • .append – append a new frame. Metadata you provide is merged with command_id and frame_id.
  • .cat – read frames from the command’s context.
  • .head – fetch the most recent frame for a topic in this context.
  • .cas – read content from CAS by hash.
  • .get – retrieve a frame by ID.
  • .remove – delete a frame from the stream.

cat and head default to the command’s context, while append accepts an explicit context flag if you need to write elsewhere.

Key Differences

FeatureCommandsHandlersGenerators
StateStatelessStateful between callsStateless
ExecutionOn-demandEvent-drivenContinuous
ResultsStreamed immediatelyBatched on completionStreamed
ParallelismMultiple parallel callsSequential processingSingle instance
Error HandlingPer-invocationUnregisters handlerAuto-restarts
ModulesSupportedSupportedNot supported