ssh-picker 0.1.0

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.
Files changed (53) hide show
  1. package/README.md +68 -0
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +192 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/config/paths.d.ts +17 -0
  6. package/dist/config/paths.js +47 -0
  7. package/dist/config/paths.js.map +1 -0
  8. package/dist/db/connection.d.ts +4 -0
  9. package/dist/db/connection.js +17 -0
  10. package/dist/db/connection.js.map +1 -0
  11. package/dist/db/migrations.d.ts +4 -0
  12. package/dist/db/migrations.js +45 -0
  13. package/dist/db/migrations.js.map +1 -0
  14. package/dist/db/repositories/serverRepository.d.ts +13 -0
  15. package/dist/db/repositories/serverRepository.js +83 -0
  16. package/dist/db/repositories/serverRepository.js.map +1 -0
  17. package/dist/db/repositories/settingsRepository.d.ts +8 -0
  18. package/dist/db/repositories/settingsRepository.js +20 -0
  19. package/dist/db/repositories/settingsRepository.js.map +1 -0
  20. package/dist/import-export/archive.d.ts +3 -0
  21. package/dist/import-export/archive.js +38 -0
  22. package/dist/import-export/archive.js.map +1 -0
  23. package/dist/sftp/client.d.ts +21 -0
  24. package/dist/sftp/client.js +123 -0
  25. package/dist/sftp/client.js.map +1 -0
  26. package/dist/shared/credentials.d.ts +2 -0
  27. package/dist/shared/credentials.js +9 -0
  28. package/dist/shared/credentials.js.map +1 -0
  29. package/dist/shared/errors.d.ts +17 -0
  30. package/dist/shared/errors.js +36 -0
  31. package/dist/shared/errors.js.map +1 -0
  32. package/dist/shared/types.d.ts +56 -0
  33. package/dist/shared/types.js +2 -0
  34. package/dist/shared/types.js.map +1 -0
  35. package/dist/ssh/client.d.ts +6 -0
  36. package/dist/ssh/client.js +45 -0
  37. package/dist/ssh/client.js.map +1 -0
  38. package/dist/tui/App.d.ts +5 -0
  39. package/dist/tui/App.js +27 -0
  40. package/dist/tui/App.js.map +1 -0
  41. package/dist/tui/screens/Dashboard.d.ts +8 -0
  42. package/dist/tui/screens/Dashboard.js +42 -0
  43. package/dist/tui/screens/Dashboard.js.map +1 -0
  44. package/dist/tui/screens/FileManager.d.ts +7 -0
  45. package/dist/tui/screens/FileManager.js +120 -0
  46. package/dist/tui/screens/FileManager.js.map +1 -0
  47. package/dist/vault/crypto.d.ts +14 -0
  48. package/dist/vault/crypto.js +43 -0
  49. package/dist/vault/crypto.js.map +1 -0
  50. package/dist/vault/vault.d.ts +7 -0
  51. package/dist/vault/vault.js +67 -0
  52. package/dist/vault/vault.js.map +1 -0
  53. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # SSHP / SSH Picker
2
+
3
+ SSHP is a terminal dashboard for saved SSH servers with an encrypted portable vault and SFTP file transfers.
4
+
5
+ ## Install
6
+
7
+ From npm:
8
+
9
+ ```bash
10
+ npm install -g ssh-picker
11
+ ```
12
+
13
+ From the GitHub release tarball:
14
+
15
+ ```bash
16
+ npm install -g https://github.com/adittanu/ssh-picker/releases/download/v0.1.0/ssh-picker-0.1.0.tgz
17
+ ```
18
+
19
+ From a local checkout:
20
+
21
+ ```bash
22
+ npm install -g .
23
+ ```
24
+
25
+ The package builds automatically during install and exposes a global `sshp` command.
26
+
27
+ ## Quick start
28
+
29
+ ```bash
30
+ sshp init # create ~/.sshp/sshp.db encrypted with a master password
31
+ sshp add # add a password-based SSH server
32
+ sshp # open the dashboard
33
+ ```
34
+
35
+ Dashboard keys:
36
+
37
+ - `Enter` connect to the selected server over SSH
38
+ - `F` open SFTP file manager
39
+ - `/` search servers
40
+ - `Q` or `Esc` quit
41
+
42
+ ## Scriptable commands
43
+
44
+ ```bash
45
+ sshp list
46
+ sshp connect <server>
47
+ sshp files <server>
48
+ sshp upload <server> <local> <remote>
49
+ sshp download <server> <remote> <local>
50
+ sshp export backup.sshp
51
+ sshp import backup.sshp
52
+ sshp config set dataDir <path>
53
+ ```
54
+
55
+ ## Security notes
56
+
57
+ - The master password is never stored.
58
+ - Credentials are encrypted with AES-256-GCM.
59
+ - The vault key is derived with Node's `scrypt` implementation.
60
+ - Export files contain the already-encrypted SQLite vault and still require the master password.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ npm install
66
+ npm run build
67
+ npm test
68
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Command } from 'commander';
4
+ import { render } from 'ink';
5
+ import { input, password, confirm } from '@inquirer/prompts';
6
+ import { statSync } from 'node:fs';
7
+ import { basename } from 'node:path';
8
+ import { writeBootstrapConfig } from '../config/paths.js';
9
+ import { toFriendlyMessage } from '../shared/errors.js';
10
+ async function askMasterPassword(message = 'Master password') {
11
+ return password({ message, mask: '*' });
12
+ }
13
+ async function unlockFromPrompt() {
14
+ const { unlockVault } = await import('../vault/vault.js');
15
+ return unlockVault(await askMasterPassword());
16
+ }
17
+ async function runInit() {
18
+ const { initVault } = await import('../vault/vault.js');
19
+ const first = await askMasterPassword('Create master password');
20
+ const second = await askMasterPassword('Confirm master password');
21
+ if (first !== second)
22
+ throw new Error('Master passwords do not match.');
23
+ const vault = initVault(first);
24
+ console.log(`Initialized SSHP vault at ${vault.dataDir}`);
25
+ }
26
+ async function runAdd() {
27
+ const [{ openMigratedDatabase }, { ServerRepository }, { encryptString }] = await Promise.all([
28
+ import('../db/connection.js'),
29
+ import('../db/repositories/serverRepository.js'),
30
+ import('../vault/crypto.js')
31
+ ]);
32
+ const vault = await unlockFromPrompt();
33
+ const name = await input({ message: 'Name' });
34
+ const host = await input({ message: 'Host' });
35
+ const username = await input({ message: 'Username' });
36
+ const portText = await input({ message: 'Port', default: '22' });
37
+ const serverPassword = await password({ message: 'SSH password', mask: '*' });
38
+ const defaultRemotePath = await input({ message: 'Default remote path', default: '/home/' + username });
39
+ const db = openMigratedDatabase(vault.dbPath);
40
+ try {
41
+ const server = new ServerRepository(db).create({
42
+ name,
43
+ host,
44
+ username,
45
+ port: Number(portText) || 22,
46
+ authType: 'password',
47
+ encryptedPassword: encryptString(serverPassword, vault.key),
48
+ defaultRemotePath
49
+ });
50
+ console.log(`Added ${server.name} (${server.username}@${server.host}:${server.port})`);
51
+ }
52
+ finally {
53
+ db.close();
54
+ }
55
+ }
56
+ async function runList() {
57
+ const [{ openMigratedDatabase }, { ServerRepository }] = await Promise.all([
58
+ import('../db/connection.js'),
59
+ import('../db/repositories/serverRepository.js')
60
+ ]);
61
+ const vault = await unlockFromPrompt();
62
+ const db = openMigratedDatabase(vault.dbPath);
63
+ try {
64
+ const servers = new ServerRepository(db).list();
65
+ if (servers.length === 0) {
66
+ console.log('No servers. Run `sshp add`.');
67
+ return;
68
+ }
69
+ for (const server of servers) {
70
+ console.log(`${server.name}\t${server.username}@${server.host}:${server.port}\t${server.connectionCount} connections`);
71
+ }
72
+ }
73
+ finally {
74
+ db.close();
75
+ }
76
+ }
77
+ async function loadServer(name) {
78
+ const [{ openMigratedDatabase }, { ServerRepository }] = await Promise.all([
79
+ import('../db/connection.js'),
80
+ import('../db/repositories/serverRepository.js')
81
+ ]);
82
+ const vault = await unlockFromPrompt();
83
+ const db = openMigratedDatabase(vault.dbPath);
84
+ try {
85
+ const repo = new ServerRepository(db);
86
+ return { vault, server: repo.findByName(name) };
87
+ }
88
+ finally {
89
+ db.close();
90
+ }
91
+ }
92
+ async function recordServerAction(vaultDbPath, serverId, action, localPath, remotePath) {
93
+ const [{ openMigratedDatabase }, { ServerRepository }] = await Promise.all([
94
+ import('../db/connection.js'),
95
+ import('../db/repositories/serverRepository.js')
96
+ ]);
97
+ const db = openMigratedDatabase(vaultDbPath);
98
+ try {
99
+ new ServerRepository(db).recordConnection(serverId, action, localPath, remotePath);
100
+ }
101
+ finally {
102
+ db.close();
103
+ }
104
+ }
105
+ async function runConnect(name) {
106
+ const [{ connectSsh }, { decryptServerCredentials }] = await Promise.all([
107
+ import('../ssh/client.js'),
108
+ import('../shared/credentials.js')
109
+ ]);
110
+ const { vault, server } = await loadServer(name);
111
+ await connectSsh({ server, credentials: decryptServerCredentials(server, vault) });
112
+ await recordServerAction(vault.dbPath, server.id, 'ssh');
113
+ }
114
+ async function runDashboard() {
115
+ const { App } = await import('../tui/App.js');
116
+ const vault = await unlockFromPrompt();
117
+ render(_jsx(App, { vault: vault }));
118
+ }
119
+ async function runFiles(name) {
120
+ const { FileManager } = await import('../tui/screens/FileManager.js');
121
+ const { vault, server } = await loadServer(name);
122
+ render(_jsx(FileManager, { server: server, vault: vault, onBack: () => undefined }));
123
+ }
124
+ async function runUpload(name, localPath, remotePath) {
125
+ const [{ withSftp }, { decryptServerCredentials }] = await Promise.all([
126
+ import('../sftp/client.js'),
127
+ import('../shared/credentials.js')
128
+ ]);
129
+ const { vault, server } = await loadServer(name);
130
+ const overwrite = await confirm({ message: 'Overwrite remote path if it exists?', default: false });
131
+ await withSftp({ server, credentials: decryptServerCredentials(server, vault) }, async (client) => {
132
+ const stat = statSync(localPath);
133
+ const target = stat.isDirectory() ? remotePath.replace(/\/$/, '') + '/' + basename(localPath) : remotePath;
134
+ await client.uploadRecursive(localPath, target, overwrite);
135
+ });
136
+ await recordServerAction(vault.dbPath, server.id, 'upload', localPath, remotePath);
137
+ console.log('Upload complete.');
138
+ }
139
+ async function runDownload(name, remotePath, localPath) {
140
+ const [{ withSftp }, { decryptServerCredentials }] = await Promise.all([
141
+ import('../sftp/client.js'),
142
+ import('../shared/credentials.js')
143
+ ]);
144
+ const { vault, server } = await loadServer(name);
145
+ const overwrite = await confirm({ message: 'Overwrite local path if it exists?', default: false });
146
+ await withSftp({ server, credentials: decryptServerCredentials(server, vault) }, async (client) => {
147
+ await client.downloadFile(remotePath, localPath, overwrite);
148
+ });
149
+ await recordServerAction(vault.dbPath, server.id, 'download', localPath, remotePath);
150
+ console.log('Download complete.');
151
+ }
152
+ async function runExport(file) {
153
+ const { exportVault } = await import('../import-export/archive.js');
154
+ console.log(`Exported to ${exportVault(file)}`);
155
+ }
156
+ async function runImport(file) {
157
+ const { importVault } = await import('../import-export/archive.js');
158
+ const ok = await confirm({ message: 'Import will replace the current vault database. Continue?', default: false });
159
+ if (!ok)
160
+ return;
161
+ console.log(`Imported to ${importVault(file)}`);
162
+ }
163
+ async function main() {
164
+ const program = new Command();
165
+ program
166
+ .name('sshp')
167
+ .description('Portable encrypted SSH/SFTP picker')
168
+ .version('0.1.0')
169
+ .action(runDashboard);
170
+ program.command('init').description('Create a portable encrypted vault').action(runInit);
171
+ program.command('add').description('Add a password-based SSH server').action(runAdd);
172
+ program.command('list').description('List saved servers').action(runList);
173
+ program.command('connect <server>').description('Connect to a server over SSH').action(runConnect);
174
+ program.command('files <server>').description('Open the SFTP file manager').action(runFiles);
175
+ program.command('upload <server> <local> <remote>').description('Upload a file or folder over SFTP').action(runUpload);
176
+ program.command('download <server> <remote> <local>').description('Download a file over SFTP').action(runDownload);
177
+ program.command('export <file>').description('Export encrypted vault backup').action(runExport);
178
+ program.command('import <file>').description('Import encrypted vault backup').action(runImport);
179
+ const config = program.command('config').description('Manage SSHP configuration');
180
+ config.command('set <key> <value>').description('Set a config value').action((key, value) => {
181
+ if (key !== 'dataDir')
182
+ throw new Error('Only dataDir is supported.');
183
+ writeBootstrapConfig({ dataDir: value });
184
+ console.log(`dataDir set to ${value}`);
185
+ });
186
+ await program.parseAsync(process.argv);
187
+ }
188
+ main().catch((error) => {
189
+ console.error(toFriendlyMessage(error));
190
+ process.exitCode = 1;
191
+ });
192
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export declare const APP_DIR_NAME = ".sshp";
2
+ export declare const DB_FILE_NAME = "sshp.db";
3
+ export declare const CONFIG_FILE_NAME = "config.json";
4
+ export interface PathResolutionOptions {
5
+ env?: NodeJS.ProcessEnv;
6
+ home?: string;
7
+ }
8
+ export interface BootstrapConfig {
9
+ dataDir?: string;
10
+ }
11
+ export declare function defaultDataDir(home?: string): string;
12
+ export declare function bootstrapConfigPath(home?: string): string;
13
+ export declare function readBootstrapConfig(home?: string): BootstrapConfig;
14
+ export declare function writeBootstrapConfig(config: BootstrapConfig, home?: string): void;
15
+ export declare function resolveDataDir(options?: PathResolutionOptions): string;
16
+ export declare function resolveDbPath(dataDir?: string): string;
17
+ export declare function ensureDataDir(dataDir?: string): string;
@@ -0,0 +1,47 @@
1
+ import { homedir } from 'node:os';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ export const APP_DIR_NAME = '.sshp';
5
+ export const DB_FILE_NAME = 'sshp.db';
6
+ export const CONFIG_FILE_NAME = 'config.json';
7
+ export function defaultDataDir(home = homedir()) {
8
+ return join(home, APP_DIR_NAME);
9
+ }
10
+ export function bootstrapConfigPath(home = homedir()) {
11
+ return join(defaultDataDir(home), CONFIG_FILE_NAME);
12
+ }
13
+ export function readBootstrapConfig(home = homedir()) {
14
+ const path = bootstrapConfigPath(home);
15
+ if (!existsSync(path))
16
+ return {};
17
+ try {
18
+ return JSON.parse(readFileSync(path, 'utf8'));
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ export function writeBootstrapConfig(config, home = homedir()) {
25
+ const path = bootstrapConfigPath(home);
26
+ mkdirSync(dirname(path), { recursive: true });
27
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
28
+ }
29
+ export function resolveDataDir(options = {}) {
30
+ const env = options.env ?? process.env;
31
+ const home = options.home ?? homedir();
32
+ const explicit = env.SSHP_DATA_DIR;
33
+ if (explicit && explicit.trim())
34
+ return resolve(explicit);
35
+ const config = readBootstrapConfig(home);
36
+ if (config.dataDir && config.dataDir.trim())
37
+ return resolve(config.dataDir);
38
+ return defaultDataDir(home);
39
+ }
40
+ export function resolveDbPath(dataDir = resolveDataDir()) {
41
+ return join(dataDir, DB_FILE_NAME);
42
+ }
43
+ export function ensureDataDir(dataDir = resolveDataDir()) {
44
+ mkdirSync(dataDir, { recursive: true });
45
+ return dataDir;
46
+ }
47
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7E,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAW9C,MAAM,UAAU,cAAc,CAAC,IAAI,GAAG,OAAO,EAAE;IAC7C,OAAO,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAI,GAAG,OAAO,EAAE;IAClD,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAI,GAAG,OAAO,EAAE;IAClD,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAoB,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAuB,EAAE,IAAI,GAAG,OAAO,EAAE;IAC5E,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAiC,EAAE;IAChE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC;IACnC,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAE5E,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAO,GAAG,cAAc,EAAE;IACtD,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAO,GAAG,cAAc,EAAE;IACtD,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export type Database = InstanceType<typeof DatabaseSync>;
3
+ export declare function openDatabase(dbPath?: string): Database;
4
+ export declare function openMigratedDatabase(dbPath?: string): Database;
@@ -0,0 +1,17 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { dirname } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { resolveDbPath } from '../config/paths.js';
5
+ import { runMigrations } from './migrations.js';
6
+ export function openDatabase(dbPath = resolveDbPath()) {
7
+ mkdirSync(dirname(dbPath), { recursive: true });
8
+ const db = new DatabaseSync(dbPath);
9
+ db.exec('PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;');
10
+ return db;
11
+ }
12
+ export function openMigratedDatabase(dbPath = resolveDbPath()) {
13
+ const db = openDatabase(dbPath);
14
+ runMigrations(db);
15
+ return db;
16
+ }
17
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIhD,MAAM,UAAU,YAAY,CAAC,MAAM,GAAG,aAAa,EAAE;IACnD,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,EAAE,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;IAChE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAM,GAAG,aAAa,EAAE;IAC3D,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Database } from './connection.js';
2
+ export declare const CURRENT_SCHEMA_VERSION = 1;
3
+ export declare function runMigrations(db: Database): void;
4
+ export declare function getSchemaVersion(db: Database): number;
@@ -0,0 +1,45 @@
1
+ export const CURRENT_SCHEMA_VERSION = 1;
2
+ export function runMigrations(db) {
3
+ db.exec(`
4
+ CREATE TABLE IF NOT EXISTS settings (
5
+ key TEXT PRIMARY KEY,
6
+ value TEXT NOT NULL
7
+ );
8
+
9
+ CREATE TABLE IF NOT EXISTS servers (
10
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
11
+ name TEXT NOT NULL UNIQUE,
12
+ host TEXT NOT NULL,
13
+ port INTEGER NOT NULL DEFAULT 22,
14
+ username TEXT NOT NULL,
15
+ auth_type TEXT NOT NULL,
16
+ encrypted_password TEXT,
17
+ encrypted_private_key TEXT,
18
+ encrypted_passphrase TEXT,
19
+ tags TEXT,
20
+ notes TEXT,
21
+ default_remote_path TEXT,
22
+ last_connected_at TEXT,
23
+ connection_count INTEGER NOT NULL DEFAULT 0,
24
+ created_at TEXT NOT NULL,
25
+ updated_at TEXT NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS connection_history (
29
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+ server_id INTEGER NOT NULL,
31
+ action TEXT NOT NULL,
32
+ local_path TEXT,
33
+ remote_path TEXT,
34
+ created_at TEXT NOT NULL,
35
+ FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
36
+ );
37
+
38
+ PRAGMA user_version = ${CURRENT_SCHEMA_VERSION};
39
+ `);
40
+ }
41
+ export function getSchemaVersion(db) {
42
+ const row = db.prepare('PRAGMA user_version').get();
43
+ return row?.user_version ?? 0;
44
+ }
45
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAmCkB,sBAAsB;GAC/C,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAY;IAC3C,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,EAA0C,CAAC;IAC5F,OAAO,GAAG,EAAE,YAAY,IAAI,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Database } from '../connection.js';
2
+ import type { CreateServerInput, ServerRecord } from '../../shared/types.js';
3
+ export declare class ServerRepository {
4
+ private readonly db;
5
+ constructor(db: Database);
6
+ create(input: CreateServerInput): ServerRecord;
7
+ list(): ServerRecord[];
8
+ findById(id: number): ServerRecord;
9
+ findByName(name: string): ServerRecord;
10
+ remove(id: number): void;
11
+ update(id: number, input: Partial<CreateServerInput>): ServerRecord;
12
+ recordConnection(id: number, action: string, localPath?: string, remotePath?: string): void;
13
+ }
@@ -0,0 +1,83 @@
1
+ import { NotFoundError } from '../../shared/errors.js';
2
+ function mapRow(row) {
3
+ return {
4
+ id: row.id,
5
+ name: row.name,
6
+ host: row.host,
7
+ port: row.port,
8
+ username: row.username,
9
+ authType: row.auth_type,
10
+ encryptedPassword: row.encrypted_password,
11
+ encryptedPrivateKey: row.encrypted_private_key,
12
+ encryptedPassphrase: row.encrypted_passphrase,
13
+ tags: row.tags,
14
+ notes: row.notes,
15
+ defaultRemotePath: row.default_remote_path,
16
+ lastConnectedAt: row.last_connected_at,
17
+ connectionCount: row.connection_count,
18
+ createdAt: row.created_at,
19
+ updatedAt: row.updated_at
20
+ };
21
+ }
22
+ export class ServerRepository {
23
+ db;
24
+ constructor(db) {
25
+ this.db = db;
26
+ }
27
+ create(input) {
28
+ const now = new Date().toISOString();
29
+ const result = this.db.prepare(`
30
+ INSERT INTO servers (
31
+ name, host, port, username, auth_type, encrypted_password,
32
+ encrypted_private_key, encrypted_passphrase, tags, notes,
33
+ default_remote_path, created_at, updated_at
34
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
35
+ `).run(input.name, input.host, input.port, input.username, input.authType, input.encryptedPassword ?? null, input.encryptedPrivateKey ?? null, input.encryptedPassphrase ?? null, input.tags ?? null, input.notes ?? null, input.defaultRemotePath ?? null, now, now);
36
+ return this.findById(Number(result.lastInsertRowid));
37
+ }
38
+ list() {
39
+ const rows = this.db.prepare('SELECT * FROM servers ORDER BY name COLLATE NOCASE').all();
40
+ return rows.map(mapRow);
41
+ }
42
+ findById(id) {
43
+ const row = this.db.prepare('SELECT * FROM servers WHERE id = ?').get(id);
44
+ if (!row)
45
+ throw new NotFoundError('Server', String(id));
46
+ return mapRow(row);
47
+ }
48
+ findByName(name) {
49
+ const row = this.db.prepare('SELECT * FROM servers WHERE name = ?').get(name);
50
+ if (!row)
51
+ throw new NotFoundError('Server', name);
52
+ return mapRow(row);
53
+ }
54
+ remove(id) {
55
+ this.db.prepare('DELETE FROM servers WHERE id = ?').run(id);
56
+ }
57
+ update(id, input) {
58
+ const current = this.findById(id);
59
+ const next = { ...current, ...input };
60
+ const now = new Date().toISOString();
61
+ this.db.prepare(`
62
+ UPDATE servers SET
63
+ name = ?, host = ?, port = ?, username = ?, auth_type = ?,
64
+ encrypted_password = ?, encrypted_private_key = ?, encrypted_passphrase = ?,
65
+ tags = ?, notes = ?, default_remote_path = ?, updated_at = ?
66
+ WHERE id = ?
67
+ `).run(next.name, next.host, next.port, next.username, next.authType, next.encryptedPassword ?? null, next.encryptedPrivateKey ?? null, next.encryptedPassphrase ?? null, next.tags ?? null, next.notes ?? null, next.defaultRemotePath ?? null, now, id);
68
+ return this.findById(id);
69
+ }
70
+ recordConnection(id, action, localPath, remotePath) {
71
+ const now = new Date().toISOString();
72
+ this.db.prepare(`
73
+ UPDATE servers
74
+ SET last_connected_at = ?, connection_count = connection_count + 1, updated_at = ?
75
+ WHERE id = ?
76
+ `).run(now, now, id);
77
+ this.db.prepare(`
78
+ INSERT INTO connection_history (server_id, action, local_path, remote_path, created_at)
79
+ VALUES (?, ?, ?, ?, ?)
80
+ `).run(id, action, localPath ?? null, remotePath ?? null, now);
81
+ }
82
+ }
83
+ //# sourceMappingURL=serverRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverRepository.js","sourceRoot":"","sources":["../../../src/db/repositories/serverRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Database } from '../connection.js';
2
+ export declare class SettingsRepository {
3
+ private readonly db;
4
+ constructor(db: Database);
5
+ get(key: string): string | null;
6
+ set(key: string, value: string): void;
7
+ delete(key: string): void;
8
+ }
@@ -0,0 +1,20 @@
1
+ export class SettingsRepository {
2
+ db;
3
+ constructor(db) {
4
+ this.db = db;
5
+ }
6
+ get(key) {
7
+ const row = this.db.prepare('SELECT value FROM settings WHERE key = ?').get(key);
8
+ return row?.value ?? null;
9
+ }
10
+ set(key, value) {
11
+ this.db.prepare(`
12
+ INSERT INTO settings (key, value) VALUES (?, ?)
13
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
14
+ `).run(key, value);
15
+ }
16
+ delete(key) {
17
+ this.db.prepare('DELETE FROM settings WHERE key = ?').run(key);
18
+ }
19
+ }
20
+ //# sourceMappingURL=settingsRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settingsRepository.js","sourceRoot":"","sources":["../../../src/db/repositories/settingsRepository.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,EAAY;QAAZ,OAAE,GAAF,EAAE,CAAU;IAAG,CAAC;IAE7C,GAAG,CAAC,GAAW;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,GAAG,CAAkC,CAAC;QAClH,OAAO,GAAG,EAAE,KAAK,IAAI,IAAI,CAAC;IAC5B,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAa;QAC5B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAGf,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export declare function exportVault(outFile: string, dataDir?: string): string;
2
+ export declare function importVault(inFile: string, dataDir?: string): string;
3
+ export declare function copyVaultDataDir(sourceDbPath: string, dataDir?: string): string;
@@ -0,0 +1,38 @@
1
+ import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { resolveDataDir, resolveDbPath } from '../config/paths.js';
5
+ import { MissingVaultError, SshpError } from '../shared/errors.js';
6
+ const MAGIC = 'SSHP_BACKUP_V1';
7
+ export function exportVault(outFile, dataDir = resolveDataDir()) {
8
+ const dbPath = resolveDbPath(dataDir);
9
+ if (!existsSync(dbPath))
10
+ throw new MissingVaultError(dataDir);
11
+ const payload = {
12
+ magic: MAGIC,
13
+ exportedAt: new Date().toISOString(),
14
+ database: readFileSync(dbPath).toString('base64')
15
+ };
16
+ const target = resolve(outFile);
17
+ mkdirSync(dirname(target), { recursive: true });
18
+ writeFileSync(target, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
19
+ return target;
20
+ }
21
+ export function importVault(inFile, dataDir = resolveDataDir()) {
22
+ const source = resolve(inFile);
23
+ const payload = JSON.parse(readFileSync(source, 'utf8'));
24
+ if (payload.magic !== MAGIC || !payload.database) {
25
+ throw new SshpError('Unsupported or corrupt SSHP backup file.', 'INVALID_BACKUP');
26
+ }
27
+ mkdirSync(dataDir, { recursive: true });
28
+ const dbPath = resolveDbPath(dataDir);
29
+ writeFileSync(dbPath, Buffer.from(payload.database, 'base64'));
30
+ return dbPath;
31
+ }
32
+ export function copyVaultDataDir(sourceDbPath, dataDir = resolveDataDir()) {
33
+ mkdirSync(dataDir, { recursive: true });
34
+ const dbPath = resolveDbPath(dataDir);
35
+ copyFileSync(sourceDbPath, dbPath);
36
+ return dbPath;
37
+ }
38
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/import-export/archive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEnE,MAAM,KAAK,GAAG,gBAAgB,CAAC;AAQ/B,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,OAAO,GAAG,cAAc,EAAE;IACrE,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAkB;QAC7B,KAAK,EAAE,KAAK;QACZ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAClD,CAAC;IACF,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,aAAa,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACvE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,OAAO,GAAG,cAAc,EAAE;IACpE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAkB,CAAC;IAC1E,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACjD,MAAM,IAAI,SAAS,CAAC,0CAA0C,EAAE,gBAAgB,CAAC,CAAC;IACpF,CAAC;IACD,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,YAAoB,EAAE,OAAO,GAAG,cAAc,EAAE;IAC/E,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { DirectoryEntry, ServerCredentials, ServerRecord } from '../shared/types.js';
2
+ export interface SftpConnectionOptions {
3
+ server: ServerRecord;
4
+ credentials: ServerCredentials;
5
+ }
6
+ export declare function listLocalDirectory(path: string): DirectoryEntry[];
7
+ export declare class SftpClient {
8
+ private conn?;
9
+ private sftp?;
10
+ connect({ server, credentials }: SftpConnectionOptions): Promise<void>;
11
+ close(): void;
12
+ listRemoteDirectory(path: string): Promise<DirectoryEntry[]>;
13
+ uploadFile(localPath: string, remotePath: string, overwrite?: boolean): Promise<void>;
14
+ downloadFile(remotePath: string, localPath: string, overwrite?: boolean): Promise<void>;
15
+ uploadRecursive(localPath: string, remotePath: string, overwrite?: boolean): Promise<void>;
16
+ downloadRecursive(remotePath: string, localPath: string, overwrite?: boolean): Promise<void>;
17
+ private remoteExists;
18
+ private mkdirRemote;
19
+ private requireSftp;
20
+ }
21
+ export declare function withSftp<T>(options: SftpConnectionOptions, fn: (client: SftpClient) => Promise<T>): Promise<T>;