[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

Adds `stream` API (#231)

authored by

Nate Moore and committed by
GitHub
(Feb 4, 2025, 11:30 PM -0600) a38b2bc6 d4444390

+106
+13
.changeset/gentle-jokes-fail.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + --- 4 + 5 + Adds `stream` API which provides the same methods as `log`, but for iterable (even async) message streams. This is particularly useful for AI responses which are dynamically generated by LLMs. 6 + 7 + ```ts 8 + import * as p from '@clack/prompts'; 9 + 10 + await p.stream.step((async function* () { 11 + yield* generateLLMResponse(question); 12 + })()) 13 + ```
+1
examples/basic/package.json
··· 10 10 }, 11 11 "scripts": { 12 12 "start": "jiti ./index.ts", 13 + "stream": "jiti ./stream.ts", 13 14 "spinner": "jiti ./spinner.ts", 14 15 "spinner-ci": "npx cross-env CI=\"true\" jiti ./spinner-ci.ts" 15 16 },
+34
examples/basic/stream.ts
··· 1 + import { setTimeout } from 'node:timers/promises'; 2 + import * as p from '@clack/prompts'; 3 + import color from 'picocolors'; 4 + 5 + async function main() { 6 + console.clear(); 7 + 8 + await setTimeout(1000); 9 + 10 + p.intro(`${color.bgCyan(color.black(' create-app '))}`); 11 + 12 + await p.stream.step((async function* () { 13 + for (const line of lorem) { 14 + for (const word of line.split(' ')) { 15 + yield word; 16 + yield ' '; 17 + await setTimeout(200); 18 + } 19 + yield '\n'; 20 + if (line !== lorem.at(-1)) { 21 + await setTimeout(1000); 22 + } 23 + } 24 + })()) 25 + 26 + p.outro(`Problems? ${color.underline(color.cyan('https://example.com/issues'))}`); 27 + } 28 + 29 + const lorem = [ 30 + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 31 + 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', 32 + ] 33 + 34 + main().catch(console.error);
+16
packages/prompts/README.md
··· 188 188 log.message('Hello, World', { symbol: color.cyan('~') }); 189 189 ``` 190 190 191 + 192 + ### Stream 193 + 194 + When interacting with dynamic LLMs or other streaming message providers, use the `stream` APIs to log messages from an iterable, even an async one. 195 + 196 + ```js 197 + import { stream } from '@clack/prompts'; 198 + 199 + stream.info((function *() { yield 'Info!'; })()); 200 + stream.success((function *() { yield 'Success!'; })()); 201 + stream.step((function *() { yield 'Step!'; })()); 202 + stream.warn((function *() { yield 'Warn!'; })()); 203 + stream.error((function *() { yield 'Error!'; })()); 204 + stream.message((function *() { yield 'Hello'; yield ", World" })(), { symbol: color.cyan('~') }); 205 + ``` 206 + 191 207 [clack-log-prompts](https://github.com/natemoo-re/clack/blob/main/.github/assets/clack-logs.png)
+42
packages/prompts/src/index.ts
··· 675 675 }, 676 676 }; 677 677 678 + const prefix = `${color.gray(S_BAR)} `; 679 + export const stream = { 680 + message: async (iterable: Iterable<string>|AsyncIterable<string>, { symbol = color.gray(S_BAR) }: LogMessageOptions = {}) => { 681 + process.stdout.write(`${color.gray(S_BAR)}\n${symbol} `); 682 + let lineWidth = 3; 683 + for await (let chunk of iterable) { 684 + chunk = chunk.replace(/\n/g, `\n${prefix}`); 685 + if (chunk.includes('\n')) { 686 + lineWidth = 3 + strip(chunk.slice(chunk.lastIndexOf('\n'))).length; 687 + } 688 + const chunkLen = strip(chunk).length; 689 + if ((lineWidth + chunkLen) < process.stdout.columns) { 690 + lineWidth += chunkLen; 691 + process.stdout.write(chunk); 692 + } else { 693 + process.stdout.write(`\n${prefix}${chunk.trimStart()}`); 694 + lineWidth = 3 + strip(chunk.trimStart()).length; 695 + } 696 + } 697 + process.stdout.write('\n'); 698 + }, 699 + info: (iterable: Iterable<string>|AsyncIterable<string>) => { 700 + return stream.message(iterable, { symbol: color.blue(S_INFO) }); 701 + }, 702 + success: (iterable: Iterable<string>|AsyncIterable<string>) => { 703 + return stream.message(iterable, { symbol: color.green(S_SUCCESS) }); 704 + }, 705 + step: (iterable: Iterable<string>|AsyncIterable<string>) => { 706 + return stream.message(iterable, { symbol: color.green(S_STEP_SUBMIT) }); 707 + }, 708 + warn: (iterable: Iterable<string>|AsyncIterable<string>) => { 709 + return stream.message(iterable, { symbol: color.yellow(S_WARN) }); 710 + }, 711 + /** alias for `log.warn()`. */ 712 + warning: (iterable: Iterable<string>|AsyncIterable<string>) => { 713 + return stream.warn(iterable); 714 + }, 715 + error: (iterable: Iterable<string>|AsyncIterable<string>) => { 716 + return stream.message(iterable, { symbol: color.red(S_ERROR) }); 717 + }, 718 + } 719 + 678 720 export const spinner = () => { 679 721 const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0']; 680 722 const delay = unicode ? 80 : 120;