oscli

Output and logging

Write lines through the shared output system and reuse styles where needed.

oscli routes user-facing output through one output layer so logs, prompts, boxes, progress, and links all stay visually aligned.

Logging

Use cli.log() when you want one line in the normal output flow.

If you call cli.log(level, message), the returned chain lets you apply text modifiers before the message is written.

await cli.run(async () => {
  cli.log("ready");
  cli.log("warn", "review this before production").bold().underline().flush();
  cli.success("Done.");
});
  ready
 review this before production
 Done.

Prop

Type

Steps

Use cli.step() to render a step with a status icon. Compose multiple steps for step lists and rail patterns.

await cli.run(async () => {
  cli.step("Creating project...", "active");
  cli.step("Installing dependencies...", "active");
  cli.step("Build complete", "done");
});
◆ Creating project...
◆ Installing dependencies...
◇ Build complete

Status options: "active" (◆), "done" (◇), "error" (✗), "warning" (⚠), "pending" (○).

Badge logging

Use cli.badge() for [TAG] prefixed log lines — common in build tools and CI output.

await cli.run(async () => {
  cli.badge("info", "Pulling image node:20-alpine");
  cli.badge("warn", "Layer cache expired");
  cli.badge("ok", "Build finished in 4.2s");
});
[INFO]  Pulling image node:20-alpine
[WARN]  Layer cache expired
[OK]    Build finished in 4.2s

Timestamped logging

Use cli.timestamp() for log stream / tail style output with real-time timestamps.

await cli.run(async () => {
  cli.timestamp("info", "Server started on port 3000");
  cli.timestamp("warn", "Rate limit approaching: 90/100");
  cli.timestamp("error", "Database connection dropped");
});
12:04:01  INFO   Server started on port 3000
12:04:05  WARN   Rate limit approaching: 90/100
12:04:07  ERROR  Database connection dropped

Inline output

Use cli.raw(), cli.lines(), and cli.write() for tight output without the section spacing that cli.log() adds.

  • cli.raw(line) — one line, no gap
  • cli.lines(lines) — multiple lines, no gaps between them
  • cli.write(content) — writes pre-formatted content (e.g. from cli.table()) without extra indent/color
await cli.run(async () => {
  cli.lines([
    "┌ Connecting",
    "",
    "├─┬ Auth",
    "│ ├── Checking token...    ok",
    "│ └── Resolved as user@app",
    "",
    "└ Ready",
  ]);
});
 Connecting

├─┬ Auth
 ├── Checking token...    ok
 └── Resolved as user@app

 Ready

Reusable styles

Use cli.style() when you want to build a style once and reuse it across multiple writes.

await cli.run(async () => {
  const callout = cli.style().color("cyan").bold();

  cli.log("info", callout.render("important"));
  cli.log(callout.render("plain line with shared styling"));
});
 important
  plain line with shared styling

Prop

Type

Exit with hints

Use cli.exit() when the command cannot continue and you want a concrete next step in the error output.

cli.exit("package.json not found.", {
  hint: "Run this command from the project root.",
  code: "not_found",
});
 package.json not found.
    → Run this command from the project root.

Inline confirm

Use cli.confirm() when you need one simple confirmation in the middle of a flow without defining a named prompt.

await cli.run(async () => {
  const approved = await cli.confirm("Deploy now?", false);

  if (!approved) {
    cli.exit("Cancelled.");
  }
});
  Deploy now?
 (y/N) _

cli.exit() writes to stderr. cli.log("error", ...) also writes to stderr. Other output stays on stdout.

On this page