CLI
No binary to install: you add the SDK to your own CLI. One report() call in
your top-level catch reports the failure and returns a known fix if one exists.
Report on failure
Section titled “Report on failure”import { createProblems, formatReport } from "@problemsdev/sdk";
const problems = createProblems({ slug: "acme", toolName: "acme-cli@2.1.0" });
try { await runCommand(argv);} catch (err) { const result = await problems.report(err, { surface: "cli", operation: argv[0], }); const guidance = formatReport(result); if (guidance) console.error(guidance); // stderr keeps stdout usable for JSON printError(err); process.exitCode = 1; // retain your command's existing failure code}report() never throws and never changes control flow. It returns a
ReportResult and swallows its own errors, so it’s safe in an existing catch:
the CLI behaves as before, the failure is reported, and r.answer may carry a
fix to show the user.
Add context
Section titled “Add context”The second argument, ReportContext, is optional. goal and operation speed
up triage:
await problems.report(err, { surface: "cli", operation: "deploy", goal: "deploy the staging environment", argKeys: ["env", "region"], // argument NAMES only, never values versions: { node: process.versions.node, "acme-cli": "2.1.0" },});Optional add-ons
Section titled “Optional add-ons”Two opt-in extras.
Catch network failures. A wrapped fetch reports any 5xx or network error your
CLI makes, and passes responses through unchanged:
globalThis.fetch = problems.wrapFetch();Observe uncaught crashes. This reports before the process dies. It only observes, and never changes crash or exit behavior:
const uninstall = problems.installProcessHooks();// call uninstall() to remove it.CLI output and shutdown
Section titled “CLI output and shutdown”formatReport(result) returns plain text containing the report reference and,
when available, the full answer, numbered steps, documentation links, and answer
ID. Cached results are labeled. Failed or disabled reports produce an empty
string. The formatter removes terminal control characters from text guidance.
It does not print, change exit codes, or make requests.
Send human guidance to stderr using your CLI’s existing output handling. For a JSON command, embed the report in the command’s existing response instead of appending a second JSON document to stdout:
const output = { error: "Deployment failed", problems: result };process.stdout.write(JSON.stringify(output) + "\n");For a standalone report JSON value, use
formatReport(result, { format: "json" }). This preserves the ReportResult
fields, including failure reasons, and adds no trailing newline. JSON retains
field values; parse it as data rather than printing decoded fields to a terminal
without sanitization. Sending or receiving an answer does not mean the command
succeeded; preserve your existing exit code.
Before normal shutdown, wait for background reports started by wrapFetch() or
unawaited report() calls:
try { await runCommand(argv);} finally { const flushed = await problems.flush({ timeoutMs: 1_000 }); // flushed.completed: all reports in this snapshot settled (success or failure) // flushed.pending: reports still in flight when the budget expired}flush() defaults to a one-second wait. Zero checks without waiting; negative
values also become zero and non-finite values use the default. It takes a
snapshot of unique report deliveries already in flight. It excludes later
reports, status/verification calls, and HTTP operations still awaiting a response
or body capture. Await those operations before flushing.
The wait is bounded even if a custom fetch ignores cancellation. Expiring the
wait does not cancel requests or retry failures; requests retain their own
timeoutMs. A completed flush means settled, not necessarily delivered. The SDK
does not install shutdown handlers or call process.exit(). Active requests may
still keep Node alive after a flush times out; this is not a process-exit deadline.
Forced exits and crashes remain best effort. flush() cannot make an
uncaughtExceptionMonitor callback wait before Node terminates.