Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
export * from './lib/key.mjs';
export { usePrefix } from './lib/use-prefix.mjs';
export { useState } from './lib/use-state.mjs';
export { useEffect } from './lib/use-effect.mjs';
export { useMemo } from './lib/use-memo.mjs';
export { useRef } from './lib/use-ref.mjs';
export { useKeypress } from './lib/use-keypress.mjs';
export { usePagination } from './lib/pagination/use-pagination.mjs';
export { createPrompt, } from './lib/create-prompt.mjs';
export { Separator } from './lib/Separator.mjs';
+18
View File
@@ -0,0 +1,18 @@
import chalk from 'chalk';
import figures from 'figures';
/**
* Separator object
* Used to space/separate choices group
*/
export class Separator {
separator = chalk.dim(new Array(15).join(figures.line));
type = 'separator';
constructor(separator) {
if (separator) {
this.separator = separator;
}
}
static isSeparator(choice) {
return Boolean(choice && choice.type === 'separator');
}
}
+98
View File
@@ -0,0 +1,98 @@
import * as readline from 'node:readline';
import { CancelablePromise } from '@inquirer/type';
import MuteStream from 'mute-stream';
import { onExit as onSignalExit } from 'signal-exit';
import ScreenManager from './screen-manager.mjs';
import { withHooks, effectScheduler } from './hook-engine.mjs';
// Take an AsyncPromptConfig and resolves all it's values.
async function getPromptConfig(config) {
const message = typeof config.message === 'function' ? config.message() : config.message;
return {
...config,
message: await message,
};
}
export function createPrompt(view) {
const prompt = (config, context) => {
// Default `input` to stdin
const input = context?.input ?? process.stdin;
// Add mute capabilities to the output
const output = new MuteStream();
output.pipe(context?.output ?? process.stdout);
const rl = readline.createInterface({
terminal: true,
input,
output,
});
const screen = new ScreenManager(rl);
let cancel = () => { };
const answer = new CancelablePromise((resolve, reject) => {
withHooks(rl, (store) => {
function checkCursorPos() {
screen.checkCursorPos();
}
const removeExitListener = onSignalExit((code, signal) => {
onExit();
reject(new Error(`User force closed the prompt with ${code} ${signal}`));
});
function onExit() {
try {
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
}
catch (err) {
reject(err);
}
if (context?.clearPromptOnDone) {
screen.clean();
}
else {
screen.clearContent();
}
screen.done();
removeExitListener();
store.rl.input.removeListener('keypress', checkCursorPos);
}
cancel = () => {
onExit();
reject(new Error('Prompt was canceled'));
};
function done(value) {
// Delay execution to let time to the hookCleanup functions to registers.
setImmediate(() => {
onExit();
// Finally we resolve our promise
resolve(value);
});
}
function workLoop(resolvedConfig) {
store.index = 0;
store.handleChange = () => workLoop(resolvedConfig);
try {
const nextView = view(resolvedConfig, done);
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
effectScheduler.run();
}
catch (err) {
onExit();
reject(err);
}
}
// TODO: we should display a loader while we get the default options.
getPromptConfig(config).then((resolvedConfig) => {
workLoop(resolvedConfig);
// Re-renders only happen when the state change; but the readline cursor could change position
// and that also requires a re-render (and a manual one because we mute the streams).
// We set the listener after the initial workLoop to avoid a double render if render triggered
// by a state change sets the cursor to the right position.
store.rl.input.on('keypress', checkCursorPos);
}, reject);
});
});
answer.cancel = cancel;
return answer;
};
return prompt;
}
+93
View File
@@ -0,0 +1,93 @@
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
const hookStorage = new AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
export function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
cb(store);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new Error('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
export function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
export function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return AsyncResource.bind(wrapped);
}
export function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
export function handleChange() {
getStore().handleChange();
}
export const effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new Error('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
};
+18
View File
@@ -0,0 +1,18 @@
export const isUpKey = (key) =>
// The up key
key.name === 'up' ||
// Vim keybinding
key.name === 'k' ||
// Emacs keybinding
(key.ctrl && key.name === 'p');
export const isDownKey = (key) =>
// The down key
key.name === 'down' ||
// Vim keybinding
key.name === 'j' ||
// Emacs keybinding
(key.ctrl && key.name === 'n');
export const isSpaceKey = (key) => key.name === 'space';
export const isBackspaceKey = (key) => key.name === 'backspace';
export const isNumberKey = (key) => '123456789'.includes(key.name);
export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
+59
View File
@@ -0,0 +1,59 @@
import { breakLines } from '../utils.mjs';
function split(content, width) {
return breakLines(content, width).split('\n');
}
/**
* Rotates an array of items by an integer number of positions.
* @param {number} count The number of positions to rotate by
* @param {T[]} items The items to rotate
*/
function rotate(count, items) {
const max = items.length;
const offset = ((count % max) + max) % max;
return items.slice(offset).concat(items.slice(0, offset));
}
/**
* Renders a page of items as lines that fit within the given width ensuring
* that the number of lines is not greater than the page size, and the active
* item renders at the provided position, while prioritizing that as many lines
* of the active item get rendered as possible.
*/
export function lines({ items, width, renderItem, active, position: requested, pageSize, }) {
const layouts = items.map((item, index) => ({
item,
index,
isActive: index === active,
}));
const layoutsInPage = rotate(active - requested, layouts).slice(0, pageSize);
const renderItemAt = (index) => split(renderItem(layoutsInPage[index]), width);
// Create a blank array of lines for the page
const pageBuffer = new Array(pageSize);
// Render the active item to decide the position
const activeItem = renderItemAt(requested).slice(0, pageSize);
const position = requested + activeItem.length <= pageSize ? requested : pageSize - activeItem.length;
// Add the lines of the active item into the page
pageBuffer.splice(position, activeItem.length, ...activeItem);
// Fill the page under the active item
let bufferPointer = position + activeItem.length;
let layoutPointer = requested + 1;
while (bufferPointer < pageSize && layoutPointer < layoutsInPage.length) {
for (const line of renderItemAt(layoutPointer)) {
pageBuffer[bufferPointer++] = line;
if (bufferPointer >= pageSize)
break;
}
layoutPointer++;
}
// Fill the page over the active item
bufferPointer = position - 1;
layoutPointer = requested - 1;
while (bufferPointer >= 0 && layoutPointer >= 0) {
for (const line of renderItemAt(layoutPointer).reverse()) {
pageBuffer[bufferPointer--] = line;
if (bufferPointer < 0)
break;
}
layoutPointer--;
}
return pageBuffer.filter((line) => typeof line === 'string');
}
+27
View File
@@ -0,0 +1,27 @@
/**
* Creates the next position for the active item considering a finite list of
* items to be rendered on a page.
*/
export function finite({ active, pageSize, total, }) {
const middle = Math.floor(pageSize / 2);
if (total <= pageSize || active < middle)
return active;
if (active >= total - middle)
return active + pageSize - total;
return middle;
}
/**
* Creates the next position for the active item considering an infinitely
* looping list of items to be rendered on the page.
*/
export function infinite({ active, lastActive, total, pageSize, pointer, }) {
if (total <= pageSize)
return active;
// Move the position only when the user moves down, and when the
// navigation fits within a single page
if (lastActive < active && active - lastActive < pageSize) {
// Limit it to the middle of the list
return Math.min(Math.floor(pageSize / 2), pointer + active - lastActive);
}
return pointer;
}
+35
View File
@@ -0,0 +1,35 @@
import chalk from 'chalk';
import { useRef } from '../use-ref.mjs';
import { readlineWidth } from '../utils.mjs';
import { lines } from './lines.mjs';
import { finite, infinite } from './position.mjs';
export function usePagination({ items, active, renderItem, pageSize = 7, loop = true, }) {
const state = useRef({ position: 0, lastActive: 0 });
const position = loop
? infinite({
active,
lastActive: state.current.lastActive,
total: items.length,
pageSize,
pointer: state.current.position,
})
: finite({
active,
total: items.length,
pageSize,
});
state.current.position = position;
state.current.lastActive = active;
const visibleLines = lines({
items,
width: readlineWidth(),
renderItem,
active,
position,
pageSize,
}).join('\n');
if (items.length > pageSize) {
return `${visibleLines}\n${chalk.dim('(Use arrow keys to reveal more choices)')}`;
}
return visibleLines;
}
+1
View File
@@ -0,0 +1 @@
export {};
+103
View File
@@ -0,0 +1,103 @@
import stripAnsi from 'strip-ansi';
import ansiEscapes from 'ansi-escapes';
import { breakLines, readlineWidth } from './utils.mjs';
const height = (content) => content.split('\n').length;
const lastLine = (content) => content.split('\n').pop() ?? '';
export default class ScreenManager {
rl;
// These variables are keeping information to allow correct prompt re-rendering
height = 0;
extraLinesUnderPrompt = 0;
cursorPos;
constructor(rl) {
this.rl = rl;
this.rl = rl;
this.cursorPos = rl.getCursorPos();
}
render(content, bottomContent = '') {
this.clean();
this.rl.output.unmute();
/**
* Write message to screen and setPrompt to control backspace
*/
const promptLine = lastLine(content);
const rawPromptLine = stripAnsi(promptLine);
// Remove the rl.line from our prompt. We can't rely on the content of
// rl.line (mainly because of the password prompt), so just rely on it's
// length.
let prompt = rawPromptLine;
if (this.rl.line.length) {
prompt = prompt.slice(0, -this.rl.line.length);
}
this.rl.setPrompt(prompt);
// SetPrompt will change cursor position, now we can get correct value
this.cursorPos = this.rl.getCursorPos();
const width = readlineWidth();
content = breakLines(content, width);
bottomContent = breakLines(bottomContent, width);
// Manually insert an extra line if we're at the end of the line.
// This prevent the cursor from appearing at the beginning of the
// current line.
if (rawPromptLine.length % width === 0) {
content += '\n';
}
let output = content + (bottomContent ? '\n' + bottomContent : '');
/**
* Re-adjust the cursor at the correct position.
*/
// We need to consider parts of the prompt under the cursor as part of the bottom
// content in order to correctly cleanup and re-render.
const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows;
const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0);
// Return cursor to the input position (on top of the bottomContent)
if (bottomContentHeight > 0)
output += ansiEscapes.cursorUp(bottomContentHeight);
// Return cursor to the initial left offset.
output += ansiEscapes.cursorTo(this.cursorPos.cols);
/**
* Set up state for next re-rendering
*/
this.extraLinesUnderPrompt = bottomContentHeight;
this.height = height(output);
this.rl.output.write(output);
this.rl.output.mute();
}
checkCursorPos() {
const cursorPos = this.rl.getCursorPos();
if (cursorPos.cols !== this.cursorPos.cols) {
this.rl.output.unmute();
this.rl.output.write(ansiEscapes.cursorTo(cursorPos.cols));
this.rl.output.mute();
this.cursorPos = cursorPos;
}
}
clean() {
this.rl.output.unmute();
this.rl.output.write([
this.extraLinesUnderPrompt > 0
? ansiEscapes.cursorDown(this.extraLinesUnderPrompt)
: '',
ansiEscapes.eraseLines(this.height),
].join(''));
this.extraLinesUnderPrompt = 0;
this.rl.output.mute();
}
clearContent() {
this.rl.output.unmute();
// Reset the cursor at the end of the previously displayed content
this.rl.output.write([
this.extraLinesUnderPrompt > 0
? ansiEscapes.cursorDown(this.extraLinesUnderPrompt)
: '',
'\n',
].join(''));
this.rl.output.mute();
}
done() {
this.rl.setPrompt('');
this.rl.output.unmute();
this.rl.output.write(ansiEscapes.cursorShow);
this.rl.output.end();
this.rl.close();
}
}
+11
View File
@@ -0,0 +1,11 @@
import { withPointer, effectScheduler } from './hook-engine.mjs';
export function useEffect(cb, depArray) {
withPointer((pointer) => {
const oldDeps = pointer.get();
const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
if (hasChanged) {
effectScheduler.queue(cb);
}
pointer.set(depArray);
});
}
+16
View File
@@ -0,0 +1,16 @@
import { useRef } from './use-ref.mjs';
import { useEffect } from './use-effect.mjs';
import { withUpdates } from './hook-engine.mjs';
export function useKeypress(userHandler) {
const signal = useRef(userHandler);
signal.current = userHandler;
useEffect((rl) => {
const handler = withUpdates((_input, event) => {
signal.current(event, rl);
});
rl.input.on('keypress', handler);
return () => {
rl.input.removeListener('keypress', handler);
};
}, []);
}
+14
View File
@@ -0,0 +1,14 @@
import { withPointer } from './hook-engine.mjs';
export function useMemo(fn, dependencies) {
return withPointer((pointer) => {
const prev = pointer.get();
if (!prev ||
prev.dependencies.length !== dependencies.length ||
prev.dependencies.some((dep, i) => dep !== dependencies[i])) {
const value = fn();
pointer.set({ value, dependencies });
return value;
}
return prev.value;
});
}
+21
View File
@@ -0,0 +1,21 @@
import chalk from 'chalk';
import spinners from 'cli-spinners';
import { useState } from './use-state.mjs';
import { useEffect } from './use-effect.mjs';
const spinner = spinners.dots;
export function usePrefix(isLoading = false) {
const [tick, setTick] = useState(0);
useEffect(() => {
if (isLoading) {
const timeout = setTimeout(() => {
setTick(tick + 1);
}, spinner.interval);
return () => clearTimeout(timeout);
}
}, [isLoading, tick]);
if (isLoading) {
const frame = tick % spinner.frames.length;
return chalk.yellow(spinner.frames[frame]);
}
return chalk.green('?');
}
+4
View File
@@ -0,0 +1,4 @@
import { useState } from './use-state.mjs';
export function useRef(val) {
return useState({ current: val })[0];
}
+19
View File
@@ -0,0 +1,19 @@
import { withPointer, handleChange } from './hook-engine.mjs';
export function useState(defaultValue) {
return withPointer((pointer) => {
const setFn = (newValue) => {
// Noop if the value is still the same.
if (pointer.get() !== newValue) {
pointer.set(newValue);
// Trigger re-render
handleChange();
}
};
if (pointer.initialized) {
return [pointer.get(), setFn];
}
const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
pointer.set(value);
return [value, setFn];
});
}
+25
View File
@@ -0,0 +1,25 @@
import cliWidth from 'cli-width';
import wrapAnsi from 'wrap-ansi';
import { readline } from './hook-engine.mjs';
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export function breakLines(content, width) {
return content
.split('\n')
.flatMap((line) => wrapAnsi(line, width, { trim: false, hard: true })
.split('\n')
.map((str) => str.trimEnd()))
.join('\n');
}
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export function readlineWidth() {
return cliWidth({ defaultWidth: 80, output: readline().output });
}
+11
View File
@@ -0,0 +1,11 @@
export * from './lib/key.mjs';
export { usePrefix } from './lib/use-prefix.mjs';
export { useState } from './lib/use-state.mjs';
export { useEffect } from './lib/use-effect.mjs';
export { useMemo } from './lib/use-memo.mjs';
export { useRef } from './lib/use-ref.mjs';
export { useKeypress } from './lib/use-keypress.mjs';
export { usePagination } from './lib/pagination/use-pagination.mjs';
export { createPrompt, type PromptConfig, type AsyncPromptConfig, } from './lib/create-prompt.mjs';
export { Separator } from './lib/Separator.mjs';
export { type InquirerReadline } from './lib/read-line.type.mjs';
+10
View File
@@ -0,0 +1,10 @@
/**
* Separator object
* Used to space/separate choices group
*/
export declare class Separator {
readonly separator: string;
readonly type = "separator";
constructor(separator?: string);
static isSeparator(choice: undefined | Separator | Record<string, unknown>): choice is Separator;
}
+11
View File
@@ -0,0 +1,11 @@
import { type Prompt, type Prettify } from '@inquirer/type';
export type AsyncPromptConfig = {
message: string | Promise<string> | (() => Promise<string>);
};
export type PromptConfig<Config> = Prettify<AsyncPromptConfig & Config>;
type ResolvedPromptConfig = {
message: string;
};
type ViewFunction<Value, Config> = (config: Prettify<Config & ResolvedPromptConfig>, done: (value: Value) => void) => string | [string, string | undefined];
export declare function createPrompt<Value, Config extends AsyncPromptConfig>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
export {};
+30
View File
@@ -0,0 +1,30 @@
import type { InquirerReadline } from './read-line.type.mjs';
type HookStore = {
rl: InquirerReadline;
hooks: any[];
hooksCleanup: Array<void | (() => void)>;
hooksEffect: Array<() => void>;
index: number;
handleChange: () => void;
};
export declare function withHooks(rl: InquirerReadline, cb: (store: HookStore) => void): void;
export declare function readline(): InquirerReadline;
export declare function withUpdates<T extends (...args: any) => any>(fn: T): (...args: Parameters<T>) => ReturnType<T>;
type SetPointer<Value> = {
get(): Value;
set(value: Value): void;
initialized: true;
};
type UnsetPointer<Value> = {
get(): void;
set(value: Value): void;
initialized: false;
};
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
export declare function handleChange(): void;
export declare const effectScheduler: {
queue(cb: (readline: InquirerReadline) => void): void;
run(): void;
};
export {};
+10
View File
@@ -0,0 +1,10 @@
export type KeypressEvent = {
name: string;
ctrl: boolean;
};
export declare const isUpKey: (key: KeypressEvent) => boolean;
export declare const isDownKey: (key: KeypressEvent) => boolean;
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
export declare const isNumberKey: (key: KeypressEvent) => boolean;
export declare const isEnterKey: (key: KeypressEvent) => boolean;
+26
View File
@@ -0,0 +1,26 @@
import { type Prettify } from '@inquirer/type';
/** Represents an item that's part of a layout, about to be rendered */
export type Layout<T> = {
item: T;
index: number;
isActive: boolean;
};
/**
* Renders a page of items as lines that fit within the given width ensuring
* that the number of lines is not greater than the page size, and the active
* item renders at the provided position, while prioritizing that as many lines
* of the active item get rendered as possible.
*/
export declare function lines<T>({ items, width, renderItem, active, position: requested, pageSize, }: {
items: readonly T[];
/** The width of a rendered line in characters. */
width: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<Layout<T>>) => string;
/** The index of the active item in the list of items. */
active: number;
/** The position on the page at which to render the active item. */
position: number;
/** The number of lines to render per page. */
pageSize: number;
}): string[];
@@ -0,0 +1,20 @@
/**
* Creates the next position for the active item considering a finite list of
* items to be rendered on a page.
*/
export declare function finite({ active, pageSize, total, }: {
active: number;
pageSize: number;
total: number;
}): number;
/**
* Creates the next position for the active item considering an infinitely
* looping list of items to be rendered on the page.
*/
export declare function infinite({ active, lastActive, total, pageSize, pointer, }: {
active: number;
lastActive: number;
total: number;
pageSize: number;
pointer: number;
}): number;
@@ -0,0 +1,13 @@
import { type Prettify } from '@inquirer/type';
import { type Layout } from './lines.mjs';
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
items: readonly T[];
/** The index of the active item. */
active: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<Layout<T>>) => string;
/** The size of the page. `7` if unspecified. */
pageSize?: number;
/** Allows creating an infinitely looping list. `true` if unspecified. */
loop?: boolean;
}): string;
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="node" resolution-mode="require"/>
/// <reference types="node" resolution-mode="require"/>
import * as readline from 'node:readline';
import MuteStream from 'mute-stream';
export type InquirerReadline = readline.ReadLine & {
output: MuteStream;
input: NodeJS.ReadableStream;
clearLine: (dir: 0 | 1 | -1) => void;
};
+13
View File
@@ -0,0 +1,13 @@
import type { InquirerReadline } from './read-line.type.mjs';
export default class ScreenManager {
private readonly rl;
private height;
private extraLinesUnderPrompt;
private cursorPos;
constructor(rl: InquirerReadline);
render(content: string, bottomContent?: string): void;
checkCursorPos(): void;
clean(): void;
clearContent(): void;
done(): void;
}
+2
View File
@@ -0,0 +1,2 @@
import type { InquirerReadline } from './read-line.type.mjs';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: unknown[]): void;
+3
View File
@@ -0,0 +1,3 @@
import { type InquirerReadline } from './read-line.type.mjs';
import { type KeypressEvent } from './key.mjs';
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void): void;
+1
View File
@@ -0,0 +1 @@
export declare function useMemo<Value>(fn: () => Value, dependencies: unknown[]): Value;
+1
View File
@@ -0,0 +1 @@
export declare function usePrefix(isLoading?: boolean): string;
+3
View File
@@ -0,0 +1,3 @@
export declare function useRef<Value>(val: Value): {
current: Value;
};
+3
View File
@@ -0,0 +1,3 @@
type NotFunction<T> = T extends Function ? never : T;
export declare function useState<Value>(defaultValue: NotFunction<Value> | (() => Value)): [Value, (newValue: Value) => void];
export {};
+13
View File
@@ -0,0 +1,13 @@
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export declare function breakLines(content: string, width: number): string;
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export declare function readlineWidth(): number;