@push.rocks/smartconsole

Typed console output, structured colors, tables and Markdown across backend and browser runtimes. The backend surface also provides command routing, interactive questions, and live task progress in one coordinated terminal session.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install and choose a surface

pnpm add @push.rocks/smartconsole
Import Runtime Capabilities
@push.rocks/smartconsole Node.js, Deno, Bun Output, ANSI, terminal tables, CLI, prompts, tasks
@push.rocks/smartconsole/web Browser window or worker Output, CSS console arguments, native inspection/tables, collapsed groups
@push.rocks/smartconsole/iso Backend or browser Common output, structured colors, inspection, tables, Markdown, groups

/iso stays restricted even when imported on a backend. Unsupported members and options are absent from its types. JavaScript callers receive errors for unsupported options. Wrong-runtime entrypoints throw; unknown hosts are rejected. Browser bundlers must honor the browser export condition. The browser and portable browser dependency graphs contain no Node builtins or terminal prompts.

import { SmartConsole, color } from '@push.rocks/smartconsole/iso';

const out = new SmartConsole();
await out.success(color.green('Ready'));
await out.inspect({ connected: true });
await out.markdown('# Status\n\n- [x] Connected\n- [ ] Synchronized');
await out.group('Details', async scope => {
  await scope.info('Output inside the group');
});
await out.dispose();

Common output

log, info, success, warn, error, and debug accept strings or IStyledText, with spaces between arguments. Use inspect(value) for arbitrary objects. { debug: false } suppresses debug output. Methods return promises; output is ordered and backend stream completion is awaited. flush() observes pending output and reports stream failures. dispose() drains output and releases resources; repeated disposal returns the same promise. Writes after disposal fail.

Groups serialize their callback with surrounding output. Use the supplied scope inside a group, including for nested groups. Awaiting the parent console from inside its own group would wait on that group. Scoped output exposes the common output API and becomes invalid when the callback finishes. Browser groups also accept { collapsed: true } on the browser console.

During a prompt, ordinary output is accepted into a session buffer and rendered after the prompt finishes. This allows an asynchronous validator to await logging. flush() during an active prompt throws; await the prompt before flushing.

Structured colors

import { color } from '@push.rocks/smartconsole/iso';

const label = color.concat(
  color.green('Saved'),
  ' ',
  color.text('Work account', { foreground: 'orange', bold: true }),
);
const custom = color.rgb('Custom', { r: 80, g: 160, b: 240 });
const plain = color.plain(label);

Named colors are black, blue, brown, cyan, green, orange, pink, red, and white. Styles support foreground/background colors, bold, dim, italic, underline and strikethrough. RGB channels are integers from 0 through 255.

IStyledText is serializable: { type: 'smartconsole.text', segments: [...] }. It carries text and styles, never ANSI or CSS. Control characters in user text are rendered visibly; tabs and newlines remain layout characters. A JSON transport can preserve this value and pass it back to an output method without an encoder in the transporting application.

The root additionally exposes color.toAnsi(value): string. /web additionally exposes color.toConsoleArgs(value): [string, ...string[]] for use with console.log(...args). /iso exposes neither conversion. Browser formatting uses fixed format placeholders, so percent signs in user text remain literal.

Tables

await out.table([
  { account: 'Work', usage: 25 },
  { account: 'Personal', usage: undefined },
], {
  columns: [
    { key: 'account', title: 'Account', value: row => row.account },
    { key: 'usage', title: 'Usage', value: row => row.usage == null ? undefined : `${row.usage}%` },
  ],
  missingText: 'Unavailable',
  emptyText: 'No accounts.',
});

Column order is explicit; keys and titles must be unique. Common cell values are string, number, boolean, null or undefined. Null/undefined use missingText. Native browser tables preserve numeric and boolean values. Cell styling belongs only to backend tables because native browser consoles cannot color individual table cells.

Backend tables add width, border: 'none' | 'ascii' | 'unicode', and overflow: 'wrap' | 'truncate'. Backend columns add width, align: 'left' | 'right' | 'center', and style: row => ITextStyle. Multiline cells, Unicode graphemes and display widths are handled before color encoding. Impossible explicit widths throw.

Backend columns may supply render: row => TText for mixed styles within a cell; value remains its scalar representation. A table theme accepts header, border, and alternateRow text styles. These options are rejected by /web and /iso, whose tables retain native scalar cells.

Markdown and inspection

Markdown is parsed through @push.rocks/smartmarkdown/iso. CommonMark/GFM headings, paragraphs, emphasis, links, lists, task lists, quotes, code blocks, tables, references and footnotes are rendered as console text. Frontmatter is omitted. headingPrefix: false hides heading markers. Backend Markdown also accepts width; browser Markdown tables use formatted lines to preserve styles. HTML and code stay inert, images become alt text plus URL, and destinations are never fetched. This package does not render a DOM terminal or execute Markdown.

Code fences with a recognized language use Lowlight's common grammars for syntax highlighting. Unknown or omitted languages retain their code text without syntax coloring. Set highlight: false to disable highlighting. All three surfaces accept a Markdown theme with heading, code, marker styles and tokens keyed by Highlight.js token names such as keyword, string, number, and comment.

Backend inspection accepts { depth, showHidden }; browser inspection passes the original object to native console inspection. Portable inspection uses each host's normal object representation without platform-specific options.

Backend terminal options

import { SmartConsole } from '@push.rocks/smartconsole';

const out = new SmartConsole({
  colors: 'auto',
  interactive: 'auto',
  symbols: 'auto',
});

Backend options can inject Node-compatible input, output, and errorOutput streams. Defaults are stdin, stdout and stderr. Color modes are auto, always and never; interaction modes use the same names. Automatic rendering produces plain output and append-only task updates without a capable TTY. Forced color or interactive rendering on an incapable output throws. Automatic color respects NO_COLOR; automatic interaction respects CI and TERM=dumb. Task symbols can be auto, unicode, or ascii.

Consoles sharing an output stream share its session and must agree on explicit terminal settings. The session owns write ordering, stream backpressure, task redraws and prompt arbitration. Disposal releases its resources without closing caller-owned streams or terminating the application.

CLI commands

const out = new SmartConsole();
out.cli.configure({ name: 'accounts', version: '1.0.0', description: 'Manage accounts' });
out.cli.command({
  name: 'list', aliases: ['ls'], description: 'Show accounts',
  options: { limit: { type: 'number', default: 10, aliases: ['n'] } },
}, async ({ options, args }) => {
  await out.log(`Listing up to ${options.limit} accounts`, ...args);
});
out.cli.default({}, async () => { await out.log('Choose a command with --help.'); });

await out.cli.run(); // Runtime user arguments, including Deno compiled binaries.
await out.dispose();

run(argv) accepts user arguments only, without executable or script paths. Command options follow the command. Leading options belong to the default command. help [command], --help/-h and --version/-v settle normally. Unknown commands, unknown options, missing required values, invalid values and handler failures reject. allowUnknownOptions: true explicitly permits forwarding options to another tool.

Option types are string, number, boolean, and strings, with aliases, defaults, required flags and descriptions. The handler receives typed options, positional args, and parsed argv (including the command in _[0]). dispatch(command, argv) invokes a registered handler programmatically. Each command has one awaited handler. onEvent(listener) provides observational start/finish/error events and returns an unsubscribe function; observers never control command completion. takeObserverErrors() reports observer failures.

Interactive questions

const account = await out.prompts.ask({
  type: 'list', name: 'account', message: 'Choose an account',
  choices: [{ name: 'Work', value: 'work' }, { name: 'Personal', value: 'personal' }],
});
const save = await out.prompts.ask({ type: 'confirm', name: 'save', message: 'Save this account?', default: true });

Supported types: input, confirm, list, rawlist, expand, checkbox, password, and editor. ask() returns the typed answer directly. Choices have name, value, optional disabled, and an expand-only unique key (h is reserved). Passwords accept mask; editors accept waitForUserInput. Editor prompts use the user's editor and a disposable temporary file; Deno requires read/write/env/run permissions for this operation.

Validators may return a boolean, an error message, or a promise of either. askAll(questions, options) returns answers keyed by question name. add(questions) and runQueue(options) support incrementally assembled queues. Names must be unique within a queue. A failed question rejects the queue.

Prompt options accept signal for cancellation and an explicit nonInteractive policy. The default is { mode: 'error' }. { mode: 'defaults' } uses only declared defaults; { mode: 'answers', answers: { save: true } } uses only supplied answers. Missing or invalid values fail; environment variables never silently approve a question. Prompts serialize across consoles sharing the terminal, pause live tasks, and reject with PromptCancelledError on cancellation. Disposal cancels active and pending prompts and restores input ownership.

Tasks and progress

const parent = out.tasks.create({ job: 'Synchronize' });
const child = parent.create({ job: 'Read accounts', showTimer: true });
await child.run(async task => {
  await task.setProgress(1, 2);
  await task.log('First account read');
  await task.setProgress(2, 2);
}, { successMessage: 'Accounts read' });
await parent.complete('Ready');
await out.dispose();

Tasks expose status, elapsed time, progress, logs and errors. Use log/update, setProgress, setTimerEnabled, setSpinnerEnabled, complete, fail, or attachError(error, { keepOpen: true }). run() awaits work, completes on success, and records and rethrows failures; errorKeepOpen keeps a failed operation visible as an active task. Complete children before completing their parent. Failing a parent fails its running children.

Options include rows, logLimit, showTimer, showSpinner, spinnerFrames, and spinnerIntervalMs. Progress requires 0 <= current <= total and a positive total. tasks.clear() stops rendering and invalidates existing handles. Finished or invalidated tasks reject further updates. Timers are stopped when unnecessary and on disposal; they do not keep a backend process alive by themselves.

Terminal user interfaces

The backend out.tui provides composable screens. /web and /iso expose no TUI members or types. Screens require interactive TTY input/output and raw input; starting a screen in a pipe, CI, or an incapable terminal throws.

const table = out.tui.table({
  rows: [{ id: 'work', usage: 25 }, { id: 'personal', usage: 80 }],
  rowKey: row => row.id,
  columns: [
    { key: 'id', title: 'Account', value: row => row.id },
    { key: 'usage', title: 'Usage', value: row => row.usage },
  ],
  onActivate: async (row, screen) => {
    if (await screen.confirm(`Use ${row.id}?`)) await activateAccount(row.id);
  },
});
await out.tui.run({
  view: out.tui.column([
    out.tui.text('Accounts', { height: 1 }),
    out.tui.panel('Saved accounts', table),
    out.tui.text('Tab: focus · Enter: activate · q: quit', { height: 1 }),
  ]),
  keys: { q: screen => screen.close() },
});
await out.dispose();

Create stateful widgets once and reuse them across renders. view can also be a function returning the current layout. Call screen.invalidate() after external updates such as table.setRows(rows) or text.setText(value); keyboard actions and terminal resizing redraw automatically. Changed terminal rows alone are written. Rows keep selection by their unique rowKey, including after refresh. Table options accept selectedKey to select an initial row without changing row order. It must identify an existing initial row; unknown keys throw. Omitting it selects the first row. Initial selection does not invoke onSelect.

Widget factories:

Factory Behavior
text(value, size?), viewer(value, size?) Styled text; viewer adds scrolling
markdown(source, options?, size?) Scrollable themed Markdown
table(options) Typed rows, rich columns, sorting, filtering, selection
row(children, size?), column(children, size?) Horizontal/vertical layout
panel(title, content, size?) Bordered titled panel
tabs([{title, content}], size?) Switchable content; arrows on the tab header
tree(nodes, onSelect?, size?) Expandable nodes with unique IDs and styled labels
input(options) Editable text/password, grapheme-aware cursor, validation
checkbox(label, checked?, size?) Boolean control
button(label, action, size?) Awaited action
form(fields, submit, label?, size?) Validates input fields before submission
progress(label, current, total, size?) Progress bar with setProgress()
logs Bounded scrollable log widget populated by console output

Sizes accept positive integer width (at least 2), height, and flex. Flexible children share remaining space. Terminal sizes that cannot fit the view show an explicit resize message and keep the screen available for resizing or quitting. TUI tables use single-line cells with truncation and keep the column header visible while scrolling. Use static out.table() for multiline cells.

Tab/Shift-Tab moves focus. Tables support arrows, Home/End, PageUp/PageDown, Enter, / to edit a filter, s to cycle sort columns, and S to reverse sorting. Escape clears a filter; Enter keeps it. Tables expose selected, setRows, setFilter and sort(columnKey, descending?). A one-column table also serves as a selection list. Trees use Left/Right to collapse/expand and Enter to activate. Viewers scroll with arrows, Home/End and PageUp/PageDown.

run accepts theme (border, heading, selected, muted), signal, keys, and an awaited onReady(screen) callback. Shortcuts use keys such as q, ctrl+r, or alt+r; plain character shortcuts do not steal input while a text field or table filter is being edited. Ctrl-C closes the screen. The context provides close(), invalidate(), focus(widget), signal, and confirm(message, {confirmLabel?, cancelLabel?}). Confirmations default to Cancel; Left/Right or Tab changes the choice, Enter accepts it, and Escape cancels it.

Actions are awaited; further management keystrokes are ignored during an action. Confirmation dialogs keep accepting input while the action awaits their answer. Closing aborts the context signal and waits for running callbacks to finish, so applications can complete necessary cleanup. A callback must not await disposal of its own console. Errors reject run() after terminal restoration.

The terminal session arbitrates screens and prompts, pauses task redraws, and buffers ordinary output while a screen is open. Use TUI dialogs inside a screen; ordinary prompts are rejected. flush() is unavailable until the screen closes. The latest 1000 buffered log entries are replayed afterward, with an explicit discard count if exceeded; the logs widget retains 500 entries. Raw mode, input listeners and the alternate screen are restored on close, abort, input EOF and callback failure. Caller-owned streams remain open. Applications may pass an AbortSignal to integrate their own process signal handling.

Migration

This package owns the functionality previously in consolecolor, smartinteract and smartcli; it has no runtime dependency on those packages or on smartlog. Use structured colors and output methods, prompts.ask() instead of answer wrappers, and one awaited CLI handler instead of command subscriptions. Logging transports remain the responsibility of smartlog.

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
Typed console output, tables, Markdown and backend interaction.
Readme
346 KiB
Languages
TypeScript 100%