shellwise 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellwise",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Smart command history with inline auto-suggest and fuzzy search for your terminal",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/search.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { search, type ScoredResult } from "../search";
2
- import { enableRawMode, disableRawMode, parseKeypress } from "../tui/input";
2
+ import { enableRawMode, disableRawMode, parseKeypress, readKeypress, closeTtyInput } from "../tui/input";
3
3
  import {
4
4
  write,
5
5
  clearLine,
@@ -14,6 +14,7 @@ import {
14
14
  import { renderSearchBox, getSearchBoxCursorCol } from "../tui/components/search-box";
15
15
  import { renderResultList } from "../tui/components/result-list";
16
16
  import { renderStatusBar } from "../tui/components/status-bar";
17
+ import { writeSync } from "fs";
17
18
 
18
19
  interface SearchState {
19
20
  query: string;
@@ -24,7 +25,7 @@ interface SearchState {
24
25
  renderedLines: number;
25
26
  }
26
27
 
27
- export async function runSearch(initialQuery: string = ""): Promise<void> {
28
+ export function runSearch(initialQuery: string = ""): void {
28
29
  const cwd = process.env.PWD || process.cwd();
29
30
 
30
31
  const state: SearchState = {
@@ -52,6 +53,7 @@ export async function runSearch(initialQuery: string = ""): Promise<void> {
52
53
  write(showCursor());
53
54
  write(moveCursorToColumn(1));
54
55
  disableRawMode();
56
+ closeTtyInput();
55
57
  closeTty();
56
58
  };
57
59
 
@@ -66,14 +68,16 @@ export async function runSearch(initialQuery: string = ""): Promise<void> {
66
68
  // Render initial frame
67
69
  render(state);
68
70
 
69
- // Input loop
71
+ // Input loop — sync reads from /dev/tty
70
72
  try {
71
- for await (const chunk of readStdin()) {
73
+ while (true) {
74
+ const chunk = readKeypress();
75
+ if (chunk.length === 0) continue;
76
+
72
77
  const key = parseKeypress(chunk);
73
78
 
74
79
  if (key.type === "special" && key.key === "escape") {
75
80
  cleanup();
76
- // Output nothing = cancel
77
81
  return;
78
82
  }
79
83
 
@@ -86,8 +90,8 @@ export async function runSearch(initialQuery: string = ""): Promise<void> {
86
90
  const selected = state.results[state.selectedIndex];
87
91
  cleanup();
88
92
  if (selected) {
89
- // Output to stdout for shell to capture
90
- process.stdout.write(selected.command);
93
+ // Output to stdout (fd 1) for shell to capture
94
+ writeSync(1, selected.command);
91
95
  }
92
96
  return;
93
97
  }
@@ -264,30 +268,3 @@ function render(state: SearchState): void {
264
268
  );
265
269
  write(showCursor());
266
270
  }
267
-
268
- async function* readStdin(): AsyncGenerator<Buffer> {
269
- const stdin = process.stdin;
270
- stdin.resume();
271
-
272
- const buffers: Buffer[] = [];
273
- let resolve: (() => void) | null = null;
274
-
275
- stdin.on("data", (data: Buffer) => {
276
- buffers.push(data);
277
- if (resolve) {
278
- resolve();
279
- resolve = null;
280
- }
281
- });
282
-
283
- while (true) {
284
- if (buffers.length === 0) {
285
- await new Promise<void>((r) => {
286
- resolve = r;
287
- });
288
- }
289
- while (buffers.length > 0) {
290
- yield buffers.shift()!;
291
- }
292
- }
293
- }
package/src/index.ts CHANGED
@@ -57,7 +57,7 @@ async function main(): Promise<void> {
57
57
  switch (command) {
58
58
  case "search": {
59
59
  const flags = parseFlags(args.slice(1));
60
- await runSearch(flags.query || "");
60
+ runSearch(flags.query || "");
61
61
  break;
62
62
  }
63
63
 
package/src/tui/input.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { openSync, readSync, closeSync } from "fs";
2
+ import { execSync } from "child_process";
3
+
1
4
  export type KeyEvent =
2
5
  | { type: "char"; char: string }
3
6
  | { type: "special"; key: SpecialKey }
@@ -53,19 +56,38 @@ export function parseKeypress(data: Buffer): KeyEvent {
53
56
  return { type: "char", char: str };
54
57
  }
55
58
 
56
- let originalRawMode: boolean | undefined;
59
+ // Use /dev/tty directly — avoids Bun kqueue bug with process.stdin
60
+ // inside $() capture from shell integration
61
+ let ttyReadFd: number | null = null;
57
62
 
58
- export function enableRawMode(): void {
59
- if (process.stdin.isTTY) {
60
- originalRawMode = process.stdin.isRaw;
61
- process.stdin.setRawMode(true);
62
- process.stdin.resume();
63
+ function getTtyReadFd(): number {
64
+ if (ttyReadFd === null) {
65
+ ttyReadFd = openSync("/dev/tty", "r");
63
66
  }
67
+ return ttyReadFd;
68
+ }
69
+
70
+ export function readKeypress(): Buffer {
71
+ const buf = Buffer.alloc(16);
72
+ const bytesRead = readSync(getTtyReadFd(), buf);
73
+ return buf.subarray(0, bytesRead);
74
+ }
75
+
76
+ export function enableRawMode(): void {
77
+ try {
78
+ execSync("stty raw -echo </dev/tty", { stdio: "ignore" });
79
+ } catch {}
64
80
  }
65
81
 
66
82
  export function disableRawMode(): void {
67
- if (process.stdin.isTTY) {
68
- process.stdin.setRawMode(originalRawMode ?? false);
69
- process.stdin.pause();
83
+ try {
84
+ execSync("stty -raw echo </dev/tty", { stdio: "ignore" });
85
+ } catch {}
86
+ }
87
+
88
+ export function closeTtyInput(): void {
89
+ if (ttyReadFd !== null) {
90
+ try { closeSync(ttyReadFd); } catch {}
91
+ ttyReadFd = null;
70
92
  }
71
93
  }