@levu/snap 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.5] - 2026-02-26
9
+
10
+ ### Fixed
11
+ - Added an additional Ghostty/macOS fallback for `Shift+Enter` when emitted as plain `1`, `3`, `~` keypress chars (no modifier metadata).
12
+ - Added submit-time sanitation of residual Ghostty shift-enter tokens (`13~`, related escape variants) so pasted/echoed artifacts become real newlines in multiline prompts.
13
+ - Added regression coverage for plain-char `13~` keypress and residual token sanitation.
14
+
15
+ ## [0.3.4] - 2026-02-26
16
+
17
+ ### Fixed
18
+ - Fixed `picocolors` ESM/CJS interop in multiline and password adapters to prevent runtime errors like `pc.cyan is not a function`.
19
+ - Keeps Ghostty `Shift+Enter` (`13~`) fallback and custom password input fixes from `0.3.3`.
20
+
8
21
  ## [0.3.3] - 2026-02-26
9
22
 
10
23
  ### Fixed
@@ -1,5 +1,6 @@
1
1
  import { createInterface, emitKeypressEvents } from 'node:readline';
2
- import * as pc from 'picocolors';
2
+ import pcModule from 'picocolors';
3
+ const pc = pcModule?.default ?? pcModule;
3
4
  export const createMultilineTextPrompt = () => {
4
5
  return async (opts) => {
5
6
  const { message, initialValue = '', placeholder = '', validate, allowPaste = false, input = process.stdin, output = process.stdout, signal, } = opts;
@@ -97,6 +98,12 @@ export const createMultilineTextPrompt = () => {
97
98
  }
98
99
  return null;
99
100
  };
101
+ const normalizeGhosttyInlineTokens = (raw) => {
102
+ return raw.replace(/(?:\u001b\[13;2u|\[13;2u|\u001b\[27;2;13~|\[27;2;13~|13~|~13)/g, '\n');
103
+ };
104
+ const buildSubmitValue = () => {
105
+ return normalizeGhosttyInlineTokens(lines.concat(getLiveLine()).join('\n'));
106
+ };
100
107
  const insertNewline = () => {
101
108
  lines.push(getLiveLine());
102
109
  currentLine = '';
@@ -170,7 +177,7 @@ export const createMultilineTextPrompt = () => {
170
177
  const now = Date.now();
171
178
  // Check for double Enter to submit
172
179
  if (line === '' && now - lastEnterTime < DOUBLE_ENTER_TIMEOUT) {
173
- submit(lines.concat(getLiveLine()).join('\n'));
180
+ submit(buildSubmitValue());
174
181
  return;
175
182
  }
176
183
  lastEnterTime = now;
@@ -209,6 +216,20 @@ export const createMultilineTextPrompt = () => {
209
216
  keypressListener = async (str, key) => {
210
217
  if (cancelled)
211
218
  return;
219
+ // Some terminals emit Shift+Enter as literal chars "13~" with no key metadata.
220
+ // Readline may already have appended those chars into rl.line by the time we run.
221
+ if (!key?.ctrl && !key?.meta && key?.name !== 'enter' && key?.name !== 'return') {
222
+ const strippedLive = stripGhosttyShiftEnterSuffix(String(rl.line ?? ''));
223
+ if (strippedLive !== null) {
224
+ lines.push(strippedLive);
225
+ currentLine = '';
226
+ rl.line = '';
227
+ output.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
228
+ output.write(`${pc.dim('> ')}${strippedLive}`);
229
+ output.write(`\n${pc.dim('> ')}`);
230
+ return;
231
+ }
232
+ }
212
233
  // Detect Ctrl+V or Cmd+V for paste
213
234
  if ((key.ctrl && key.name === 'v') || (key.meta && key.name === 'v')) {
214
235
  const pasted = await handlePaste();
@@ -240,7 +261,7 @@ export const createMultilineTextPrompt = () => {
240
261
  insertNewline();
241
262
  return;
242
263
  }
243
- submit(lines.concat(getLiveLine()).join('\n'));
264
+ submit(buildSubmitValue());
244
265
  }
245
266
  else if (key.name === 'escape') {
246
267
  doCancel();
@@ -1,7 +1,8 @@
1
1
  import { password as clackPassword } from '@clack/prompts';
2
2
  import { emitKeypressEvents } from 'node:readline';
3
- import * as pc from 'picocolors';
3
+ import pcModule from 'picocolors';
4
4
  import { PromptCancelledError, unwrapClackResult } from './cancel.js';
5
+ const pc = pcModule?.default ?? pcModule;
5
6
  export const runPasswordPrompt = async (input) => {
6
7
  const inStream = (input.input ?? process.stdin);
7
8
  const outStream = (input.output ?? process.stdout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levu/snap",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "snap": "./dist/cli-entry.js"