The problem
Agents operating CLIs have two jobs: read the output, decide the next command. Job one is easy - parse stdout. Job two is hard - how does the agent know which command to try next?
The usual answer is "stuff a manual into the system prompt." That approach has three failure modes:
- Stale - the prompt drifts out of sync with the CLI's actual commands
- Expensive - every agent invocation pays for the whole manual
- Lossy - the agent has to re-derive "what makes sense next" from first principles, every time
The next-steps block replaces that with structured post-command hints
written to stderr as NDJSON. The CLI tells the agent what to try next.
The agent reads it. No prompt engineering.
Quickstart
import { emit } from "@/cli/agent/json-mode";
import { emitNextSteps } from "@/cli/agent/next-steps";
program.command("list").action(async (opts) => {
const items = await fetchItems();
emit(items, opts);
emitNextSteps(
[
{ command: "myapp show <id>", description: "see details for one item" },
{ command: "myapp quote <id>", description: "live price" },
{
command: "myapp export --format csv",
description: "export all items",
optional: true,
},
],
opts,
);
});The agent loop
const { stdout, stderr } = await spawn("myapp", ["list", "--json"]);
const data = JSON.parse(stdout);
const hints = stderr
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line))
.filter((obj) => obj.type === "next-step");
nextCommand(data, hints);Signature
export type NextStep = {
command: string;
description: string;
optional?: boolean;
};
export function emitNextSteps(
steps: NextStep[],
opts?: EmitOptions,
): void;The discipline rule
next-steps writes to stderr only. Never stdout. This keeps stdout
mono-typed as "the answer to this command" and stderr as "everything else."
Chained with json-mode
Installing next-steps automatically pulls json-mode via registry
dependencies. The two share detectMode() so mode detection stays
consistent.