oscli

Visual primitives

Compose boxes, tables, spinners, progress, links, trees, and diffs with the same output system.

Visual primitives are small on purpose. Some return strings for composition. Others print directly through the shared output system.

Primitive overview

Prop

Type

Box and table

Use cli.table() when you want aligned text, then pass that string into cli.box() when you want framing.

src/cli.ts
await cli.run(async () => {
  const summary = cli.table(
    ["Field", "Value"],
    [
      ["project", "oscli"],
      ["teamSize", 3],
      ["approved", true],
    ],
  );

  cli.box({
    title: "Summary",
    content: summary,
  });
});
Rendered output
─ Summary ───────────────┐
  Field     Value        │
  ─────────────────      │
  project   oscli        │
  teamSize  3            │
  approved  true         │
─────────────────────────┘

Spinner

Use cli.spin() around one async operation.

src/cli.ts
await cli.run(async () => {
  await cli.spin("Installing packages", async () => {
    await new Promise((resolve) => setTimeout(resolve, 300));
  });
});
Rendered output
  ⠸ Installing packages...   0.2s
 Installed packages       0.3s

If you do not pass doneLabel, oscli tries to convert common gerunds to a past-tense completion label.

Progress

Use cli.progress() when your work already has named steps.

src/cli.ts
await cli.run(async () => {
  const steps = ["Validate", "Build", "Finalize"] as const;

  await cli.progress("Running steps", steps, async (step) => {
    if (step === "Validate") {
      await new Promise((resolve) => setTimeout(resolve, 150));
    }

    if (step === "Build") {
      await new Promise((resolve) => setTimeout(resolve, 150));
    }

    if (step === "Finalize") {
      await new Promise((resolve) => setTimeout(resolve, 150));
    }
  });
});
Rendered output
  ⠸ Running steps  [######--------------]  [00:01]   33%
 Running steps  [####################]  [00:03]  100%

Use dividers for section boundaries and links for docs or support URLs.

src/cli.ts
await cli.run(async () => {
  cli.divider("Results");
  cli.link("oscli docs", "https://github.com/aidankmcalister/oscli");
});
Rendered output
  ───────────────── Results ────────────────
  oscli docs (https://github.com/aidankmcalister/oscli)

Tree

Use cli.tree() when you want to render directory-style output inside another primitive.

src/cli.ts
export const structure = cli.tree({
  src: {
    "index.ts": null,
    "client.ts": null,
  },
  "package.json": null,
});

cli.box({
  title: "Project",
  content: structure,
});
Rendered output
─ Project ───────────────┐
  ├─ src                │
  │  ├─ index.ts        │
  │  └─ client.ts       │
  └─ package.json       │
────────────────────────┘

Diff

Use cli.diff() when you want to show a text change without pulling in an external diff library.

src/cli.ts
await cli.run(async () => {
  cli.diff("hello\nworld", "hello\noscli");
});
Rendered output
    hello
  - world
  + oscli

cli.table() and cli.tree() return strings. cli.box(), cli.diff(), cli.divider(), and cli.link() print immediately.

On this page