@abhiseck/zssh 1.0.0 → 1.2.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.
- package/dist/commands/add.js +10 -40
- package/dist/commands/connect.js +63 -45
- package/dist/commands/list.js +9 -40
- package/dist/commands/remove.js +3 -36
- package/dist/commands/sync.js +16 -49
- package/dist/index.js +19 -10
- package/dist/lib/config.js +2 -2
- package/dist/lib/session.js +79 -0
- package/package.json +1 -1
- package/src/commands/add.ts +10 -3
- package/src/commands/connect.ts +58 -6
- package/src/commands/list.ts +3 -1
- package/src/commands/remove.ts +1 -1
- package/src/commands/sync.ts +1 -1
- package/src/index.ts +19 -5
- package/src/lib/config.ts +2 -1
- package/src/lib/session.ts +49 -0
- package/test-ppk.js +4 -0
package/dist/commands/add.js
CHANGED
|
@@ -1,47 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.addCommand = addCommand;
|
|
40
|
-
const
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
42
|
-
const uuid_1 = require("uuid");
|
|
43
9
|
async function addCommand(configManager) {
|
|
44
|
-
console.log(
|
|
10
|
+
console.log(chalk_1.default.blue("Adding new SSH connection..."));
|
|
45
11
|
const answers = await inquirer_1.default.prompt([
|
|
46
12
|
{
|
|
47
13
|
type: "input",
|
|
@@ -93,8 +59,12 @@ async function addCommand(configManager) {
|
|
|
93
59
|
message: "Description (optional):",
|
|
94
60
|
},
|
|
95
61
|
]);
|
|
62
|
+
const connections = configManager.getConnections();
|
|
63
|
+
const nextId = connections.length > 0
|
|
64
|
+
? (Math.max(...connections.map((c) => parseInt(c.id) || 0)) + 1).toString()
|
|
65
|
+
: "1";
|
|
96
66
|
const connection = {
|
|
97
|
-
id:
|
|
67
|
+
id: nextId,
|
|
98
68
|
alias: answers.alias,
|
|
99
69
|
host: answers.host,
|
|
100
70
|
port: parseInt(answers.port),
|
|
@@ -105,8 +75,8 @@ async function addCommand(configManager) {
|
|
|
105
75
|
createdAt: new Date().toISOString(),
|
|
106
76
|
};
|
|
107
77
|
configManager.addConnection(connection);
|
|
108
|
-
console.log(
|
|
109
|
-
console.log(`ID: ${
|
|
78
|
+
console.log(chalk_1.default.green(`\nConnection added successfully!`));
|
|
79
|
+
console.log(`ID: ${chalk_1.default.cyan(connection.id)}`);
|
|
110
80
|
if (connection.alias)
|
|
111
|
-
console.log(`Alias: ${
|
|
81
|
+
console.log(`Alias: ${chalk_1.default.cyan(connection.alias)}`);
|
|
112
82
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -1,56 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.connectCommand = connectCommand;
|
|
37
|
-
const
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
38
8
|
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const ssh2_1 = require("ssh2");
|
|
39
13
|
async function isSshpassInstalled() {
|
|
40
14
|
return new Promise((resolve) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
15
|
+
const isWin = process.platform === "win32";
|
|
16
|
+
const checkCommand = isWin ? "where sshpass" : "command -v sshpass";
|
|
17
|
+
(0, child_process_1.exec)(checkCommand, (error) => {
|
|
18
|
+
resolve(!error);
|
|
44
19
|
});
|
|
45
20
|
});
|
|
46
21
|
}
|
|
47
22
|
async function connectCommand(configManager, idOrAlias) {
|
|
48
23
|
const conn = configManager.getConnection(idOrAlias);
|
|
49
24
|
if (!conn) {
|
|
50
|
-
console.log(
|
|
25
|
+
console.log(chalk_1.default.red(`Connection not found: ${idOrAlias}`));
|
|
51
26
|
return;
|
|
52
27
|
}
|
|
53
|
-
console.log(
|
|
28
|
+
console.log(chalk_1.default.blue(`Connecting to ${conn.alias || conn.id} (${conn.username}@${conn.host})...`));
|
|
54
29
|
const args = [];
|
|
55
30
|
// Port
|
|
56
31
|
if (conn.port) {
|
|
@@ -58,7 +33,50 @@ async function connectCommand(configManager, idOrAlias) {
|
|
|
58
33
|
}
|
|
59
34
|
// Identity file
|
|
60
35
|
if (conn.privateKeyPath) {
|
|
61
|
-
|
|
36
|
+
let keyPath = conn.privateKeyPath;
|
|
37
|
+
if (keyPath.toLowerCase().endsWith(".ppk")) {
|
|
38
|
+
try {
|
|
39
|
+
let realPath = keyPath;
|
|
40
|
+
if (realPath.startsWith("~/")) {
|
|
41
|
+
realPath = path_1.default.join(os_1.default.homedir(), realPath.slice(2));
|
|
42
|
+
}
|
|
43
|
+
const keyData = fs_1.default.readFileSync(realPath);
|
|
44
|
+
const parsedKeys = ssh2_1.utils.parseKey(keyData);
|
|
45
|
+
if (parsedKeys instanceof Error) {
|
|
46
|
+
console.log(chalk_1.default.red(`Failed to parse PPK file: ${parsedKeys.message}`));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const parsedKey = Array.isArray(parsedKeys)
|
|
50
|
+
? parsedKeys[0]
|
|
51
|
+
: parsedKeys;
|
|
52
|
+
const tempKeyPath = path_1.default.join(os_1.default.tmpdir(), `sshc_key_${conn.id}.pem`);
|
|
53
|
+
fs_1.default.writeFileSync(tempKeyPath, parsedKey.getPrivatePEM(), {
|
|
54
|
+
mode: 0o600,
|
|
55
|
+
});
|
|
56
|
+
keyPath = tempKeyPath;
|
|
57
|
+
const cleanUp = () => {
|
|
58
|
+
try {
|
|
59
|
+
if (fs_1.default.existsSync(tempKeyPath))
|
|
60
|
+
fs_1.default.unlinkSync(tempKeyPath);
|
|
61
|
+
}
|
|
62
|
+
catch (e) { }
|
|
63
|
+
};
|
|
64
|
+
process.on("exit", cleanUp);
|
|
65
|
+
process.on("SIGINT", () => {
|
|
66
|
+
cleanUp();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
process.on("SIGTERM", () => {
|
|
70
|
+
cleanUp();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.log(chalk_1.default.red(`Error processing PPK file: ${err.message}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
args.push("-i", keyPath);
|
|
62
80
|
}
|
|
63
81
|
// Destination
|
|
64
82
|
args.push(`${conn.username}@${conn.host}`);
|
|
@@ -77,9 +95,9 @@ async function connectCommand(configManager, idOrAlias) {
|
|
|
77
95
|
finalArgs = ["-e", "ssh", ...args]; // -e means take password from env
|
|
78
96
|
}
|
|
79
97
|
else {
|
|
80
|
-
console.log(
|
|
81
|
-
console.log(
|
|
82
|
-
console.log(
|
|
98
|
+
console.log(chalk_1.default.yellow('Warning: "sshpass" is not installed.'));
|
|
99
|
+
console.log(chalk_1.default.yellow("Cannot auto-fill password. You will be prompted by SSH."));
|
|
100
|
+
console.log(chalk_1.default.dim('Install sshpass (e.g. "brew install sshpass" on Mac) to enable auto-login.'));
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
const child = (0, child_process_1.spawn)(command, finalArgs, {
|
|
@@ -88,10 +106,10 @@ async function connectCommand(configManager, idOrAlias) {
|
|
|
88
106
|
});
|
|
89
107
|
child.on("close", (code) => {
|
|
90
108
|
if (code !== 0) {
|
|
91
|
-
console.log(
|
|
109
|
+
console.log(chalk_1.default.red(`\nSSH session ended with code ${code}`));
|
|
92
110
|
}
|
|
93
111
|
else {
|
|
94
|
-
console.log(
|
|
112
|
+
console.log(chalk_1.default.green("\nDisconnected."));
|
|
95
113
|
}
|
|
96
114
|
});
|
|
97
115
|
}
|
package/dist/commands/list.js
CHANGED
|
@@ -1,57 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.listCommand = listCommand;
|
|
40
|
-
const
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
8
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
42
9
|
async function listCommand(configManager) {
|
|
43
10
|
const connections = configManager.getConnections();
|
|
44
11
|
if (connections.length === 0) {
|
|
45
|
-
console.log(
|
|
12
|
+
console.log(chalk_1.default.yellow('No connections found. Use "zssh add" to add one.'));
|
|
46
13
|
return;
|
|
47
14
|
}
|
|
48
15
|
const table = new cli_table3_1.default({
|
|
49
16
|
head: [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
chalk_1.default.cyan("ID"),
|
|
18
|
+
chalk_1.default.cyan("Alias"),
|
|
19
|
+
chalk_1.default.cyan("Host"),
|
|
20
|
+
chalk_1.default.cyan("User"),
|
|
21
|
+
chalk_1.default.cyan("Port"),
|
|
22
|
+
chalk_1.default.cyan("Description"),
|
|
55
23
|
],
|
|
56
24
|
style: { head: [], border: [] },
|
|
57
25
|
});
|
|
@@ -62,6 +30,7 @@ async function listCommand(configManager) {
|
|
|
62
30
|
conn.host,
|
|
63
31
|
conn.username,
|
|
64
32
|
conn.port,
|
|
33
|
+
conn.description || "",
|
|
65
34
|
]);
|
|
66
35
|
});
|
|
67
36
|
console.log(table.toString());
|
package/dist/commands/remove.js
CHANGED
|
@@ -1,48 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.removeCommand = removeCommand;
|
|
40
|
-
const
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
42
9
|
async function removeCommand(configManager, idOrAlias) {
|
|
43
10
|
const conn = configManager.getConnection(idOrAlias);
|
|
44
11
|
if (!conn) {
|
|
45
|
-
console.log(
|
|
12
|
+
console.log(chalk_1.default.red(`Connection not found: ${idOrAlias}`));
|
|
46
13
|
return;
|
|
47
14
|
}
|
|
48
15
|
const { confirm } = await inquirer_1.default.prompt([
|
|
@@ -55,6 +22,6 @@ async function removeCommand(configManager, idOrAlias) {
|
|
|
55
22
|
]);
|
|
56
23
|
if (confirm) {
|
|
57
24
|
configManager.removeConnection(idOrAlias);
|
|
58
|
-
console.log(
|
|
25
|
+
console.log(chalk_1.default.green("Connection removed."));
|
|
59
26
|
}
|
|
60
27
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -39,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
6
|
exports.saveCommand = saveCommand;
|
|
40
7
|
exports.syncCommand = syncCommand;
|
|
41
8
|
const axios_1 = __importDefault(require("axios"));
|
|
42
|
-
const
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
10
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
44
11
|
const GITHUB_API = "https://api.github.com";
|
|
45
12
|
async function getGithubToken(configManager) {
|
|
@@ -63,28 +30,28 @@ async function saveCommand(configManager) {
|
|
|
63
30
|
const config = configManager.getConfig();
|
|
64
31
|
const encryptedContent = configManager.getEncryptedContent(); // We need a way to get raw encrypted string
|
|
65
32
|
if (!encryptedContent) {
|
|
66
|
-
console.log(
|
|
33
|
+
console.log(chalk_1.default.red("Error: Could not retrieve encrypted config."));
|
|
67
34
|
return;
|
|
68
35
|
}
|
|
69
36
|
const files = {
|
|
70
|
-
"
|
|
37
|
+
"zssh_config.lock": {
|
|
71
38
|
content: encryptedContent,
|
|
72
39
|
},
|
|
73
40
|
};
|
|
74
41
|
try {
|
|
75
42
|
if (config.gistId) {
|
|
76
43
|
// Update existing Gist
|
|
77
|
-
console.log(
|
|
44
|
+
console.log(chalk_1.default.blue("Updating existing Gist..."));
|
|
78
45
|
await axios_1.default.patch(`${GITHUB_API}/gists/${config.gistId}`, { files }, {
|
|
79
46
|
headers: { Authorization: `token ${token}` },
|
|
80
47
|
});
|
|
81
|
-
console.log(
|
|
48
|
+
console.log(chalk_1.default.green("Config saved to Gist successfully."));
|
|
82
49
|
}
|
|
83
50
|
else {
|
|
84
51
|
// Create new Gist
|
|
85
|
-
console.log(
|
|
52
|
+
console.log(chalk_1.default.blue("Creating new Gist..."));
|
|
86
53
|
const response = await axios_1.default.post(`${GITHUB_API}/gists`, {
|
|
87
|
-
description: "
|
|
54
|
+
description: "ZSSH Encrypted Config",
|
|
88
55
|
public: false,
|
|
89
56
|
files,
|
|
90
57
|
}, {
|
|
@@ -92,12 +59,12 @@ async function saveCommand(configManager) {
|
|
|
92
59
|
});
|
|
93
60
|
const gistId = response.data.id;
|
|
94
61
|
configManager.setGistId(gistId);
|
|
95
|
-
console.log(
|
|
96
|
-
console.log(
|
|
62
|
+
console.log(chalk_1.default.green(`Gist created with ID: ${gistId}`));
|
|
63
|
+
console.log(chalk_1.default.green("Config saved to Gist successfully."));
|
|
97
64
|
}
|
|
98
65
|
}
|
|
99
66
|
catch (error) {
|
|
100
|
-
console.error(
|
|
67
|
+
console.error(chalk_1.default.red("Failed to save to Gist:"), error.response?.data?.message || error.message);
|
|
101
68
|
}
|
|
102
69
|
}
|
|
103
70
|
async function syncCommand(configManager) {
|
|
@@ -115,13 +82,13 @@ async function syncCommand(configManager) {
|
|
|
115
82
|
configManager.setGistId(gistId);
|
|
116
83
|
}
|
|
117
84
|
try {
|
|
118
|
-
console.log(
|
|
85
|
+
console.log(chalk_1.default.blue("Fetching Gist..."));
|
|
119
86
|
const response = await axios_1.default.get(`${GITHUB_API}/gists/${gistId}`, {
|
|
120
87
|
headers: { Authorization: `token ${token}` },
|
|
121
88
|
});
|
|
122
|
-
const file = response.data.files["
|
|
89
|
+
const file = response.data.files["zssh_config.lock"];
|
|
123
90
|
if (!file) {
|
|
124
|
-
console.log(
|
|
91
|
+
console.log(chalk_1.default.red("Invalid Gist: zssh_config.lock not found."));
|
|
125
92
|
return;
|
|
126
93
|
}
|
|
127
94
|
const encryptedContent = file.content;
|
|
@@ -133,13 +100,13 @@ async function syncCommand(configManager) {
|
|
|
133
100
|
// Use `save` to push.
|
|
134
101
|
const merged = configManager.mergeEncrypted(encryptedContent);
|
|
135
102
|
if (merged) {
|
|
136
|
-
console.log(
|
|
103
|
+
console.log(chalk_1.default.green("Sync completed. Remote changes merged into local config."));
|
|
137
104
|
}
|
|
138
105
|
else {
|
|
139
|
-
console.log(
|
|
106
|
+
console.log(chalk_1.default.yellow("Sync completed but no changes were made or merge failed."));
|
|
140
107
|
}
|
|
141
108
|
}
|
|
142
109
|
catch (error) {
|
|
143
|
-
console.error(
|
|
110
|
+
console.error(chalk_1.default.red("Failed to sync with Gist:"), error.response?.data?.message || error.message);
|
|
144
111
|
}
|
|
145
112
|
}
|
package/dist/index.js
CHANGED
|
@@ -37,13 +37,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
const
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
41
|
const commander_1 = require("commander");
|
|
42
42
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
43
43
|
const config_1 = require("./lib/config");
|
|
44
|
+
const session_1 = require("./lib/session");
|
|
44
45
|
const program = new commander_1.Command();
|
|
45
46
|
const configManager = new config_1.ConfigManager();
|
|
46
|
-
async function promptForMasterPassword(isNew = false) {
|
|
47
|
+
async function promptForMasterPassword(isNew = false, forcePrompt = false) {
|
|
48
|
+
if (!isNew && !forcePrompt) {
|
|
49
|
+
const sessionPass = await (0, session_1.getSessionPassword)();
|
|
50
|
+
if (sessionPass)
|
|
51
|
+
return sessionPass;
|
|
52
|
+
}
|
|
47
53
|
const { password } = await inquirer_1.default.prompt([
|
|
48
54
|
{
|
|
49
55
|
type: "password",
|
|
@@ -56,20 +62,22 @@ async function promptForMasterPassword(isNew = false) {
|
|
|
56
62
|
]);
|
|
57
63
|
return password;
|
|
58
64
|
}
|
|
59
|
-
async function initConfig() {
|
|
65
|
+
async function initConfig(requirePassword = false) {
|
|
60
66
|
if (configManager.isConfigExists()) {
|
|
61
|
-
const password = await promptForMasterPassword();
|
|
67
|
+
const password = await promptForMasterPassword(false, requirePassword);
|
|
62
68
|
configManager.setMasterKey(password);
|
|
63
69
|
try {
|
|
64
70
|
configManager.load();
|
|
71
|
+
await (0, session_1.saveSessionPassword)(password);
|
|
65
72
|
}
|
|
66
73
|
catch (e) {
|
|
67
|
-
|
|
74
|
+
await (0, session_1.clearSession)();
|
|
75
|
+
console.error(chalk_1.default.red("Invalid password or corrupted config."));
|
|
68
76
|
process.exit(1);
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
else {
|
|
72
|
-
console.log(
|
|
80
|
+
console.log(chalk_1.default.yellow("No existing config found. Initializing..."));
|
|
73
81
|
const password = await promptForMasterPassword(true);
|
|
74
82
|
const { confirm } = await inquirer_1.default.prompt([
|
|
75
83
|
{
|
|
@@ -80,12 +88,13 @@ async function initConfig() {
|
|
|
80
88
|
},
|
|
81
89
|
]);
|
|
82
90
|
if (password !== confirm) {
|
|
83
|
-
console.error(
|
|
91
|
+
console.error(chalk_1.default.red("Passwords do not match."));
|
|
84
92
|
process.exit(1);
|
|
85
93
|
}
|
|
86
94
|
configManager.setMasterKey(password);
|
|
87
95
|
configManager.save(); // Create empty encrypted file
|
|
88
|
-
|
|
96
|
+
await (0, session_1.saveSessionPassword)(password);
|
|
97
|
+
console.log(chalk_1.default.green("Config initialized!"));
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
program
|
|
@@ -137,7 +146,7 @@ program
|
|
|
137
146
|
.command("save")
|
|
138
147
|
.description("Save config to GitHub Gist")
|
|
139
148
|
.action(async () => {
|
|
140
|
-
await initConfig();
|
|
149
|
+
await initConfig(true);
|
|
141
150
|
const { saveCommand } = await Promise.resolve().then(() => __importStar(require("./commands/sync")));
|
|
142
151
|
await saveCommand(configManager);
|
|
143
152
|
});
|
|
@@ -145,7 +154,7 @@ program
|
|
|
145
154
|
.command("sync")
|
|
146
155
|
.description("Sync config from GitHub Gist")
|
|
147
156
|
.action(async () => {
|
|
148
|
-
await initConfig();
|
|
157
|
+
await initConfig(true);
|
|
149
158
|
const { syncCommand } = await Promise.resolve().then(() => __importStar(require("./commands/sync")));
|
|
150
159
|
await syncCommand(configManager);
|
|
151
160
|
});
|
package/dist/lib/config.js
CHANGED
|
@@ -38,7 +38,7 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const crypto_1 = require("./crypto");
|
|
41
|
-
const CONFIG_PATH = path.join(os.homedir(), ".
|
|
41
|
+
const CONFIG_PATH = path.join(os.homedir(), ".zssh.json");
|
|
42
42
|
class ConfigManager {
|
|
43
43
|
constructor() {
|
|
44
44
|
this.masterKey = null;
|
|
@@ -158,7 +158,7 @@ class ConfigManager {
|
|
|
158
158
|
this.save();
|
|
159
159
|
}
|
|
160
160
|
getConnection(idOrAlias) {
|
|
161
|
-
return this.config.connections.find((c) => c.id === idOrAlias || c.alias === idOrAlias);
|
|
161
|
+
return this.config.connections.find((c) => c.id === idOrAlias || c.alias.toLowerCase() === idOrAlias.toLowerCase());
|
|
162
162
|
}
|
|
163
163
|
isConfigExists() {
|
|
164
164
|
return fs.existsSync(CONFIG_PATH);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getSessionPassword = getSessionPassword;
|
|
37
|
+
exports.saveSessionPassword = saveSessionPassword;
|
|
38
|
+
exports.clearSession = clearSession;
|
|
39
|
+
const keytar = __importStar(require("keytar"));
|
|
40
|
+
const SERVICE_NAME = "zssh";
|
|
41
|
+
const ACCOUNT_NAME = "session";
|
|
42
|
+
const TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
43
|
+
async function getSessionPassword() {
|
|
44
|
+
try {
|
|
45
|
+
const dataString = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
46
|
+
if (!dataString)
|
|
47
|
+
return null;
|
|
48
|
+
const data = JSON.parse(dataString);
|
|
49
|
+
if (Date.now() > data.expiresAt) {
|
|
50
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Refresh expiration smoothly
|
|
54
|
+
data.expiresAt = Date.now() + TIMEOUT_MS;
|
|
55
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(data));
|
|
56
|
+
return data.password;
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function saveSessionPassword(password) {
|
|
63
|
+
try {
|
|
64
|
+
const data = {
|
|
65
|
+
password,
|
|
66
|
+
expiresAt: Date.now() + TIMEOUT_MS,
|
|
67
|
+
};
|
|
68
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(data));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// safely ignore keychain errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function clearSession() {
|
|
75
|
+
try {
|
|
76
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
77
|
+
}
|
|
78
|
+
catch (e) { }
|
|
79
|
+
}
|
package/package.json
CHANGED
package/src/commands/add.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
|
-
import { v4 as uuidv4 } from "uuid";
|
|
4
3
|
import { ConfigManager } from "../lib/config";
|
|
5
4
|
import { SSHConnection } from "../lib/types";
|
|
6
5
|
|
|
@@ -60,8 +59,16 @@ export async function addCommand(configManager: ConfigManager) {
|
|
|
60
59
|
},
|
|
61
60
|
]);
|
|
62
61
|
|
|
62
|
+
const connections = configManager.getConnections();
|
|
63
|
+
const nextId =
|
|
64
|
+
connections.length > 0
|
|
65
|
+
? (
|
|
66
|
+
Math.max(...connections.map((c) => parseInt(c.id) || 0)) + 1
|
|
67
|
+
).toString()
|
|
68
|
+
: "1";
|
|
69
|
+
|
|
63
70
|
const connection: SSHConnection = {
|
|
64
|
-
id:
|
|
71
|
+
id: nextId,
|
|
65
72
|
alias: answers.alias,
|
|
66
73
|
host: answers.host,
|
|
67
74
|
port: parseInt(answers.port),
|
package/src/commands/connect.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { spawn } from "child_process";
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { exec, spawn } from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { utils } from "ssh2";
|
|
3
7
|
import { ConfigManager } from "../lib/config";
|
|
4
8
|
|
|
5
9
|
async function isSshpassInstalled(): Promise<boolean> {
|
|
6
10
|
return new Promise((resolve) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
const isWin = process.platform === "win32";
|
|
12
|
+
const checkCommand = isWin ? "where sshpass" : "command -v sshpass";
|
|
13
|
+
|
|
14
|
+
exec(checkCommand, (error) => {
|
|
15
|
+
resolve(!error);
|
|
10
16
|
});
|
|
11
17
|
});
|
|
12
18
|
}
|
|
@@ -37,7 +43,53 @@ export async function connectCommand(
|
|
|
37
43
|
|
|
38
44
|
// Identity file
|
|
39
45
|
if (conn.privateKeyPath) {
|
|
40
|
-
|
|
46
|
+
let keyPath = conn.privateKeyPath;
|
|
47
|
+
if (keyPath.toLowerCase().endsWith(".ppk")) {
|
|
48
|
+
try {
|
|
49
|
+
let realPath = keyPath;
|
|
50
|
+
if (realPath.startsWith("~/")) {
|
|
51
|
+
realPath = path.join(os.homedir(), realPath.slice(2));
|
|
52
|
+
}
|
|
53
|
+
const keyData = fs.readFileSync(realPath);
|
|
54
|
+
const parsedKeys = utils.parseKey(keyData);
|
|
55
|
+
|
|
56
|
+
if (parsedKeys instanceof Error) {
|
|
57
|
+
console.log(
|
|
58
|
+
chalk.red(`Failed to parse PPK file: ${parsedKeys.message}`),
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parsedKey = Array.isArray(parsedKeys)
|
|
64
|
+
? parsedKeys[0]
|
|
65
|
+
: parsedKeys;
|
|
66
|
+
const tempKeyPath = path.join(os.tmpdir(), `sshc_key_${conn.id}.pem`);
|
|
67
|
+
|
|
68
|
+
fs.writeFileSync(tempKeyPath, parsedKey.getPrivatePEM(), {
|
|
69
|
+
mode: 0o600,
|
|
70
|
+
});
|
|
71
|
+
keyPath = tempKeyPath;
|
|
72
|
+
|
|
73
|
+
const cleanUp = () => {
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(tempKeyPath)) fs.unlinkSync(tempKeyPath);
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
};
|
|
78
|
+
process.on("exit", cleanUp);
|
|
79
|
+
process.on("SIGINT", () => {
|
|
80
|
+
cleanUp();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
83
|
+
process.on("SIGTERM", () => {
|
|
84
|
+
cleanUp();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
});
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
console.log(chalk.red(`Error processing PPK file: ${err.message}`));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
args.push("-i", keyPath);
|
|
41
93
|
}
|
|
42
94
|
|
|
43
95
|
// Destination
|
package/src/commands/list.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from "chalk";
|
|
2
2
|
import Table from "cli-table3";
|
|
3
3
|
import { ConfigManager } from "../lib/config";
|
|
4
4
|
|
|
@@ -19,6 +19,7 @@ export async function listCommand(configManager: ConfigManager) {
|
|
|
19
19
|
chalk.cyan("Host"),
|
|
20
20
|
chalk.cyan("User"),
|
|
21
21
|
chalk.cyan("Port"),
|
|
22
|
+
chalk.cyan("Description"),
|
|
22
23
|
],
|
|
23
24
|
style: { head: [], border: [] },
|
|
24
25
|
});
|
|
@@ -30,6 +31,7 @@ export async function listCommand(configManager: ConfigManager) {
|
|
|
30
31
|
conn.host,
|
|
31
32
|
conn.username,
|
|
32
33
|
conn.port,
|
|
34
|
+
conn.description || "",
|
|
33
35
|
]);
|
|
34
36
|
});
|
|
35
37
|
|
package/src/commands/remove.ts
CHANGED
package/src/commands/sync.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import chalk from "chalk";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
5
|
import { ConfigManager } from "./lib/config";
|
|
6
|
+
import {
|
|
7
|
+
clearSession,
|
|
8
|
+
getSessionPassword,
|
|
9
|
+
saveSessionPassword,
|
|
10
|
+
} from "./lib/session";
|
|
6
11
|
|
|
7
12
|
const program = new Command();
|
|
8
13
|
const configManager = new ConfigManager();
|
|
9
14
|
|
|
10
15
|
async function promptForMasterPassword(
|
|
11
16
|
isNew: boolean = false,
|
|
17
|
+
forcePrompt: boolean = false,
|
|
12
18
|
): Promise<string> {
|
|
19
|
+
if (!isNew && !forcePrompt) {
|
|
20
|
+
const sessionPass = await getSessionPassword();
|
|
21
|
+
if (sessionPass) return sessionPass;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
const { password } = await inquirer.prompt([
|
|
14
25
|
{
|
|
15
26
|
type: "password",
|
|
@@ -23,13 +34,15 @@ async function promptForMasterPassword(
|
|
|
23
34
|
return password;
|
|
24
35
|
}
|
|
25
36
|
|
|
26
|
-
async function initConfig() {
|
|
37
|
+
async function initConfig(requirePassword = false) {
|
|
27
38
|
if (configManager.isConfigExists()) {
|
|
28
|
-
const password = await promptForMasterPassword();
|
|
39
|
+
const password = await promptForMasterPassword(false, requirePassword);
|
|
29
40
|
configManager.setMasterKey(password);
|
|
30
41
|
try {
|
|
31
42
|
configManager.load();
|
|
43
|
+
await saveSessionPassword(password);
|
|
32
44
|
} catch (e) {
|
|
45
|
+
await clearSession();
|
|
33
46
|
console.error(chalk.red("Invalid password or corrupted config."));
|
|
34
47
|
process.exit(1);
|
|
35
48
|
}
|
|
@@ -52,6 +65,7 @@ async function initConfig() {
|
|
|
52
65
|
|
|
53
66
|
configManager.setMasterKey(password);
|
|
54
67
|
configManager.save(); // Create empty encrypted file
|
|
68
|
+
await saveSessionPassword(password);
|
|
55
69
|
console.log(chalk.green("Config initialized!"));
|
|
56
70
|
}
|
|
57
71
|
}
|
|
@@ -113,7 +127,7 @@ program
|
|
|
113
127
|
.command("save")
|
|
114
128
|
.description("Save config to GitHub Gist")
|
|
115
129
|
.action(async () => {
|
|
116
|
-
await initConfig();
|
|
130
|
+
await initConfig(true);
|
|
117
131
|
const { saveCommand } = await import("./commands/sync");
|
|
118
132
|
await saveCommand(configManager);
|
|
119
133
|
});
|
|
@@ -122,7 +136,7 @@ program
|
|
|
122
136
|
.command("sync")
|
|
123
137
|
.description("Sync config from GitHub Gist")
|
|
124
138
|
.action(async () => {
|
|
125
|
-
await initConfig();
|
|
139
|
+
await initConfig(true);
|
|
126
140
|
const { syncCommand } = await import("./commands/sync");
|
|
127
141
|
await syncCommand(configManager);
|
|
128
142
|
});
|
package/src/lib/config.ts
CHANGED
|
@@ -150,7 +150,8 @@ export class ConfigManager {
|
|
|
150
150
|
|
|
151
151
|
public getConnection(idOrAlias: string): SSHConnection | undefined {
|
|
152
152
|
return this.config.connections.find(
|
|
153
|
-
(c) =>
|
|
153
|
+
(c) =>
|
|
154
|
+
c.id === idOrAlias || c.alias.toLowerCase() === idOrAlias.toLowerCase(),
|
|
154
155
|
);
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as keytar from "keytar";
|
|
2
|
+
|
|
3
|
+
const SERVICE_NAME = "zssh";
|
|
4
|
+
const ACCOUNT_NAME = "session";
|
|
5
|
+
const TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
6
|
+
|
|
7
|
+
interface SessionData {
|
|
8
|
+
password: string;
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getSessionPassword(): Promise<string | null> {
|
|
13
|
+
try {
|
|
14
|
+
const dataString = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
15
|
+
if (!dataString) return null;
|
|
16
|
+
|
|
17
|
+
const data: SessionData = JSON.parse(dataString);
|
|
18
|
+
if (Date.now() > data.expiresAt) {
|
|
19
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Refresh expiration smoothly
|
|
24
|
+
data.expiresAt = Date.now() + TIMEOUT_MS;
|
|
25
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(data));
|
|
26
|
+
|
|
27
|
+
return data.password;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function saveSessionPassword(password: string): Promise<void> {
|
|
34
|
+
try {
|
|
35
|
+
const data: SessionData = {
|
|
36
|
+
password,
|
|
37
|
+
expiresAt: Date.now() + TIMEOUT_MS,
|
|
38
|
+
};
|
|
39
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(data));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
// safely ignore keychain errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function clearSession(): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
}
|
package/test-ppk.js
ADDED