[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.

feat: add `withGuide` support to password prompt (#451)

authored by

James Garbutt and committed by
GitHub
(Feb 4, 2026, 9:20 AM UTC) f9b99531 76550d6f

+122 -10
+5
.changeset/rotten-jokes-read.md
··· 1 + --- 2 + "@clack/prompts": patch 3 + --- 4 + 5 + Add `withGuide` support to password prompt.
+16 -10
packages/prompts/src/password.ts
··· 1 - import { PasswordPrompt } from '@clack/core'; 1 + import { PasswordPrompt, settings } from '@clack/core'; 2 2 import color from 'picocolors'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; 4 4 ··· 16 16 input: opts.input, 17 17 output: opts.output, 18 18 render() { 19 - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 19 + const hasGuide = (opts.withGuide ?? settings.withGuide) !== false; 20 + const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 20 21 const userInput = this.userInputWithCursor; 21 22 const masked = this.masked; 22 23 ··· 26 27 if (opts.clearOnError) { 27 28 this.clear(); 28 29 } 29 - return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( 30 - S_BAR_END 31 - )} ${color.yellow(this.error)}\n`; 30 + const errorPrefix = hasGuide ? `${color.yellow(S_BAR)}` : ''; 31 + const errorPrefixEnd = hasGuide ? color.yellow(S_BAR_END) : ''; 32 + return `${title.trim()}\n${errorPrefix}${maskedText}\n${errorPrefixEnd} ${color.yellow(this.error)}\n`; 32 33 } 33 34 case 'submit': { 34 35 const maskedText = masked ? ` ${color.dim(masked)}` : ''; 35 - return `${title}${color.gray(S_BAR)}${maskedText}`; 36 + const submitPrefix = hasGuide ? color.gray(S_BAR) : ''; 37 + return `${title}${submitPrefix}${maskedText}`; 36 38 } 37 39 case 'cancel': { 38 40 const maskedText = masked ? ` ${color.strikethrough(color.dim(masked))}` : ''; 39 - return `${title}${color.gray(S_BAR)}${maskedText}${ 40 - masked ? `\n${color.gray(S_BAR)}` : '' 41 + const cancelPrefix = hasGuide ? color.gray(S_BAR) : ''; 42 + return `${title}${cancelPrefix}${maskedText}${ 43 + masked && hasGuide ? `\n${color.gray(S_BAR)}` : '' 41 44 }`; 42 45 } 43 - default: 44 - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; 46 + default: { 47 + const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 48 + const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 49 + return `${title}${defaultPrefix}${userInput}\n${defaultPrefixEnd}\n`; 50 + } 45 51 } 46 52 }, 47 53 }).prompt() as Promise<string | symbol>;
+68
packages/prompts/test/__snapshots__/password.test.ts.snap
··· 57 57 ] 58 58 `; 59 59 60 + exports[`password (isCI = false) > global withGuide: false removes guide 1`] = ` 61 + [ 62 + "<cursor.hide>", 63 + "◆ foo 64 + _ 65 + 66 + ", 67 + "<cursor.backward count=999><cursor.up count=3>", 68 + "<erase.down>", 69 + "◇ foo 70 + ", 71 + " 72 + ", 73 + "<cursor.show>", 74 + ] 75 + `; 76 + 60 77 exports[`password (isCI = false) > renders and clears validation errors 1`] = ` 61 78 [ 62 79 "<cursor.hide>", ··· 191 208 "<erase.down>", 192 209 "◇ foo 193 210 │", 211 + " 212 + ", 213 + "<cursor.show>", 214 + ] 215 + `; 216 + 217 + exports[`password (isCI = false) > withGuide: false removes guide 1`] = ` 218 + [ 219 + "<cursor.hide>", 220 + "◆ foo 221 + _ 222 + 223 + ", 224 + "<cursor.backward count=999><cursor.up count=3>", 225 + "<erase.down>", 226 + "◇ foo 227 + ", 194 228 " 195 229 ", 196 230 "<cursor.show>", ··· 254 288 ] 255 289 `; 256 290 291 + exports[`password (isCI = true) > global withGuide: false removes guide 1`] = ` 292 + [ 293 + "<cursor.hide>", 294 + "◆ foo 295 + _ 296 + 297 + ", 298 + "<cursor.backward count=999><cursor.up count=3>", 299 + "<erase.down>", 300 + "◇ foo 301 + ", 302 + " 303 + ", 304 + "<cursor.show>", 305 + ] 306 + `; 307 + 257 308 exports[`password (isCI = true) > renders and clears validation errors 1`] = ` 258 309 [ 259 310 "<cursor.hide>", ··· 393 444 "<cursor.show>", 394 445 ] 395 446 `; 447 + 448 + exports[`password (isCI = true) > withGuide: false removes guide 1`] = ` 449 + [ 450 + "<cursor.hide>", 451 + "◆ foo 452 + _ 453 + 454 + ", 455 + "<cursor.backward count=999><cursor.up count=3>", 456 + "<erase.down>", 457 + "◇ foo 458 + ", 459 + " 460 + ", 461 + "<cursor.show>", 462 + ] 463 + `;
+33
packages/prompts/test/password.test.ts
··· 1 + import { updateSettings } from '@clack/core'; 1 2 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 2 3 import * as prompts from '../src/index.js'; 3 4 import { MockReadable, MockWritable } from './test-utils.js'; ··· 23 24 24 25 afterEach(() => { 25 26 vi.restoreAllMocks(); 27 + updateSettings({ withGuide: true }); 26 28 }); 27 29 28 30 test('renders message', async () => { ··· 147 149 const value = await result; 148 150 149 151 expect(value).toBe('yz'); 152 + expect(output.buffer).toMatchSnapshot(); 153 + }); 154 + 155 + test('withGuide: false removes guide', async () => { 156 + const result = prompts.password({ 157 + message: 'foo', 158 + withGuide: false, 159 + input, 160 + output, 161 + }); 162 + 163 + input.emit('keypress', '', { name: 'return' }); 164 + 165 + await result; 166 + 167 + expect(output.buffer).toMatchSnapshot(); 168 + }); 169 + 170 + test('global withGuide: false removes guide', async () => { 171 + updateSettings({ withGuide: false }); 172 + 173 + const result = prompts.password({ 174 + message: 'foo', 175 + input, 176 + output, 177 + }); 178 + 179 + input.emit('keypress', '', { name: 'return' }); 180 + 181 + await result; 182 + 150 183 expect(output.buffer).toMatchSnapshot(); 151 184 }); 152 185 });