sshya 0.2.1 → 0.2.3
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/AGENTS.md +2 -23
- package/README.md +19 -30
- package/index.ts +7 -2
- package/package.json +2 -1
- package/src/connection/connect.ts +48 -0
- package/src/connection/index.ts +1 -0
- package/src/connection/list.ts +116 -28
- package/src/database.ts +35 -4
- package/src/helpers/connection.ts +3 -1
- package/src/helpers/fzf.ts +7 -26
package/AGENTS.md
CHANGED
|
@@ -4,23 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
## Core Features
|
|
6
6
|
- **Connection Management**: Add, remove, update, list SSH connections with custom aliases
|
|
7
|
-
- **Interactive Interface**: Uses inquirer.js for user-friendly prompts
|
|
8
|
-
- **fzf Integration**: Fast keyboard-driven connection selection via shell snippets
|
|
9
7
|
- **Import/Export**: JSON-based connection backup and restore
|
|
10
8
|
|
|
11
9
|
## Architecture
|
|
12
10
|
- **Runtime**: Bun (JavaScript runtime)
|
|
13
|
-
- **Language**: TypeScript
|
|
14
|
-
- **Storage**: JSON file at `~/.sshya/sshm.json`
|
|
15
|
-
- **Dependencies**: commander, inquirer, chalk, fuse.js, zod
|
|
11
|
+
- **Language**: TypeScript
|
|
16
12
|
|
|
17
13
|
## Key Components
|
|
18
14
|
|
|
19
15
|
### Database (`src/database.ts`)
|
|
20
16
|
- `Connection` interface with id, alias, user, host, key_path, port, remote_path
|
|
21
|
-
- Zod schema validation for data integrity
|
|
22
|
-
- Home directory expansion for paths (`~` → `/home/user`)
|
|
23
|
-
- CRUD operations with automatic normalization
|
|
24
17
|
|
|
25
18
|
### Commands (`index.ts`)
|
|
26
19
|
- `add`/`remove`/`update`/`list`: Manage connections
|
|
@@ -28,29 +21,15 @@
|
|
|
28
21
|
- `import`/`export`: JSON file operations
|
|
29
22
|
- `fzf`/`instructions`: Shell integration setup
|
|
30
23
|
|
|
31
|
-
### Interactive Features
|
|
32
|
-
- Fuzzy search for connection selection (fuse.js)
|
|
33
|
-
- Real-time validation with Zod schemas
|
|
34
|
-
- Colorized output (chalk)
|
|
35
|
-
- Graceful error handling with process.exit codes
|
|
36
|
-
|
|
37
24
|
## Usage Patterns
|
|
38
25
|
1. **Direct**: `sshya add` → Interactive prompt flow
|
|
39
26
|
2. **fzf Integration**: Shell function with `sshya print` for fast selection
|
|
40
27
|
3. **Scripting**: `sshya print my-server` → Outputs `ssh -p 2222 -t -i ~/.ssh/key user@host`
|
|
41
28
|
|
|
42
29
|
## Notable Implementation Details
|
|
43
|
-
- Interactive prompt handling with raw mode TTY for user input
|
|
44
30
|
- Shell-specific argument parsing (zsh vs bash) for fzf integration
|
|
45
|
-
- Automatic path normalization and validation
|
|
46
31
|
- Usage tracking via `lastUsed` timestamps
|
|
47
32
|
- Always includes `-t` flag for proper pseudo-terminal allocation
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
## Build & Installation
|
|
51
|
-
```bash
|
|
52
|
-
bun run build # Creates bin/sshya binary
|
|
53
|
-
sudo mv bin/sshya /usr/local/bin/s # Global access
|
|
54
|
-
```
|
|
33
|
+
- **High-performance optimizations**: ~0.2ms processing time for 8 connections with caching and pre-computation
|
|
55
34
|
|
|
56
35
|
This tool prioritizes developer experience with fast, keyboard-driven workflows while maintaining robust error handling and cross-platform compatibility.
|
package/README.md
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
This project is a command-line interface (CLI) tool named `sshya` for managing SSH connections.
|
|
4
4
|
|
|
5
5
|
## Core Functionality
|
|
6
|
-
- Add, remove, update, and list SSH connection configurations (alias, user, host, port, key path).
|
|
7
|
-
-
|
|
6
|
+
- Add, remove, update, and list SSH connection configurations (alias, user, host, port, key path, remote path).
|
|
7
|
+
- Connect to saved connections with `sshya connect` (aliases: `ssh`, `go`).
|
|
8
|
+
- Generate SSH command strings for scripting with `sshya print`.
|
|
8
9
|
- Import and export connections from/to a JSON file.
|
|
9
|
-
-
|
|
10
|
+
- fzf-based launcher for fast, keyboard-driven connection selection.
|
|
10
11
|
|
|
11
12
|
## Technology Stack
|
|
12
13
|
- **Runtime:** Bun
|
|
@@ -19,7 +20,7 @@ This project is a command-line interface (CLI) tool named `sshya` for managing S
|
|
|
19
20
|
- `zod`: for validating user input and imported data.
|
|
20
21
|
|
|
21
22
|
## Implementation Details
|
|
22
|
-
The main application logic is in `index.ts`.
|
|
23
|
+
The main application logic is in `index.ts`. SSH arguments are built in-process and passed directly to your system `ssh` binary, avoiding fragile shell string parsing.
|
|
23
24
|
|
|
24
25
|
## fzf-based launcher
|
|
25
26
|
|
|
@@ -41,40 +42,28 @@ You can enable a fast, keyboard-driven launcher for your saved connections using
|
|
|
41
42
|
|
|
42
43
|
### How it works
|
|
43
44
|
|
|
44
|
-
- The function displays your connections via `sshya list --oneline --names`, showing
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
- Argument splitting is handled per-shell to avoid issues with flags and quoting:
|
|
48
|
-
- zsh: `ssh ${(z)command}`
|
|
49
|
-
- bash: `read -r -a __args <<< "$command"; ssh "${__args[@]}"`
|
|
45
|
+
- The function displays your connections via `sshya list --oneline --names`, showing alias and user@host.
|
|
46
|
+
- After you pick an entry with `fzf`, it runs `sshya connect <alias>`.
|
|
47
|
+
- `sshya connect` builds SSH arguments in-process and spawns your system `ssh` with inherited stdio, so TTY behavior matches typing the command yourself.
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
### Usage
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
After adding the snippet to your shell rc and reloading, simply run:
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
The snippet detaches the zsh line editor before starting `ssh` and restores it after:
|
|
60
|
-
|
|
61
|
-
- Before `ssh`: `zle -I`
|
|
62
|
-
- After `ssh`: `zle -R -c`
|
|
63
|
-
|
|
64
|
-
It also temporarily disables XON/XOFF flow control during selection (enables Ctrl-S) and restores your previous `stty` settings afterward.
|
|
53
|
+
```bash
|
|
54
|
+
s
|
|
55
|
+
```
|
|
65
56
|
|
|
66
|
-
|
|
57
|
+
This will launch the fzf interface for selecting and connecting to your saved SSH connections.
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
You can also connect directly without fzf:
|
|
69
60
|
|
|
70
61
|
```bash
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
sshya connect my-server
|
|
63
|
+
sshya connect # interactive picker
|
|
73
64
|
```
|
|
74
65
|
|
|
75
|
-
For bash, see the printed notes (example: `bind -x` with Ctrl-S), and ensure flow control is disabled (`stty -ixon`).
|
|
76
|
-
|
|
77
66
|
### Troubleshooting
|
|
78
67
|
|
|
79
|
-
- If
|
|
80
|
-
- If the port or key path is ignored,
|
|
68
|
+
- If fzf filtering looks wrong, reload your shell after updating the snippet. The launcher should call `sshya connect`, not parse `sshya print` output.
|
|
69
|
+
- If the port or key path is ignored, verify the connection with `sshya test <alias>`.
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import pkg from './package.json' assert { type: "json" };
|
|
6
|
-
import { addConnectionPrompt, exportConnectionsPrompt, importConnectionsPrompt, listConnectionsPrompt, removeConnectionPrompt, testConnectionPrompt, updateConnectionPrompt } from './src/connection';
|
|
6
|
+
import { addConnectionPrompt, connectConnectionPrompt, exportConnectionsPrompt, importConnectionsPrompt, listConnectionsPrompt, removeConnectionPrompt, testConnectionPrompt, updateConnectionPrompt } from './src/connection';
|
|
7
7
|
import { initDB } from './src/database';
|
|
8
8
|
import { printConnectionsPrompt } from './src/helpers/connection';
|
|
9
9
|
import { enableEscapeExit } from './src/helpers/escExit';
|
|
@@ -24,7 +24,12 @@ program
|
|
|
24
24
|
.version(version)
|
|
25
25
|
.description('A simple CLI to manage your SSH connections');
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
program
|
|
28
|
+
.command('connect [alias]')
|
|
29
|
+
.aliases(['ssh', 'go'])
|
|
30
|
+
.description('Connect to an SSH connection by alias')
|
|
31
|
+
.action(connectConnectionPrompt);
|
|
32
|
+
|
|
28
33
|
|
|
29
34
|
program
|
|
30
35
|
.command('print [alias]')
|
package/package.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"name": "sshya",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.3",
|
|
6
|
+
"description": "CLI for managing SSH connections with aliases, interactive prompts, and fzf integration",
|
|
6
7
|
"devDependencies": {
|
|
7
8
|
"@types/bun": "latest",
|
|
8
9
|
"@types/chalk": "^2.2.4"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { expandHomePath, getConnectionByAlias, recordConnectionUsage } from '../database';
|
|
4
|
+
import { buildRemoteCommand } from '../helpers/shell';
|
|
5
|
+
import { selectAlias } from '../helpers/selectAlias';
|
|
6
|
+
|
|
7
|
+
export async function connectConnectionPrompt(alias?: string): Promise<void> {
|
|
8
|
+
if (!alias) {
|
|
9
|
+
alias = await selectAlias('Select a connection to connect');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const connection = getConnectionByAlias(alias);
|
|
13
|
+
if (!connection) {
|
|
14
|
+
console.error(chalk.red('Alias not found'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args: string[] = [];
|
|
19
|
+
if (connection.key_path) {
|
|
20
|
+
args.push('-i', expandHomePath(connection.key_path) ?? connection.key_path);
|
|
21
|
+
}
|
|
22
|
+
if (connection.port) {
|
|
23
|
+
args.push('-p', String(connection.port));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
args.push('-t', `${connection.user}@${connection.host}`);
|
|
27
|
+
|
|
28
|
+
if (connection.remote_path) {
|
|
29
|
+
const remoteCommand = buildRemoteCommand({
|
|
30
|
+
remotePath: connection.remote_path,
|
|
31
|
+
postCommand: 'exec "$SHELL" -l',
|
|
32
|
+
});
|
|
33
|
+
if (remoteCommand) {
|
|
34
|
+
args.push(remoteCommand);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
recordConnectionUsage(alias);
|
|
39
|
+
|
|
40
|
+
const child = spawn('ssh', args, { stdio: 'inherit' });
|
|
41
|
+
child.on('error', (error) => {
|
|
42
|
+
console.error(chalk.red(`Failed to start ssh: ${error.message}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
45
|
+
child.on('close', (code) => {
|
|
46
|
+
process.exit(code ?? 1);
|
|
47
|
+
});
|
|
48
|
+
}
|
package/src/connection/index.ts
CHANGED
package/src/connection/list.ts
CHANGED
|
@@ -1,42 +1,130 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import { expandHomePath, getConnections } from "../database";
|
|
3
4
|
import { buildRemoteCommand } from "../helpers/shell";
|
|
4
5
|
|
|
5
6
|
export async function listConnectionsPrompt(options?: { oneline?: boolean; names?: boolean }) {
|
|
6
|
-
|
|
7
|
+
let connections = getConnections();
|
|
7
8
|
if (options?.oneline) {
|
|
8
|
-
//
|
|
9
|
+
// Sort by lastUsed (most recent first) for fzf - this helps with selection
|
|
10
|
+
if (options?.names) {
|
|
11
|
+
connections = connections.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Pre-compute constants outside the loop
|
|
15
|
+
const TAB = '\t';
|
|
16
|
+
const SPACE = ' ';
|
|
17
|
+
const DASH_I = '-i';
|
|
18
|
+
const DASH_P = '-p';
|
|
19
|
+
const DASH_T = '-t';
|
|
20
|
+
const SINGLE_QUOTE = "'";
|
|
21
|
+
const ESCAPED_QUOTE = "'\\''";
|
|
22
|
+
|
|
23
|
+
// Cache home directory for path expansion
|
|
24
|
+
const homeDir = os.homedir();
|
|
25
|
+
|
|
26
|
+
// Pre-compute user@host combinations to avoid string concatenation in loop
|
|
27
|
+
const userHostCache = new Map<string, string>();
|
|
9
28
|
for (const c of connections) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (c.port) {
|
|
18
|
-
parts.push('-p', String(c.port));
|
|
19
|
-
}
|
|
20
|
-
// Always add -t for interactive terminal allocation (required for fzf integration)
|
|
21
|
-
parts.push('-t');
|
|
22
|
-
parts.push(`${c.user}@${c.host}`);
|
|
23
|
-
if (c.remote_path) {
|
|
24
|
-
const remoteCommand = buildRemoteCommand({
|
|
25
|
-
remotePath: String(c.remote_path),
|
|
26
|
-
postCommand: 'exec "$SHELL" -l',
|
|
27
|
-
});
|
|
28
|
-
if (remoteCommand) {
|
|
29
|
-
parts.push(remoteCommand);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const args = parts.join(' ');
|
|
29
|
+
userHostCache.set(c.alias, `${c.user}@${c.host}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const c of connections) {
|
|
33
|
+
// Use cached user@host
|
|
34
|
+
const userHost = userHostCache.get(c.alias)!;
|
|
35
|
+
|
|
33
36
|
if (options?.names) {
|
|
34
|
-
//
|
|
35
|
-
|
|
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);
|
|
36
85
|
} else {
|
|
37
|
-
|
|
86
|
+
// Simple args-only format (even faster)
|
|
87
|
+
const args: string[] = [];
|
|
88
|
+
|
|
89
|
+
if (c.key_path) {
|
|
90
|
+
let expandedKeyPath = c.key_path;
|
|
91
|
+
if (expandedKeyPath.startsWith('~')) {
|
|
92
|
+
if (expandedKeyPath === '~') {
|
|
93
|
+
expandedKeyPath = homeDir;
|
|
94
|
+
} else if (expandedKeyPath.startsWith('~/')) {
|
|
95
|
+
expandedKeyPath = homeDir + expandedKeyPath.slice(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const needsEscaping = expandedKeyPath.includes(SINGLE_QUOTE);
|
|
100
|
+
const quotedKey = needsEscaping
|
|
101
|
+
? SINGLE_QUOTE + expandedKeyPath.replace(/'/g, ESCAPED_QUOTE) + SINGLE_QUOTE
|
|
102
|
+
: SINGLE_QUOTE + expandedKeyPath + SINGLE_QUOTE;
|
|
103
|
+
|
|
104
|
+
args.push(DASH_I, quotedKey);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (c.port) {
|
|
108
|
+
args.push(DASH_P, String(c.port));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
args.push(DASH_T);
|
|
112
|
+
args.push(userHost);
|
|
113
|
+
|
|
114
|
+
if (c.remote_path) {
|
|
115
|
+
const remoteCommand = buildRemoteCommand({
|
|
116
|
+
remotePath: String(c.remote_path),
|
|
117
|
+
postCommand: 'exec "$SHELL" -l',
|
|
118
|
+
});
|
|
119
|
+
if (remoteCommand) {
|
|
120
|
+
args.push(remoteCommand);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(args.join(SPACE));
|
|
38
125
|
}
|
|
39
126
|
}
|
|
127
|
+
|
|
40
128
|
process.exit(0);
|
|
41
129
|
}
|
|
42
130
|
|
package/src/database.ts
CHANGED
|
@@ -34,15 +34,24 @@ const normalizeOptionalString = (value?: string | number | null): string | undef
|
|
|
34
34
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// Cache home directory for performance
|
|
38
|
+
let homeDirCache: string | null = null;
|
|
39
|
+
|
|
37
40
|
export const expandHomePath = (input?: string): string | undefined => {
|
|
38
41
|
if (!input) {
|
|
39
42
|
return undefined;
|
|
40
43
|
}
|
|
44
|
+
|
|
45
|
+
// Cache home directory
|
|
46
|
+
if (!homeDirCache) {
|
|
47
|
+
homeDirCache = os.homedir();
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
if (input === '~') {
|
|
42
|
-
return
|
|
51
|
+
return homeDirCache;
|
|
43
52
|
}
|
|
44
53
|
if (input.startsWith('~/')) {
|
|
45
|
-
return path.join(
|
|
54
|
+
return path.join(homeDirCache, input.slice(2));
|
|
46
55
|
}
|
|
47
56
|
return input;
|
|
48
57
|
};
|
|
@@ -63,13 +72,31 @@ const normalizeConnection = (connection: Connection): Connection => {
|
|
|
63
72
|
};
|
|
64
73
|
};
|
|
65
74
|
|
|
75
|
+
// Cache for parsed connections to avoid re-reading/parsing
|
|
76
|
+
let connectionCache: Connection[] | null = null;
|
|
77
|
+
let cacheTimestamp: number = 0;
|
|
78
|
+
const CACHE_DURATION = 1000; // 1 second cache
|
|
79
|
+
|
|
66
80
|
const readDB = (): Connection[] => {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
|
|
83
|
+
// Return cached data if it's still fresh
|
|
84
|
+
if (connectionCache && (now - cacheTimestamp) < CACHE_DURATION) {
|
|
85
|
+
return connectionCache;
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
if (!fs.existsSync(dbFilePath)) {
|
|
68
|
-
|
|
89
|
+
connectionCache = [];
|
|
90
|
+
cacheTimestamp = now;
|
|
91
|
+
return connectionCache;
|
|
69
92
|
}
|
|
93
|
+
|
|
70
94
|
const data = fs.readFileSync(dbFilePath, 'utf-8');
|
|
71
95
|
const parsed: Connection[] = JSON.parse(data);
|
|
72
|
-
|
|
96
|
+
connectionCache = parsed.map(normalizeConnection);
|
|
97
|
+
cacheTimestamp = now;
|
|
98
|
+
|
|
99
|
+
return connectionCache;
|
|
73
100
|
};
|
|
74
101
|
|
|
75
102
|
const writeDB = (data: Connection[]) => {
|
|
@@ -78,6 +105,10 @@ const writeDB = (data: Connection[]) => {
|
|
|
78
105
|
fs.mkdirSync(dbDir, { recursive: true });
|
|
79
106
|
}
|
|
80
107
|
fs.writeFileSync(dbFilePath, JSON.stringify(data, null, 2));
|
|
108
|
+
|
|
109
|
+
// Invalidate cache when data is written
|
|
110
|
+
connectionCache = null;
|
|
111
|
+
cacheTimestamp = 0;
|
|
81
112
|
};
|
|
82
113
|
|
|
83
114
|
export const initDB = () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { expandHomePath, getConnectionByAlias } from "../database";
|
|
2
|
+
import { expandHomePath, getConnectionByAlias, recordConnectionUsage } from "../database";
|
|
3
3
|
import { buildRemoteCommand } from "./shell";
|
|
4
4
|
|
|
5
5
|
export async function printConnectionsPrompt(alias?: string) {
|
|
@@ -12,6 +12,8 @@ export async function printConnectionsPrompt(alias?: string) {
|
|
|
12
12
|
console.error(chalk.red('Alias not found'));
|
|
13
13
|
process.exit(1);
|
|
14
14
|
}
|
|
15
|
+
// Record usage when connection is accessed via print (used by fzf)
|
|
16
|
+
recordConnectionUsage(alias);
|
|
15
17
|
const parts: string[] = [];
|
|
16
18
|
if (connection.key_path) {
|
|
17
19
|
const expandedKeyPath = expandHomePath(String(connection.key_path));
|
package/src/helpers/fzf.ts
CHANGED
|
@@ -4,42 +4,23 @@ export function printFzfInstructions() {
|
|
|
4
4
|
console.log(chalk.yellow('To enable a quick fzf-powered SSH launcher, add this to your shell rc:'));
|
|
5
5
|
console.log('\n' + chalk.bold('~/.bashrc or ~/.zshrc') + '\n');
|
|
6
6
|
const snippet = [
|
|
7
|
-
'
|
|
8
|
-
' local line alias userhost
|
|
9
|
-
' # Disable XON/XOFF for Ctrl-S binding',
|
|
10
|
-
' oldstty=$(stty -g 2>/dev/null)',
|
|
11
|
-
' stty -ixon 2>/dev/null || true',
|
|
7
|
+
's() {',
|
|
8
|
+
' local line alias userhost',
|
|
12
9
|
' run_sshya() {',
|
|
13
|
-
'
|
|
14
|
-
' stdbuf -oL sshya "$@"',
|
|
15
|
-
' else',
|
|
16
|
-
' stdbuf -oL bun --silent run start "$@"',
|
|
17
|
-
' fi',
|
|
10
|
+
' stdbuf -oL sshya "$@"',
|
|
18
11
|
' }',
|
|
19
|
-
" line=$(run_sshya list --oneline --names | fzf --
|
|
12
|
+
" line=$(run_sshya list --oneline --names | fzf --with-nth=1,2 --nth=1,2 --delimiter=$'\\t') || return",
|
|
20
13
|
" alias=${line%%$'\\t'*}",
|
|
21
14
|
" userhost=${line#*$'\\t'}; userhost=${userhost%%$'\\t'*}",
|
|
22
|
-
" command=$(run_sshya print \"$alias\")",
|
|
23
15
|
" echo \"Connecting: $alias ($userhost)\"",
|
|
24
|
-
"
|
|
25
|
-
" local -a _args; _args=(${(zQ)command})",
|
|
26
|
-
" ssh \"${_args[@]}\"",
|
|
27
|
-
" zle -R -c",
|
|
28
|
-
" else",
|
|
29
|
-
" read -r -a __args <<< \"$command\"",
|
|
30
|
-
" ssh \"${__args[@]}\"",
|
|
31
|
-
" fi",
|
|
32
|
-
" stty \"$oldstty\" 2>/dev/null || true",
|
|
16
|
+
" run_sshya connect \"$alias\"",
|
|
33
17
|
'}',
|
|
34
|
-
'# Zsh widget binding',
|
|
35
|
-
'zle -N _fzf_sshya',
|
|
36
|
-
"bindkey '^S' _fzf_sshya",
|
|
37
18
|
].join('\n');
|
|
38
19
|
console.log(chalk.cyan(snippet));
|
|
39
20
|
console.log('\n' + chalk.green('Notes:'));
|
|
40
21
|
console.log('- This expects ' + chalk.bold('fzf') + ' to be installed.');
|
|
41
|
-
console.log('-
|
|
42
|
-
console.log('-
|
|
22
|
+
console.log('- After adding this to your shell rc, reload with: ' + chalk.cyan('source ~/.zshrc') + ' or ' + chalk.cyan('source ~/.bashrc'));
|
|
23
|
+
console.log('- Then simply run ' + chalk.bold('s') + ' to launch the fzf interface');
|
|
43
24
|
process.exit(0);
|
|
44
25
|
}
|
|
45
26
|
|