sshya 0.2.3 → 0.2.4

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
@@ -2,7 +2,7 @@
2
2
  "name": "sshya",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.2.3",
5
+ "version": "0.2.4",
6
6
  "description": "CLI for managing SSH connections with aliases, interactive prompts, and fzf integration",
7
7
  "devDependencies": {
8
8
  "@types/bun": "latest",
@@ -1,9 +1,12 @@
1
1
  import chalk from "chalk";
2
2
  import inquirer from "inquirer";
3
3
  import { addConnection, connectionSchema } from "../database";
4
+ import { enableEscapeExit } from "../helpers/escExit";
4
5
  import { testConnectionPrompt } from "./test";
5
6
 
6
7
  export async function addConnectionPrompt() {
8
+ const cleanup = enableEscapeExit();
9
+ try {
7
10
  const answers = await inquirer.prompt([
8
11
  {
9
12
  type: 'input',
@@ -64,4 +67,7 @@ export async function addConnectionPrompt() {
64
67
  } catch (err: any) {
65
68
  console.error(chalk.red(err.message));
66
69
  }
70
+ } finally {
71
+ cleanup();
72
+ }
67
73
  }
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import fs from 'fs';
3
3
  import inquirer from 'inquirer';
4
4
  import { addConnection, connectionSchema, getConnectionByAlias, updateConnection } from '../database';
5
+ import { enableEscapeExit } from '../helpers/escExit';
5
6
 
6
7
  export const importConnectionsPrompt = async (file: string) => {
7
8
  if (!fs.existsSync(file)) {
@@ -24,6 +25,8 @@ export const importConnectionsPrompt = async (file: string) => {
24
25
 
25
26
  console.log(`Found ${connectionsToImport.length} connections to import.`);
26
27
 
28
+ const cleanup = enableEscapeExit();
29
+ try {
27
30
  for (const conn of connectionsToImport) {
28
31
  const validation = connectionSchema.safeParse(conn);
29
32
  if (!validation.success) {
@@ -77,6 +80,9 @@ export const importConnectionsPrompt = async (file: string) => {
77
80
  console.log(`- ${importedCount} new connections added.`);
78
81
  console.log(`- ${updatedCount} connections overwritten.`);
79
82
  console.log(`- ${skippedCount} connections skipped.`);
83
+ } finally {
84
+ cleanup();
85
+ }
80
86
  } catch (err: any) {
81
87
  console.error(chalk.red(`Error reading or parsing file: ${err instanceof Error ? err.message : String(err)}`));
82
88
  }
@@ -1,12 +1,13 @@
1
1
  import chalk from "chalk";
2
2
  import os from "os";
3
- import { expandHomePath, getConnections } from "../database";
3
+ import { getConnections } from "../database";
4
4
  import { buildRemoteCommand } from "../helpers/shell";
5
5
 
6
6
  export async function listConnectionsPrompt(options?: { oneline?: boolean; names?: boolean }) {
7
7
  let connections = getConnections();
8
8
  if (options?.oneline) {
9
- // Sort by lastUsed (most recent first) for fzf - this helps with selection
9
+ // Sort by lastUsed descending so most recently used is first in the pipe;
10
+ // fzf's default layout places the first line at the bottom near the cursor.
10
11
  if (options?.names) {
11
12
  connections = connections.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0));
12
13
  }
@@ -34,54 +35,7 @@ export async function listConnectionsPrompt(options?: { oneline?: boolean; names
34
35
  const userHost = userHostCache.get(c.alias)!;
35
36
 
36
37
  if (options?.names) {
37
- // Optimized tab-separated format for fzf
38
- let output = c.alias + TAB + userHost + TAB;
39
-
40
- // Build SSH args array more efficiently
41
- const args: string[] = [];
42
-
43
- if (c.key_path) {
44
- // Fast path expansion - only expand if it starts with ~
45
- let expandedKeyPath = c.key_path;
46
- if (expandedKeyPath.startsWith('~')) {
47
- if (expandedKeyPath === '~') {
48
- expandedKeyPath = homeDir;
49
- } else if (expandedKeyPath.startsWith('~/')) {
50
- expandedKeyPath = homeDir + expandedKeyPath.slice(1);
51
- }
52
- }
53
-
54
- // Fast quote escaping - only escape single quotes
55
- const needsEscaping = expandedKeyPath.includes(SINGLE_QUOTE);
56
- const quotedKey = needsEscaping
57
- ? SINGLE_QUOTE + expandedKeyPath.replace(/'/g, ESCAPED_QUOTE) + SINGLE_QUOTE
58
- : SINGLE_QUOTE + expandedKeyPath + SINGLE_QUOTE;
59
-
60
- args.push(DASH_I, quotedKey);
61
- }
62
-
63
- if (c.port) {
64
- args.push(DASH_P, String(c.port));
65
- }
66
-
67
- // Always add -t for interactive terminal allocation
68
- args.push(DASH_T);
69
- args.push(userHost);
70
-
71
- if (c.remote_path) {
72
- // Only build remote command if needed
73
- const remoteCommand = buildRemoteCommand({
74
- remotePath: String(c.remote_path),
75
- postCommand: 'exec "$SHELL" -l',
76
- });
77
- if (remoteCommand) {
78
- args.push(remoteCommand);
79
- }
80
- }
81
-
82
- // Join args with spaces (faster than array join for small arrays)
83
- output += args.join(SPACE);
84
- console.log(output);
38
+ console.log(c.alias + TAB + userHost);
85
39
  } else {
86
40
  // Simple args-only format (even faster)
87
41
  const args: string[] = [];
@@ -1,6 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import inquirer from "inquirer";
3
3
  import { getConnectionByAlias, updateConnection } from "../database";
4
+ import { enableEscapeExit } from "../helpers/escExit";
4
5
  import { selectAlias } from "../helpers/selectAlias";
5
6
  import { testConnectionPrompt } from "./test";
6
7
 
@@ -12,6 +13,8 @@ export async function updateConnectionPrompt(alias?: string) {
12
13
 
13
14
  const connection = getConnectionByAlias(alias);
14
15
  if (connection) {
16
+ const cleanup = enableEscapeExit();
17
+ try {
15
18
  const answers = await inquirer.prompt([
16
19
  {
17
20
  type: 'input',
@@ -59,6 +62,9 @@ export async function updateConnectionPrompt(alias?: string) {
59
62
  if (test) {
60
63
  await testConnectionPrompt(alias);
61
64
  }
65
+ } finally {
66
+ cleanup();
67
+ }
62
68
  } else {
63
69
  console.error(chalk.red('Alias not found'));
64
70
  }
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import Fuse from "fuse.js";
3
3
  import inquirer from "inquirer";
4
4
  import { getConnections } from "../database";
5
+ import { enableEscapeExit } from "./escExit";
5
6
 
6
7
  /**
7
8
  * Prompt user to choose a connection alias using fuzzy search.
@@ -17,31 +18,36 @@ export async function selectAlias(message: string = "Select a connection"): Prom
17
18
  }
18
19
 
19
20
 
20
- const { alias } = await inquirer.prompt([
21
- {
22
- type: 'search',
23
- name: 'alias',
24
- message,
25
- // Fuzzy-search the aliases using Fuse.js as the user types.
26
- // The signature matches the @inquirer/search documentation:
27
- // (term: string | void, { signal }: { signal: AbortSignal }) => Promise<Choice[]>
28
- source: async (term: unknown) => {
29
- const allChoices = connections.map(c => ({ name: c.alias, value: c.alias }));
21
+ const cleanup = enableEscapeExit();
22
+ try {
23
+ const { alias } = await inquirer.prompt([
24
+ {
25
+ type: 'search',
26
+ name: 'alias',
27
+ message,
28
+ // Fuzzy-search the aliases using Fuse.js as the user types.
29
+ // The signature matches the @inquirer/search documentation:
30
+ // (term: string | void, { signal }: { signal: AbortSignal }) => Promise<Choice[]>
31
+ source: async (term: unknown) => {
32
+ const allChoices = connections.map(c => ({ name: c.alias, value: c.alias }));
30
33
 
31
- // When term is undefined or empty, return the full, recently-used-sorted list.
32
- if (typeof term !== 'string' || term.trim().length === 0) {
33
- return allChoices;
34
- }
34
+ // When term is undefined or empty, return the full, recently-used-sorted list.
35
+ if (typeof term !== 'string' || term.trim().length === 0) {
36
+ return allChoices;
37
+ }
35
38
 
36
- // Use Fuse.js for fuzzy matching on the `name` field of choice objects.
37
- const fuse = new Fuse(allChoices, {
38
- keys: ['name'],
39
- threshold: 0.4,
40
- });
39
+ // Use Fuse.js for fuzzy matching on the `name` field of choice objects.
40
+ const fuse = new Fuse(allChoices, {
41
+ keys: ['name'],
42
+ threshold: 0.4,
43
+ });
41
44
 
42
- return fuse.search(term).map(r => r.item);
45
+ return fuse.search(term).map(r => r.item);
46
+ },
43
47
  },
44
- },
45
- ]);
46
- return alias as string;
48
+ ]);
49
+ return alias as string;
50
+ } finally {
51
+ cleanup();
52
+ }
47
53
  }