@ebowwa/terminal 0.3.6 → 0.3.8
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/cpufeatures-7nkbz0at.node +0 -0
- package/dist/index.d.ts +1 -14
- package/dist/index.js +477 -149
- package/dist/lib/activities.d.ts +11 -0
- package/dist/lib/hetzner/client.d.ts +6 -0
- package/dist/lib/hetzner/types.d.ts +13 -0
- package/dist/manager.d.ts +22 -1
- package/dist/mcp/index.js +99 -149
- package/dist/sessions.d.ts +3 -1
- package/dist/sshcrypto-jh8dt6n3.node +0 -0
- package/dist/tmux-manager.d.ts +4 -3
- package/package.json +68 -19
- package/dist/mcp/index.d.ts +0 -8
- package/dist/mcp/stdio.d.ts +0 -8
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -19,17 +19,4 @@ export { generateLocalSessionName, isLocalTmuxInstalled, listLocalSessions, hasL
|
|
|
19
19
|
export { RESOURCE_COMMANDS } from "./resources.js";
|
|
20
20
|
export type { ResourceCommand } from "./resources.js";
|
|
21
21
|
export { detectNetworkError, formatNetworkError, type NetworkErrorDetails, } from "./network-error-detector.js";
|
|
22
|
-
|
|
23
|
-
// SSH Config management
|
|
24
|
-
export {
|
|
25
|
-
addSSHConfigEntry,
|
|
26
|
-
removeSSHConfigEntry,
|
|
27
|
-
updateSSHConfigHost,
|
|
28
|
-
listSSHConfigEntries,
|
|
29
|
-
validateSSHConnection,
|
|
30
|
-
ensureCorrectSSHKey,
|
|
31
|
-
waitForSSHReady,
|
|
32
|
-
syncNodesToSSHConfig,
|
|
33
|
-
type SSHConfigEntry,
|
|
34
|
-
type SyncResult,
|
|
35
|
-
} from "./config.js";
|
|
22
|
+
export { addSSHConfigEntry, removeSSHConfigEntry, updateSSHConfigHost, listSSHConfigEntries, validateSSHConnection, ensureCorrectSSHKey, waitForSSHReady, syncNodesToSSHConfig, type SSHConfigEntry, type SyncResult, } from "./config.js";
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,6 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
15
15
|
});
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
18
|
var __export = (target, all) => {
|
|
20
19
|
for (var name in all)
|
|
21
20
|
__defProp(target, name, {
|
|
@@ -444,35 +443,34 @@ var init_error = __esm(() => {
|
|
|
444
443
|
};
|
|
445
444
|
});
|
|
446
445
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
password: zod_1.z.string().optional()
|
|
446
|
+
// ../codespaces-types/runtime/ssh.js
|
|
447
|
+
import { z } from "zod";
|
|
448
|
+
var SSHOptionsSchema, SSHCommandSchema, SCPOptionsSchema, FilesListOptionsSchema, FilePreviewOptionsSchema;
|
|
449
|
+
var init_ssh = __esm(() => {
|
|
450
|
+
SSHOptionsSchema = z.object({
|
|
451
|
+
host: z.string().min(1, "Host is required"),
|
|
452
|
+
user: z.string().default("root"),
|
|
453
|
+
timeout: z.number().int().positive().default(5),
|
|
454
|
+
port: z.number().int().positive().max(65535).default(22),
|
|
455
|
+
keyPath: z.string().optional(),
|
|
456
|
+
password: z.string().optional()
|
|
459
457
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
source:
|
|
463
|
-
destination:
|
|
464
|
-
recursive:
|
|
465
|
-
preserve:
|
|
458
|
+
SSHCommandSchema = z.string().min(1, "Command cannot be empty");
|
|
459
|
+
SCPOptionsSchema = SSHOptionsSchema.extend({
|
|
460
|
+
source: z.string().min(1, "Source is required"),
|
|
461
|
+
destination: z.string().min(1, "Destination is required"),
|
|
462
|
+
recursive: z.boolean().default(false),
|
|
463
|
+
preserve: z.boolean().default(false)
|
|
466
464
|
});
|
|
467
|
-
|
|
468
|
-
host:
|
|
469
|
-
user:
|
|
470
|
-
path:
|
|
465
|
+
FilesListOptionsSchema = z.object({
|
|
466
|
+
host: z.string().min(1, "Host is required"),
|
|
467
|
+
user: z.string().default("root"),
|
|
468
|
+
path: z.string().default(".")
|
|
471
469
|
});
|
|
472
|
-
|
|
473
|
-
host:
|
|
474
|
-
user:
|
|
475
|
-
path:
|
|
470
|
+
FilePreviewOptionsSchema = z.object({
|
|
471
|
+
host: z.string().min(1, "Host is required"),
|
|
472
|
+
user: z.string().default("root"),
|
|
473
|
+
path: z.string().min(1, "Path is required")
|
|
476
474
|
});
|
|
477
475
|
});
|
|
478
476
|
|
|
@@ -482,11 +480,11 @@ __export(exports_client, {
|
|
|
482
480
|
execSSH: () => execSSH
|
|
483
481
|
});
|
|
484
482
|
async function execSSH(command, options) {
|
|
485
|
-
const validatedCommand =
|
|
483
|
+
const validatedCommand = SSHCommandSchema.safeParse(command);
|
|
486
484
|
if (!validatedCommand.success) {
|
|
487
485
|
throw new Error(`Invalid SSH command: ${validatedCommand.error.issues.map((i) => i.message).join(", ")}`);
|
|
488
486
|
}
|
|
489
|
-
const validatedOptions =
|
|
487
|
+
const validatedOptions = SSHOptionsSchema.safeParse(options);
|
|
490
488
|
if (!validatedOptions.success) {
|
|
491
489
|
throw new Error(`Invalid SSH options: ${validatedOptions.error.issues.map((i) => i.message).join(", ")}`);
|
|
492
490
|
}
|
|
@@ -506,126 +504,16 @@ async function execSSH(command, options) {
|
|
|
506
504
|
throw new SSHError(`SSH command failed: ${validatedCommand.data}`, error);
|
|
507
505
|
}
|
|
508
506
|
}
|
|
509
|
-
var import_ssh;
|
|
510
507
|
var init_client = __esm(() => {
|
|
511
508
|
init_error();
|
|
509
|
+
init_ssh();
|
|
512
510
|
init_pool();
|
|
513
|
-
import_ssh = __toESM(require_ssh(), 1);
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// node_modules/@ebowwa/codespaces-types/compile/terminal-websocket.js
|
|
517
|
-
var require_terminal_websocket = __commonJS((exports) => {
|
|
518
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
519
|
-
exports.WebSocketCloseCode = undefined;
|
|
520
|
-
exports.WebSocketCloseCode = {
|
|
521
|
-
NORMAL_CLOSURE: 1000,
|
|
522
|
-
ENDPOINT_GOING_AWAY: 1001,
|
|
523
|
-
PROTOCOL_ERROR: 1002,
|
|
524
|
-
UNSUPPORTED_DATA: 1003,
|
|
525
|
-
NO_STATUS_RECEIVED: 1005,
|
|
526
|
-
ABNORMAL_CLOSURE: 1006,
|
|
527
|
-
INVALID_FRAME_PAYLOAD_DATA: 1007,
|
|
528
|
-
POLICY_VIOLATION: 1008,
|
|
529
|
-
MESSAGE_TOO_BIG: 1009,
|
|
530
|
-
MISSING_MANDATORY_EXTENSION: 1010,
|
|
531
|
-
INTERNAL_ERROR: 1011,
|
|
532
|
-
SERVICE_RESTART: 1012,
|
|
533
|
-
TRY_AGAIN_LATER: 1013,
|
|
534
|
-
SESSION_NOT_FOUND: 4001,
|
|
535
|
-
SESSION_ALREADY_CLOSED: 4002,
|
|
536
|
-
INVALID_MESSAGE_FORMAT: 4003,
|
|
537
|
-
AUTHENTICATION_FAILED: 4004,
|
|
538
|
-
CONNECTION_TIMEOUT: 4005,
|
|
539
|
-
SESSION_LIMIT_REACHED: 4006,
|
|
540
|
-
INVALID_HOST: 4007,
|
|
541
|
-
SSH_CONNECTION_FAILED: 4008,
|
|
542
|
-
NETWORK_BLOCKED: 4009
|
|
543
|
-
};
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
// node_modules/@ebowwa/codespaces-types/compile/index.js
|
|
547
|
-
var require_compile = __commonJS((exports) => {
|
|
548
|
-
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
549
|
-
if (k2 === undefined)
|
|
550
|
-
k2 = k;
|
|
551
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
552
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
553
|
-
desc = { enumerable: true, get: function() {
|
|
554
|
-
return m[k];
|
|
555
|
-
} };
|
|
556
|
-
}
|
|
557
|
-
Object.defineProperty(o, k2, desc);
|
|
558
|
-
} : function(o, m, k, k2) {
|
|
559
|
-
if (k2 === undefined)
|
|
560
|
-
k2 = k;
|
|
561
|
-
o[k2] = m[k];
|
|
562
|
-
});
|
|
563
|
-
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
564
|
-
for (var p in m)
|
|
565
|
-
if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p))
|
|
566
|
-
__createBinding(exports2, m, p);
|
|
567
|
-
};
|
|
568
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
569
|
-
exports.LoopStatus = exports.VolumeStatus = exports.ActionStatus = exports.EnvironmentStatus = undefined;
|
|
570
|
-
exports.getEnvLocation = getEnvLocation;
|
|
571
|
-
exports.getEnvRegionName = getEnvRegionName;
|
|
572
|
-
exports.getEnvLocationLabel = getEnvLocationLabel;
|
|
573
|
-
__exportStar(require_terminal_websocket(), exports);
|
|
574
|
-
var EnvironmentStatus;
|
|
575
|
-
(function(EnvironmentStatus2) {
|
|
576
|
-
EnvironmentStatus2["Running"] = "running";
|
|
577
|
-
EnvironmentStatus2["Stopped"] = "stopped";
|
|
578
|
-
EnvironmentStatus2["Creating"] = "creating";
|
|
579
|
-
EnvironmentStatus2["Deleting"] = "deleting";
|
|
580
|
-
EnvironmentStatus2["Starting"] = "starting";
|
|
581
|
-
EnvironmentStatus2["Stopping"] = "stopping";
|
|
582
|
-
EnvironmentStatus2["Initializing"] = "initializing";
|
|
583
|
-
})(EnvironmentStatus || (exports.EnvironmentStatus = EnvironmentStatus = {}));
|
|
584
|
-
var ActionStatus;
|
|
585
|
-
(function(ActionStatus2) {
|
|
586
|
-
ActionStatus2["Running"] = "running";
|
|
587
|
-
ActionStatus2["Success"] = "success";
|
|
588
|
-
ActionStatus2["Error"] = "error";
|
|
589
|
-
})(ActionStatus || (exports.ActionStatus = ActionStatus = {}));
|
|
590
|
-
var VolumeStatus;
|
|
591
|
-
(function(VolumeStatus2) {
|
|
592
|
-
VolumeStatus2["Creating"] = "creating";
|
|
593
|
-
VolumeStatus2["Available"] = "available";
|
|
594
|
-
VolumeStatus2["Deleting"] = "deleting";
|
|
595
|
-
})(VolumeStatus || (exports.VolumeStatus = VolumeStatus = {}));
|
|
596
|
-
var LoopStatus;
|
|
597
|
-
(function(LoopStatus2) {
|
|
598
|
-
LoopStatus2["Running"] = "running";
|
|
599
|
-
LoopStatus2["Stopped"] = "stopped";
|
|
600
|
-
LoopStatus2["Error"] = "error";
|
|
601
|
-
LoopStatus2["Completed"] = "completed";
|
|
602
|
-
})(LoopStatus || (exports.LoopStatus = LoopStatus = {}));
|
|
603
|
-
function getEnvLocation(env) {
|
|
604
|
-
if (!env)
|
|
605
|
-
return null;
|
|
606
|
-
return env.location || null;
|
|
607
|
-
}
|
|
608
|
-
function getEnvRegionName(env) {
|
|
609
|
-
var _a;
|
|
610
|
-
if (!env)
|
|
611
|
-
return "Unknown";
|
|
612
|
-
return ((_a = env.location) === null || _a === undefined ? undefined : _a.name) || "Unknown";
|
|
613
|
-
}
|
|
614
|
-
function getEnvLocationLabel(env) {
|
|
615
|
-
var _a, _b, _c;
|
|
616
|
-
if (!env)
|
|
617
|
-
return "Unknown";
|
|
618
|
-
if (((_a = env.location) === null || _a === undefined ? undefined : _a.city) && ((_b = env.location) === null || _b === undefined ? undefined : _b.country)) {
|
|
619
|
-
return "".concat(env.location.city, ", ").concat(env.location.country);
|
|
620
|
-
}
|
|
621
|
-
return ((_c = env.location) === null || _c === undefined ? undefined : _c.name) || "Unknown";
|
|
622
|
-
}
|
|
623
511
|
});
|
|
624
512
|
|
|
625
513
|
// src/tmux.ts
|
|
626
514
|
init_pool();
|
|
627
515
|
|
|
628
|
-
//
|
|
516
|
+
// ../ssh/flags.js
|
|
629
517
|
function sshConfig(key, value) {
|
|
630
518
|
return { type: "config", key, value };
|
|
631
519
|
}
|
|
@@ -1264,7 +1152,7 @@ class TmuxSessionManager {
|
|
|
1264
1152
|
return { success: false, error: `Node ${nodeId} is not running` };
|
|
1265
1153
|
}
|
|
1266
1154
|
try {
|
|
1267
|
-
const result = await createOrAttachTmuxSession(node.ip, node.user, node.keyPath, {
|
|
1155
|
+
const result = await createOrAttachTmuxSession(node.ip, node.user, node.keyPath, {});
|
|
1268
1156
|
return {
|
|
1269
1157
|
success: true,
|
|
1270
1158
|
sshArgs: result.sshArgs,
|
|
@@ -1768,10 +1656,10 @@ async function execViaTmuxParallel(commands, options, timeout = 10) {
|
|
|
1768
1656
|
}
|
|
1769
1657
|
// src/scp.ts
|
|
1770
1658
|
init_error();
|
|
1659
|
+
init_ssh();
|
|
1771
1660
|
init_pool();
|
|
1772
|
-
var import_ssh2 = __toESM(require_ssh(), 1);
|
|
1773
1661
|
async function scpUpload(options) {
|
|
1774
|
-
const validated =
|
|
1662
|
+
const validated = SCPOptionsSchema.safeParse(options);
|
|
1775
1663
|
if (!validated.success) {
|
|
1776
1664
|
throw new Error(`Invalid SCP options: ${validated.error.issues.map((i) => i.message).join(", ")}`);
|
|
1777
1665
|
}
|
|
@@ -1798,7 +1686,7 @@ async function scpUpload(options) {
|
|
|
1798
1686
|
}
|
|
1799
1687
|
}
|
|
1800
1688
|
async function scpDownload(options) {
|
|
1801
|
-
const validated =
|
|
1689
|
+
const validated = SCPOptionsSchema.safeParse(options);
|
|
1802
1690
|
if (!validated.success) {
|
|
1803
1691
|
throw new Error(`Invalid SCP options: ${validated.error.issues.map((i) => i.message).join(", ")}`);
|
|
1804
1692
|
}
|
|
@@ -2449,7 +2337,63 @@ init_pool();
|
|
|
2449
2337
|
|
|
2450
2338
|
// src/sessions.ts
|
|
2451
2339
|
import path2 from "path";
|
|
2452
|
-
|
|
2340
|
+
|
|
2341
|
+
// ../codespaces-types/compile/index.js
|
|
2342
|
+
var WebSocketCloseCode = {
|
|
2343
|
+
NORMAL_CLOSURE: 1000,
|
|
2344
|
+
ENDPOINT_GOING_AWAY: 1001,
|
|
2345
|
+
PROTOCOL_ERROR: 1002,
|
|
2346
|
+
UNSUPPORTED_DATA: 1003,
|
|
2347
|
+
NO_STATUS_RECEIVED: 1005,
|
|
2348
|
+
ABNORMAL_CLOSURE: 1006,
|
|
2349
|
+
INVALID_FRAME_PAYLOAD_DATA: 1007,
|
|
2350
|
+
POLICY_VIOLATION: 1008,
|
|
2351
|
+
MESSAGE_TOO_BIG: 1009,
|
|
2352
|
+
MISSING_MANDATORY_EXTENSION: 1010,
|
|
2353
|
+
INTERNAL_ERROR: 1011,
|
|
2354
|
+
SERVICE_RESTART: 1012,
|
|
2355
|
+
TRY_AGAIN_LATER: 1013,
|
|
2356
|
+
SESSION_NOT_FOUND: 4001,
|
|
2357
|
+
SESSION_ALREADY_CLOSED: 4002,
|
|
2358
|
+
INVALID_MESSAGE_FORMAT: 4003,
|
|
2359
|
+
AUTHENTICATION_FAILED: 4004,
|
|
2360
|
+
CONNECTION_TIMEOUT: 4005,
|
|
2361
|
+
SESSION_LIMIT_REACHED: 4006,
|
|
2362
|
+
INVALID_HOST: 4007,
|
|
2363
|
+
SSH_CONNECTION_FAILED: 4008,
|
|
2364
|
+
NETWORK_BLOCKED: 4009
|
|
2365
|
+
};
|
|
2366
|
+
var EnvironmentStatus;
|
|
2367
|
+
((EnvironmentStatus2) => {
|
|
2368
|
+
EnvironmentStatus2["Running"] = "running";
|
|
2369
|
+
EnvironmentStatus2["Stopped"] = "stopped";
|
|
2370
|
+
EnvironmentStatus2["Creating"] = "creating";
|
|
2371
|
+
EnvironmentStatus2["Deleting"] = "deleting";
|
|
2372
|
+
EnvironmentStatus2["Starting"] = "starting";
|
|
2373
|
+
EnvironmentStatus2["Stopping"] = "stopping";
|
|
2374
|
+
EnvironmentStatus2["Initializing"] = "initializing";
|
|
2375
|
+
})(EnvironmentStatus ||= {});
|
|
2376
|
+
var ActionStatus;
|
|
2377
|
+
((ActionStatus2) => {
|
|
2378
|
+
ActionStatus2["Running"] = "running";
|
|
2379
|
+
ActionStatus2["Success"] = "success";
|
|
2380
|
+
ActionStatus2["Error"] = "error";
|
|
2381
|
+
})(ActionStatus ||= {});
|
|
2382
|
+
var VolumeStatus;
|
|
2383
|
+
((VolumeStatus2) => {
|
|
2384
|
+
VolumeStatus2["Creating"] = "creating";
|
|
2385
|
+
VolumeStatus2["Available"] = "available";
|
|
2386
|
+
VolumeStatus2["Deleting"] = "deleting";
|
|
2387
|
+
})(VolumeStatus ||= {});
|
|
2388
|
+
var LoopStatus;
|
|
2389
|
+
((LoopStatus2) => {
|
|
2390
|
+
LoopStatus2["Running"] = "running";
|
|
2391
|
+
LoopStatus2["Stopped"] = "stopped";
|
|
2392
|
+
LoopStatus2["Error"] = "error";
|
|
2393
|
+
LoopStatus2["Completed"] = "completed";
|
|
2394
|
+
})(LoopStatus ||= {});
|
|
2395
|
+
|
|
2396
|
+
// src/sessions.ts
|
|
2453
2397
|
var metadataModule = null;
|
|
2454
2398
|
function loadMetadata() {
|
|
2455
2399
|
if (metadataModule === null) {
|
|
@@ -2502,7 +2446,7 @@ async function closeSession(sessionId) {
|
|
|
2502
2446
|
} catch {}
|
|
2503
2447
|
}
|
|
2504
2448
|
try {
|
|
2505
|
-
session.ws?.close(
|
|
2449
|
+
session.ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, "Session closed");
|
|
2506
2450
|
} catch {}
|
|
2507
2451
|
if (session.bootstrapLogStreamer) {
|
|
2508
2452
|
try {
|
|
@@ -2822,14 +2766,14 @@ async function getOrCreateSession(host, user = "root", sessionId = null, keyPath
|
|
|
2822
2766
|
console.log(`[Terminal] Process for ${newSessionId} exited with code ${exitCode}`);
|
|
2823
2767
|
session.closed = true;
|
|
2824
2768
|
try {
|
|
2825
|
-
session.ws?.close(
|
|
2769
|
+
session.ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, `SSH process exited (code: ${exitCode})`);
|
|
2826
2770
|
} catch {}
|
|
2827
2771
|
terminalSessions.delete(newSessionId);
|
|
2828
2772
|
}).catch((err) => {
|
|
2829
2773
|
console.log(`[Terminal] Process exit error for ${newSessionId}:`, err);
|
|
2830
2774
|
session.closed = true;
|
|
2831
2775
|
try {
|
|
2832
|
-
session.ws?.close(
|
|
2776
|
+
session.ws?.close(WebSocketCloseCode.INTERNAL_ERROR, "SSH process error");
|
|
2833
2777
|
} catch {}
|
|
2834
2778
|
});
|
|
2835
2779
|
return session;
|
|
@@ -3379,14 +3323,392 @@ var RESOURCE_COMMANDS = {
|
|
|
3379
3323
|
connections: `cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | wc -l`,
|
|
3380
3324
|
ports: `cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | grep -v 'local_address' | awk '{print $2}' | cut -d: -f2 | sort -u | tr '\\n' ';' | sed 's/;$//'`
|
|
3381
3325
|
};
|
|
3326
|
+
// src/network-error-detector.ts
|
|
3327
|
+
function detectNetworkError(error, host, serverStatus) {
|
|
3328
|
+
const errorMessage = typeof error === "string" ? error : error.message;
|
|
3329
|
+
const isConnectionRefused = errorMessage.includes("ECONNREFUSED") || errorMessage.includes("Connection refused") || errorMessage.includes("connect ECONNREFUSED");
|
|
3330
|
+
const isHetznerOrDatacenterIP = host.startsWith("195.") || host.startsWith("148.") || host.startsWith("116.") || host.startsWith("138.") || host.includes("hetzner") || host.includes("fsn1") || host.includes("nbg1") || host.includes("hel1");
|
|
3331
|
+
const serverIsRunning = serverStatus === "running";
|
|
3332
|
+
if (isConnectionRefused && serverIsRunning && isHetznerOrDatacenterIP) {
|
|
3333
|
+
return {
|
|
3334
|
+
type: "NETWORK_BLOCKED",
|
|
3335
|
+
message: "Network appears to be blocking this server",
|
|
3336
|
+
troubleshooting: [
|
|
3337
|
+
"\u2713 Server is running and responding to API",
|
|
3338
|
+
"\u2717 Your network is refusing connections to this IP",
|
|
3339
|
+
"",
|
|
3340
|
+
"\u26A0\uFE0F COMMON CAUSES:",
|
|
3341
|
+
"\u2022 Corporate/public WiFi blocks datacenter IPs",
|
|
3342
|
+
"\u2022 ISP firewall filtering VPS providers",
|
|
3343
|
+
"\u2022 Router content filtering",
|
|
3344
|
+
"",
|
|
3345
|
+
"\uD83D\uDD27 SOLUTIONS:",
|
|
3346
|
+
"1. Try a different network (hotspot, home WiFi, VPN)",
|
|
3347
|
+
"2. Check router firewall settings",
|
|
3348
|
+
"3. Contact ISP about blocking Hetzner IPs",
|
|
3349
|
+
"",
|
|
3350
|
+
"\uD83D\uDCA1 TEST: Works from other networks? = Network block confirmed"
|
|
3351
|
+
],
|
|
3352
|
+
isLikelyNetworkBlock: true
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
if (isConnectionRefused && !serverIsRunning) {
|
|
3356
|
+
return {
|
|
3357
|
+
type: "SERVER_NOT_READY",
|
|
3358
|
+
message: "Server is not ready yet",
|
|
3359
|
+
troubleshooting: [
|
|
3360
|
+
"Server is still starting up",
|
|
3361
|
+
"SSH daemon hasn't started yet",
|
|
3362
|
+
"",
|
|
3363
|
+
"\u23F3 Wait 1-2 minutes and try again"
|
|
3364
|
+
],
|
|
3365
|
+
isLikelyNetworkBlock: false
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
if (errorMessage.includes("All configured authentication") || errorMessage.includes("Authentication failed")) {
|
|
3369
|
+
return {
|
|
3370
|
+
type: "AUTH_FAILED",
|
|
3371
|
+
message: "SSH authentication failed",
|
|
3372
|
+
troubleshooting: [
|
|
3373
|
+
"SSH key issue or wrong credentials",
|
|
3374
|
+
"Check that the SSH key is configured correctly"
|
|
3375
|
+
],
|
|
3376
|
+
isLikelyNetworkBlock: false
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
return {
|
|
3380
|
+
type: "UNKNOWN",
|
|
3381
|
+
message: errorMessage,
|
|
3382
|
+
troubleshooting: [
|
|
3383
|
+
"Check if server is running",
|
|
3384
|
+
"Verify network connectivity",
|
|
3385
|
+
"Check SSH key configuration"
|
|
3386
|
+
],
|
|
3387
|
+
isLikelyNetworkBlock: false
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
function formatNetworkError(diagnosis) {
|
|
3391
|
+
if (diagnosis.isLikelyNetworkBlock) {
|
|
3392
|
+
return `\u26A0\uFE0F ${diagnosis.message}
|
|
3393
|
+
|
|
3394
|
+
${diagnosis.troubleshooting.join(`
|
|
3395
|
+
`)}`;
|
|
3396
|
+
}
|
|
3397
|
+
return diagnosis.message;
|
|
3398
|
+
}
|
|
3399
|
+
// src/config.ts
|
|
3400
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync } from "fs";
|
|
3401
|
+
import { join, dirname, isAbsolute, resolve } from "path";
|
|
3402
|
+
import { exec as exec2 } from "child_process";
|
|
3403
|
+
import { promisify as promisify3 } from "util";
|
|
3404
|
+
var execAsync3 = promisify3(exec2);
|
|
3405
|
+
var SSH_CONFIG_PATH = join(process.env.HOME || "~", ".ssh", "config");
|
|
3406
|
+
var BLOCK_START = "# >>> hetzner-codespaces managed";
|
|
3407
|
+
var BLOCK_END = "# <<< hetzner-codespaces managed";
|
|
3408
|
+
function resolveKeyPath2(keyPath) {
|
|
3409
|
+
if (isAbsolute(keyPath)) {
|
|
3410
|
+
return keyPath;
|
|
3411
|
+
}
|
|
3412
|
+
const resolved = resolve(process.cwd(), keyPath);
|
|
3413
|
+
if (existsSync(resolved)) {
|
|
3414
|
+
try {
|
|
3415
|
+
return realpathSync(resolved);
|
|
3416
|
+
} catch {
|
|
3417
|
+
return resolved;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
return keyPath;
|
|
3421
|
+
}
|
|
3422
|
+
function readSSHConfig() {
|
|
3423
|
+
if (!existsSync(SSH_CONFIG_PATH)) {
|
|
3424
|
+
return "";
|
|
3425
|
+
}
|
|
3426
|
+
return readFileSync(SSH_CONFIG_PATH, "utf-8");
|
|
3427
|
+
}
|
|
3428
|
+
function writeSSHConfig(content) {
|
|
3429
|
+
const sshDir = dirname(SSH_CONFIG_PATH);
|
|
3430
|
+
if (!existsSync(sshDir)) {
|
|
3431
|
+
mkdirSync(sshDir, { mode: 448, recursive: true });
|
|
3432
|
+
}
|
|
3433
|
+
writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
|
|
3434
|
+
}
|
|
3435
|
+
function extractManagedBlock(config) {
|
|
3436
|
+
const startIdx = config.indexOf(BLOCK_START);
|
|
3437
|
+
const endIdx = config.indexOf(BLOCK_END);
|
|
3438
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
3439
|
+
return { before: config, managed: "", after: "" };
|
|
3440
|
+
}
|
|
3441
|
+
return {
|
|
3442
|
+
before: config.substring(0, startIdx),
|
|
3443
|
+
managed: config.substring(startIdx, endIdx + BLOCK_END.length + 1),
|
|
3444
|
+
after: config.substring(endIdx + BLOCK_END.length + 1)
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
function parseManagedEntries(managed) {
|
|
3448
|
+
const entries = new Map;
|
|
3449
|
+
const hostRegex = /# node-id: (\S+)\nHost ([^\n]+)\n([\s\S]*?)(?=# node-id:|$)/g;
|
|
3450
|
+
let match;
|
|
3451
|
+
while ((match = hostRegex.exec(managed)) !== null) {
|
|
3452
|
+
const id = match[1];
|
|
3453
|
+
const hosts = match[2].trim();
|
|
3454
|
+
const body = match[3];
|
|
3455
|
+
const hostMatch = body.match(/HostName\s+(\S+)/);
|
|
3456
|
+
const userMatch = body.match(/User\s+(\S+)/);
|
|
3457
|
+
const keyMatch = body.match(/IdentityFile\s+(\S+)/);
|
|
3458
|
+
const portMatch = body.match(/Port\s+(\d+)/);
|
|
3459
|
+
if (hostMatch && keyMatch) {
|
|
3460
|
+
entries.set(id, {
|
|
3461
|
+
id,
|
|
3462
|
+
name: hosts.split(/\s+/)[1] || hosts,
|
|
3463
|
+
host: hostMatch[1],
|
|
3464
|
+
user: userMatch?.[1] || "root",
|
|
3465
|
+
keyPath: keyMatch[1],
|
|
3466
|
+
port: portMatch ? parseInt(portMatch[1]) : 22
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
return entries;
|
|
3471
|
+
}
|
|
3472
|
+
function generateEntryBlock(entry) {
|
|
3473
|
+
const sanitizedName = entry.name.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
3474
|
+
const aliases = `node-${entry.id} ${sanitizedName}`;
|
|
3475
|
+
const absoluteKeyPath = resolveKeyPath2(entry.keyPath);
|
|
3476
|
+
return `# node-id: ${entry.id}
|
|
3477
|
+
Host ${aliases}
|
|
3478
|
+
HostName ${entry.host}
|
|
3479
|
+
User ${entry.user || "root"}
|
|
3480
|
+
IdentityFile "${absoluteKeyPath}"
|
|
3481
|
+
Port ${entry.port || 22}
|
|
3482
|
+
StrictHostKeyChecking no
|
|
3483
|
+
UserKnownHostsFile /dev/null
|
|
3484
|
+
LogLevel ERROR
|
|
3485
|
+
IdentitiesOnly yes
|
|
3486
|
+
|
|
3487
|
+
`;
|
|
3488
|
+
}
|
|
3489
|
+
function buildManagedBlock(entries) {
|
|
3490
|
+
if (entries.size === 0) {
|
|
3491
|
+
return "";
|
|
3492
|
+
}
|
|
3493
|
+
let block = `${BLOCK_START}
|
|
3494
|
+
`;
|
|
3495
|
+
block += `# Auto-generated SSH aliases for Hetzner nodes
|
|
3496
|
+
`;
|
|
3497
|
+
block += `# Do not edit manually - changes will be overwritten
|
|
3498
|
+
|
|
3499
|
+
`;
|
|
3500
|
+
for (const entry of entries.values()) {
|
|
3501
|
+
block += generateEntryBlock(entry);
|
|
3502
|
+
}
|
|
3503
|
+
block += `${BLOCK_END}
|
|
3504
|
+
`;
|
|
3505
|
+
return block;
|
|
3506
|
+
}
|
|
3507
|
+
function addSSHConfigEntry(entry) {
|
|
3508
|
+
const config = readSSHConfig();
|
|
3509
|
+
const { before, managed, after } = extractManagedBlock(config);
|
|
3510
|
+
const entries = parseManagedEntries(managed);
|
|
3511
|
+
entries.set(entry.id, entry);
|
|
3512
|
+
const newManaged = buildManagedBlock(entries);
|
|
3513
|
+
const newConfig = before.trimEnd() + `
|
|
3514
|
+
|
|
3515
|
+
` + newManaged + after.trimStart();
|
|
3516
|
+
writeSSHConfig(newConfig);
|
|
3517
|
+
console.log(`[SSH Config] Added alias: ssh node-${entry.id} / ssh ${entry.name.replace(/[^a-zA-Z0-9_-]/g, "-")}`);
|
|
3518
|
+
}
|
|
3519
|
+
function removeSSHConfigEntry(id) {
|
|
3520
|
+
const config = readSSHConfig();
|
|
3521
|
+
const { before, managed, after } = extractManagedBlock(config);
|
|
3522
|
+
const entries = parseManagedEntries(managed);
|
|
3523
|
+
if (!entries.has(id)) {
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
entries.delete(id);
|
|
3527
|
+
const newManaged = buildManagedBlock(entries);
|
|
3528
|
+
const newConfig = before.trimEnd() + (newManaged ? `
|
|
3529
|
+
|
|
3530
|
+
` + newManaged : "") + after.trimStart();
|
|
3531
|
+
writeSSHConfig(newConfig);
|
|
3532
|
+
console.log(`[SSH Config] Removed alias for node-${id}`);
|
|
3533
|
+
}
|
|
3534
|
+
function updateSSHConfigHost(id, newHost) {
|
|
3535
|
+
const config = readSSHConfig();
|
|
3536
|
+
const { before, managed, after } = extractManagedBlock(config);
|
|
3537
|
+
const entries = parseManagedEntries(managed);
|
|
3538
|
+
const entry = entries.get(id);
|
|
3539
|
+
if (!entry) {
|
|
3540
|
+
console.warn(`[SSH Config] No entry found for node-${id}`);
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
entry.host = newHost;
|
|
3544
|
+
entries.set(id, entry);
|
|
3545
|
+
const newManaged = buildManagedBlock(entries);
|
|
3546
|
+
const newConfig = before.trimEnd() + `
|
|
3547
|
+
|
|
3548
|
+
` + newManaged + after.trimStart();
|
|
3549
|
+
writeSSHConfig(newConfig);
|
|
3550
|
+
console.log(`[SSH Config] Updated node-${id} host to ${newHost}`);
|
|
3551
|
+
}
|
|
3552
|
+
function listSSHConfigEntries() {
|
|
3553
|
+
const config = readSSHConfig();
|
|
3554
|
+
const { managed } = extractManagedBlock(config);
|
|
3555
|
+
const entries = parseManagedEntries(managed);
|
|
3556
|
+
return Array.from(entries.values());
|
|
3557
|
+
}
|
|
3558
|
+
async function validateSSHConnection(host, keyPath, user = "root", timeoutSeconds = 10) {
|
|
3559
|
+
try {
|
|
3560
|
+
const cmd = [
|
|
3561
|
+
"ssh",
|
|
3562
|
+
"-o",
|
|
3563
|
+
"StrictHostKeyChecking=no",
|
|
3564
|
+
"-o",
|
|
3565
|
+
"UserKnownHostsFile=/dev/null",
|
|
3566
|
+
"-o",
|
|
3567
|
+
`ConnectTimeout=${timeoutSeconds}`,
|
|
3568
|
+
"-o",
|
|
3569
|
+
"IdentitiesOnly=yes",
|
|
3570
|
+
"-o",
|
|
3571
|
+
"BatchMode=yes",
|
|
3572
|
+
"-i",
|
|
3573
|
+
keyPath,
|
|
3574
|
+
`${user}@${host}`,
|
|
3575
|
+
"echo CONNECTION_OK"
|
|
3576
|
+
].join(" ");
|
|
3577
|
+
const { stdout } = await execAsync3(cmd, { timeout: (timeoutSeconds + 5) * 1000 });
|
|
3578
|
+
if (stdout.includes("CONNECTION_OK")) {
|
|
3579
|
+
return { success: true };
|
|
3580
|
+
}
|
|
3581
|
+
return {
|
|
3582
|
+
success: false,
|
|
3583
|
+
error: "Connection established but test command failed",
|
|
3584
|
+
diagnostics: stdout
|
|
3585
|
+
};
|
|
3586
|
+
} catch (error) {
|
|
3587
|
+
let diagnostics = "";
|
|
3588
|
+
if (!existsSync(keyPath)) {
|
|
3589
|
+
diagnostics += `Key file missing: ${keyPath}
|
|
3590
|
+
`;
|
|
3591
|
+
}
|
|
3592
|
+
try {
|
|
3593
|
+
const { stdout: agentKeys } = await execAsync3("ssh-add -l 2>&1");
|
|
3594
|
+
diagnostics += `ssh-agent keys:
|
|
3595
|
+
${agentKeys}
|
|
3596
|
+
`;
|
|
3597
|
+
} catch {
|
|
3598
|
+
diagnostics += `ssh-agent: no keys loaded
|
|
3599
|
+
`;
|
|
3600
|
+
}
|
|
3601
|
+
return {
|
|
3602
|
+
success: false,
|
|
3603
|
+
error: error.message || String(error),
|
|
3604
|
+
diagnostics
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
async function ensureCorrectSSHKey(keyPath) {
|
|
3609
|
+
try {
|
|
3610
|
+
const { stdout: ourFingerprint } = await execAsync3(`ssh-keygen -lf "${keyPath}.pub"`);
|
|
3611
|
+
const ourFp = ourFingerprint.split(/\s+/)[1];
|
|
3612
|
+
const { stdout: agentList } = await execAsync3("ssh-add -l 2>&1").catch(() => ({ stdout: "" }));
|
|
3613
|
+
if (!agentList.includes(ourFp)) {
|
|
3614
|
+
await execAsync3("ssh-add -D 2>/dev/null").catch(() => {});
|
|
3615
|
+
await execAsync3(`ssh-add "${keyPath}"`);
|
|
3616
|
+
console.log(`[SSH] Added key to ssh-agent: ${keyPath}`);
|
|
3617
|
+
}
|
|
3618
|
+
} catch (error) {
|
|
3619
|
+
console.warn(`[SSH] Could not configure ssh-agent: ${error}`);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
async function waitForSSHReady(host, keyPath, options = {}) {
|
|
3623
|
+
const {
|
|
3624
|
+
user = "root",
|
|
3625
|
+
maxAttempts = 30,
|
|
3626
|
+
intervalMs = 5000,
|
|
3627
|
+
onAttempt
|
|
3628
|
+
} = options;
|
|
3629
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
3630
|
+
onAttempt?.(attempt, maxAttempts);
|
|
3631
|
+
const result = await validateSSHConnection(host, keyPath, user, 5);
|
|
3632
|
+
if (result.success) {
|
|
3633
|
+
return { success: true, attempts: attempt };
|
|
3634
|
+
}
|
|
3635
|
+
if (result.error?.includes("Permission denied")) {
|
|
3636
|
+
return {
|
|
3637
|
+
success: false,
|
|
3638
|
+
attempts: attempt,
|
|
3639
|
+
error: `SSH key rejected: ${result.error}
|
|
3640
|
+
${result.diagnostics || ""}`
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
if (attempt < maxAttempts) {
|
|
3644
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
return {
|
|
3648
|
+
success: false,
|
|
3649
|
+
attempts: maxAttempts,
|
|
3650
|
+
error: `SSH not ready after ${maxAttempts} attempts (${maxAttempts * intervalMs / 1000}s)`
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
async function syncNodesToSSHConfig(nodes, options = {}) {
|
|
3654
|
+
const { validateSSH = false, onProgress } = options;
|
|
3655
|
+
const results = [];
|
|
3656
|
+
const existingEntries = listSSHConfigEntries();
|
|
3657
|
+
const existingIds = new Set(existingEntries.map((e) => e.id));
|
|
3658
|
+
for (const node of nodes) {
|
|
3659
|
+
const result = {
|
|
3660
|
+
id: node.id,
|
|
3661
|
+
name: node.name,
|
|
3662
|
+
ip: node.ip,
|
|
3663
|
+
status: "added"
|
|
3664
|
+
};
|
|
3665
|
+
try {
|
|
3666
|
+
if (existingIds.has(node.id)) {
|
|
3667
|
+
const existing = existingEntries.find((e) => e.id === node.id);
|
|
3668
|
+
if (existing?.host === node.ip) {
|
|
3669
|
+
result.status = "skipped";
|
|
3670
|
+
} else {
|
|
3671
|
+
updateSSHConfigHost(node.id, node.ip);
|
|
3672
|
+
result.status = "updated";
|
|
3673
|
+
}
|
|
3674
|
+
} else {
|
|
3675
|
+
addSSHConfigEntry({
|
|
3676
|
+
id: node.id,
|
|
3677
|
+
name: node.name,
|
|
3678
|
+
host: node.ip,
|
|
3679
|
+
user: "root",
|
|
3680
|
+
keyPath: node.keyPath
|
|
3681
|
+
});
|
|
3682
|
+
result.status = "added";
|
|
3683
|
+
}
|
|
3684
|
+
if (validateSSH && result.status !== "skipped") {
|
|
3685
|
+
const sshResult = await validateSSHConnection(node.ip, node.keyPath);
|
|
3686
|
+
result.sshReady = sshResult.success;
|
|
3687
|
+
if (!sshResult.success) {
|
|
3688
|
+
result.error = sshResult.error;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
} catch (error) {
|
|
3692
|
+
result.status = "error";
|
|
3693
|
+
result.error = String(error);
|
|
3694
|
+
}
|
|
3695
|
+
results.push(result);
|
|
3696
|
+
onProgress?.(result);
|
|
3697
|
+
}
|
|
3698
|
+
return results;
|
|
3699
|
+
}
|
|
3382
3700
|
export {
|
|
3383
3701
|
writeToSession,
|
|
3384
3702
|
writeToPTY,
|
|
3385
3703
|
waitForTextInPane,
|
|
3704
|
+
waitForSSHReady,
|
|
3386
3705
|
validateSSHKeyMatch,
|
|
3387
3706
|
validateSSHKeyForServer,
|
|
3707
|
+
validateSSHConnection,
|
|
3708
|
+
updateSSHConfigHost,
|
|
3388
3709
|
testSSHKeyConnection,
|
|
3389
3710
|
testSSHConnection,
|
|
3711
|
+
syncNodesToSSHConfig,
|
|
3390
3712
|
switchWindow,
|
|
3391
3713
|
switchPane,
|
|
3392
3714
|
switchLocalWindow,
|
|
@@ -3403,12 +3725,14 @@ export {
|
|
|
3403
3725
|
resetTmuxManager,
|
|
3404
3726
|
renameWindow,
|
|
3405
3727
|
renameLocalWindow,
|
|
3728
|
+
removeSSHConfigEntry,
|
|
3406
3729
|
readFromPTY,
|
|
3407
3730
|
previewFile,
|
|
3408
3731
|
normalizeFingerprint,
|
|
3409
3732
|
listWindowPanes,
|
|
3410
3733
|
listTmuxSessions,
|
|
3411
3734
|
listSessionWindows,
|
|
3735
|
+
listSSHConfigEntries,
|
|
3412
3736
|
listLocalWindowPanes,
|
|
3413
3737
|
listLocalSessions,
|
|
3414
3738
|
listLocalSessionWindows,
|
|
@@ -3447,11 +3771,14 @@ export {
|
|
|
3447
3771
|
getActivePTYSessions,
|
|
3448
3772
|
generateSessionName,
|
|
3449
3773
|
generateLocalSessionName,
|
|
3774
|
+
formatNetworkError,
|
|
3450
3775
|
execViaTmuxParallel,
|
|
3451
3776
|
execViaTmux,
|
|
3452
3777
|
execSSHParallel,
|
|
3453
3778
|
execSSH,
|
|
3454
3779
|
ensureTmux,
|
|
3780
|
+
ensureCorrectSSHKey,
|
|
3781
|
+
detectNetworkError,
|
|
3455
3782
|
detachWebSocket,
|
|
3456
3783
|
createPTYSession,
|
|
3457
3784
|
createOrAttachTmuxSession,
|
|
@@ -3466,6 +3793,7 @@ export {
|
|
|
3466
3793
|
capturePane,
|
|
3467
3794
|
captureLocalPane,
|
|
3468
3795
|
attachWebSocket,
|
|
3796
|
+
addSSHConfigEntry,
|
|
3469
3797
|
TmuxSessionManager,
|
|
3470
3798
|
SSHKeyMismatchError,
|
|
3471
3799
|
SSHError,
|
package/dist/manager.d.ts
CHANGED
|
@@ -14,7 +14,27 @@
|
|
|
14
14
|
* ENVIRONMENT VARIABLES:
|
|
15
15
|
* - HETZNER_SSH_KEYS_DIR: Override default SSH keys directory
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
interface HetznerSSHKeysAPI {
|
|
18
|
+
getAll(): Promise<{
|
|
19
|
+
ssh_keys: HetznerSSHKey[];
|
|
20
|
+
}>;
|
|
21
|
+
list(): Promise<{
|
|
22
|
+
ssh_keys: HetznerSSHKey[];
|
|
23
|
+
}>;
|
|
24
|
+
create(params: {
|
|
25
|
+
name: string;
|
|
26
|
+
public_key: string;
|
|
27
|
+
}): Promise<HetznerSSHKey>;
|
|
28
|
+
findByName(name: string): Promise<HetznerSSHKey | undefined>;
|
|
29
|
+
}
|
|
30
|
+
interface HetznerClient {
|
|
31
|
+
ssh_keys: HetznerSSHKeysAPI;
|
|
32
|
+
}
|
|
33
|
+
interface HetznerSSHKey {
|
|
34
|
+
id: number;
|
|
35
|
+
name: string;
|
|
36
|
+
fingerprint: string;
|
|
37
|
+
}
|
|
18
38
|
declare module "bun" {
|
|
19
39
|
interface Env {
|
|
20
40
|
HETZNER_SSH_KEYS_DIR?: string;
|
|
@@ -100,3 +120,4 @@ export declare class SSHKeyManager {
|
|
|
100
120
|
* @returns SSH key information
|
|
101
121
|
*/
|
|
102
122
|
export declare function ensureSSHKey(hetznerClient: HetznerClient, config?: SSHKeyManagerConfig): Promise<SSHKeyInfo>;
|
|
123
|
+
export {};
|
package/dist/mcp/index.js
CHANGED
|
@@ -16,7 +16,6 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
16
16
|
});
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
19
|
var __export = (target, all) => {
|
|
21
20
|
for (var name in all)
|
|
22
21
|
__defProp(target, name, {
|
|
@@ -445,35 +444,34 @@ var init_error = __esm(() => {
|
|
|
445
444
|
};
|
|
446
445
|
});
|
|
447
446
|
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
password: zod_1.z.string().optional()
|
|
447
|
+
// ../codespaces-types/runtime/ssh.js
|
|
448
|
+
import { z } from "zod";
|
|
449
|
+
var SSHOptionsSchema, SSHCommandSchema, SCPOptionsSchema, FilesListOptionsSchema, FilePreviewOptionsSchema;
|
|
450
|
+
var init_ssh = __esm(() => {
|
|
451
|
+
SSHOptionsSchema = z.object({
|
|
452
|
+
host: z.string().min(1, "Host is required"),
|
|
453
|
+
user: z.string().default("root"),
|
|
454
|
+
timeout: z.number().int().positive().default(5),
|
|
455
|
+
port: z.number().int().positive().max(65535).default(22),
|
|
456
|
+
keyPath: z.string().optional(),
|
|
457
|
+
password: z.string().optional()
|
|
460
458
|
});
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
source:
|
|
464
|
-
destination:
|
|
465
|
-
recursive:
|
|
466
|
-
preserve:
|
|
459
|
+
SSHCommandSchema = z.string().min(1, "Command cannot be empty");
|
|
460
|
+
SCPOptionsSchema = SSHOptionsSchema.extend({
|
|
461
|
+
source: z.string().min(1, "Source is required"),
|
|
462
|
+
destination: z.string().min(1, "Destination is required"),
|
|
463
|
+
recursive: z.boolean().default(false),
|
|
464
|
+
preserve: z.boolean().default(false)
|
|
467
465
|
});
|
|
468
|
-
|
|
469
|
-
host:
|
|
470
|
-
user:
|
|
471
|
-
path:
|
|
466
|
+
FilesListOptionsSchema = z.object({
|
|
467
|
+
host: z.string().min(1, "Host is required"),
|
|
468
|
+
user: z.string().default("root"),
|
|
469
|
+
path: z.string().default(".")
|
|
472
470
|
});
|
|
473
|
-
|
|
474
|
-
host:
|
|
475
|
-
user:
|
|
476
|
-
path:
|
|
471
|
+
FilePreviewOptionsSchema = z.object({
|
|
472
|
+
host: z.string().min(1, "Host is required"),
|
|
473
|
+
user: z.string().default("root"),
|
|
474
|
+
path: z.string().min(1, "Path is required")
|
|
477
475
|
});
|
|
478
476
|
});
|
|
479
477
|
|
|
@@ -483,11 +481,11 @@ __export(exports_client, {
|
|
|
483
481
|
execSSH: () => execSSH
|
|
484
482
|
});
|
|
485
483
|
async function execSSH(command, options) {
|
|
486
|
-
const validatedCommand =
|
|
484
|
+
const validatedCommand = SSHCommandSchema.safeParse(command);
|
|
487
485
|
if (!validatedCommand.success) {
|
|
488
486
|
throw new Error(`Invalid SSH command: ${validatedCommand.error.issues.map((i) => i.message).join(", ")}`);
|
|
489
487
|
}
|
|
490
|
-
const validatedOptions =
|
|
488
|
+
const validatedOptions = SSHOptionsSchema.safeParse(options);
|
|
491
489
|
if (!validatedOptions.success) {
|
|
492
490
|
throw new Error(`Invalid SSH options: ${validatedOptions.error.issues.map((i) => i.message).join(", ")}`);
|
|
493
491
|
}
|
|
@@ -507,126 +505,16 @@ async function execSSH(command, options) {
|
|
|
507
505
|
throw new SSHError(`SSH command failed: ${validatedCommand.data}`, error);
|
|
508
506
|
}
|
|
509
507
|
}
|
|
510
|
-
var import_ssh;
|
|
511
508
|
var init_client = __esm(() => {
|
|
512
509
|
init_error();
|
|
510
|
+
init_ssh();
|
|
513
511
|
init_pool();
|
|
514
|
-
import_ssh = __toESM(require_ssh(), 1);
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// node_modules/@ebowwa/codespaces-types/compile/terminal-websocket.js
|
|
518
|
-
var require_terminal_websocket = __commonJS((exports) => {
|
|
519
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
520
|
-
exports.WebSocketCloseCode = undefined;
|
|
521
|
-
exports.WebSocketCloseCode = {
|
|
522
|
-
NORMAL_CLOSURE: 1000,
|
|
523
|
-
ENDPOINT_GOING_AWAY: 1001,
|
|
524
|
-
PROTOCOL_ERROR: 1002,
|
|
525
|
-
UNSUPPORTED_DATA: 1003,
|
|
526
|
-
NO_STATUS_RECEIVED: 1005,
|
|
527
|
-
ABNORMAL_CLOSURE: 1006,
|
|
528
|
-
INVALID_FRAME_PAYLOAD_DATA: 1007,
|
|
529
|
-
POLICY_VIOLATION: 1008,
|
|
530
|
-
MESSAGE_TOO_BIG: 1009,
|
|
531
|
-
MISSING_MANDATORY_EXTENSION: 1010,
|
|
532
|
-
INTERNAL_ERROR: 1011,
|
|
533
|
-
SERVICE_RESTART: 1012,
|
|
534
|
-
TRY_AGAIN_LATER: 1013,
|
|
535
|
-
SESSION_NOT_FOUND: 4001,
|
|
536
|
-
SESSION_ALREADY_CLOSED: 4002,
|
|
537
|
-
INVALID_MESSAGE_FORMAT: 4003,
|
|
538
|
-
AUTHENTICATION_FAILED: 4004,
|
|
539
|
-
CONNECTION_TIMEOUT: 4005,
|
|
540
|
-
SESSION_LIMIT_REACHED: 4006,
|
|
541
|
-
INVALID_HOST: 4007,
|
|
542
|
-
SSH_CONNECTION_FAILED: 4008,
|
|
543
|
-
NETWORK_BLOCKED: 4009
|
|
544
|
-
};
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
// node_modules/@ebowwa/codespaces-types/compile/index.js
|
|
548
|
-
var require_compile = __commonJS((exports) => {
|
|
549
|
-
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
550
|
-
if (k2 === undefined)
|
|
551
|
-
k2 = k;
|
|
552
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
553
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
554
|
-
desc = { enumerable: true, get: function() {
|
|
555
|
-
return m[k];
|
|
556
|
-
} };
|
|
557
|
-
}
|
|
558
|
-
Object.defineProperty(o, k2, desc);
|
|
559
|
-
} : function(o, m, k, k2) {
|
|
560
|
-
if (k2 === undefined)
|
|
561
|
-
k2 = k;
|
|
562
|
-
o[k2] = m[k];
|
|
563
|
-
});
|
|
564
|
-
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
565
|
-
for (var p in m)
|
|
566
|
-
if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p))
|
|
567
|
-
__createBinding(exports2, m, p);
|
|
568
|
-
};
|
|
569
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
570
|
-
exports.LoopStatus = exports.VolumeStatus = exports.ActionStatus = exports.EnvironmentStatus = undefined;
|
|
571
|
-
exports.getEnvLocation = getEnvLocation;
|
|
572
|
-
exports.getEnvRegionName = getEnvRegionName;
|
|
573
|
-
exports.getEnvLocationLabel = getEnvLocationLabel;
|
|
574
|
-
__exportStar(require_terminal_websocket(), exports);
|
|
575
|
-
var EnvironmentStatus;
|
|
576
|
-
(function(EnvironmentStatus2) {
|
|
577
|
-
EnvironmentStatus2["Running"] = "running";
|
|
578
|
-
EnvironmentStatus2["Stopped"] = "stopped";
|
|
579
|
-
EnvironmentStatus2["Creating"] = "creating";
|
|
580
|
-
EnvironmentStatus2["Deleting"] = "deleting";
|
|
581
|
-
EnvironmentStatus2["Starting"] = "starting";
|
|
582
|
-
EnvironmentStatus2["Stopping"] = "stopping";
|
|
583
|
-
EnvironmentStatus2["Initializing"] = "initializing";
|
|
584
|
-
})(EnvironmentStatus || (exports.EnvironmentStatus = EnvironmentStatus = {}));
|
|
585
|
-
var ActionStatus;
|
|
586
|
-
(function(ActionStatus2) {
|
|
587
|
-
ActionStatus2["Running"] = "running";
|
|
588
|
-
ActionStatus2["Success"] = "success";
|
|
589
|
-
ActionStatus2["Error"] = "error";
|
|
590
|
-
})(ActionStatus || (exports.ActionStatus = ActionStatus = {}));
|
|
591
|
-
var VolumeStatus;
|
|
592
|
-
(function(VolumeStatus2) {
|
|
593
|
-
VolumeStatus2["Creating"] = "creating";
|
|
594
|
-
VolumeStatus2["Available"] = "available";
|
|
595
|
-
VolumeStatus2["Deleting"] = "deleting";
|
|
596
|
-
})(VolumeStatus || (exports.VolumeStatus = VolumeStatus = {}));
|
|
597
|
-
var LoopStatus;
|
|
598
|
-
(function(LoopStatus2) {
|
|
599
|
-
LoopStatus2["Running"] = "running";
|
|
600
|
-
LoopStatus2["Stopped"] = "stopped";
|
|
601
|
-
LoopStatus2["Error"] = "error";
|
|
602
|
-
LoopStatus2["Completed"] = "completed";
|
|
603
|
-
})(LoopStatus || (exports.LoopStatus = LoopStatus = {}));
|
|
604
|
-
function getEnvLocation(env) {
|
|
605
|
-
if (!env)
|
|
606
|
-
return null;
|
|
607
|
-
return env.location || null;
|
|
608
|
-
}
|
|
609
|
-
function getEnvRegionName(env) {
|
|
610
|
-
var _a;
|
|
611
|
-
if (!env)
|
|
612
|
-
return "Unknown";
|
|
613
|
-
return ((_a = env.location) === null || _a === undefined ? undefined : _a.name) || "Unknown";
|
|
614
|
-
}
|
|
615
|
-
function getEnvLocationLabel(env) {
|
|
616
|
-
var _a, _b, _c;
|
|
617
|
-
if (!env)
|
|
618
|
-
return "Unknown";
|
|
619
|
-
if (((_a = env.location) === null || _a === undefined ? undefined : _a.city) && ((_b = env.location) === null || _b === undefined ? undefined : _b.country)) {
|
|
620
|
-
return "".concat(env.location.city, ", ").concat(env.location.country);
|
|
621
|
-
}
|
|
622
|
-
return ((_c = env.location) === null || _c === undefined ? undefined : _c.name) || "Unknown";
|
|
623
|
-
}
|
|
624
512
|
});
|
|
625
513
|
|
|
626
514
|
// src/tmux.ts
|
|
627
515
|
init_pool();
|
|
628
516
|
|
|
629
|
-
//
|
|
517
|
+
// ../ssh/flags.js
|
|
630
518
|
function sshConfig(key, value) {
|
|
631
519
|
return { type: "config", key, value };
|
|
632
520
|
}
|
|
@@ -1265,7 +1153,7 @@ class TmuxSessionManager {
|
|
|
1265
1153
|
return { success: false, error: `Node ${nodeId} is not running` };
|
|
1266
1154
|
}
|
|
1267
1155
|
try {
|
|
1268
|
-
const result = await createOrAttachTmuxSession(node.ip, node.user, node.keyPath, {
|
|
1156
|
+
const result = await createOrAttachTmuxSession(node.ip, node.user, node.keyPath, {});
|
|
1269
1157
|
return {
|
|
1270
1158
|
success: true,
|
|
1271
1159
|
sshArgs: result.sshArgs,
|
|
@@ -1697,10 +1585,10 @@ async function testSSHConnection(options) {
|
|
|
1697
1585
|
init_pool();
|
|
1698
1586
|
// src/scp.ts
|
|
1699
1587
|
init_error();
|
|
1588
|
+
init_ssh();
|
|
1700
1589
|
init_pool();
|
|
1701
|
-
var import_ssh2 = __toESM(require_ssh(), 1);
|
|
1702
1590
|
async function scpUpload(options) {
|
|
1703
|
-
const validated =
|
|
1591
|
+
const validated = SCPOptionsSchema.safeParse(options);
|
|
1704
1592
|
if (!validated.success) {
|
|
1705
1593
|
throw new Error(`Invalid SCP options: ${validated.error.issues.map((i) => i.message).join(", ")}`);
|
|
1706
1594
|
}
|
|
@@ -1727,7 +1615,7 @@ async function scpUpload(options) {
|
|
|
1727
1615
|
}
|
|
1728
1616
|
}
|
|
1729
1617
|
async function scpDownload(options) {
|
|
1730
|
-
const validated =
|
|
1618
|
+
const validated = SCPOptionsSchema.safeParse(options);
|
|
1731
1619
|
if (!validated.success) {
|
|
1732
1620
|
throw new Error(`Invalid SCP options: ${validated.error.issues.map((i) => i.message).join(", ")}`);
|
|
1733
1621
|
}
|
|
@@ -1989,7 +1877,63 @@ init_pool();
|
|
|
1989
1877
|
|
|
1990
1878
|
// src/sessions.ts
|
|
1991
1879
|
import path2 from "path";
|
|
1992
|
-
|
|
1880
|
+
|
|
1881
|
+
// ../codespaces-types/compile/index.js
|
|
1882
|
+
var WebSocketCloseCode = {
|
|
1883
|
+
NORMAL_CLOSURE: 1000,
|
|
1884
|
+
ENDPOINT_GOING_AWAY: 1001,
|
|
1885
|
+
PROTOCOL_ERROR: 1002,
|
|
1886
|
+
UNSUPPORTED_DATA: 1003,
|
|
1887
|
+
NO_STATUS_RECEIVED: 1005,
|
|
1888
|
+
ABNORMAL_CLOSURE: 1006,
|
|
1889
|
+
INVALID_FRAME_PAYLOAD_DATA: 1007,
|
|
1890
|
+
POLICY_VIOLATION: 1008,
|
|
1891
|
+
MESSAGE_TOO_BIG: 1009,
|
|
1892
|
+
MISSING_MANDATORY_EXTENSION: 1010,
|
|
1893
|
+
INTERNAL_ERROR: 1011,
|
|
1894
|
+
SERVICE_RESTART: 1012,
|
|
1895
|
+
TRY_AGAIN_LATER: 1013,
|
|
1896
|
+
SESSION_NOT_FOUND: 4001,
|
|
1897
|
+
SESSION_ALREADY_CLOSED: 4002,
|
|
1898
|
+
INVALID_MESSAGE_FORMAT: 4003,
|
|
1899
|
+
AUTHENTICATION_FAILED: 4004,
|
|
1900
|
+
CONNECTION_TIMEOUT: 4005,
|
|
1901
|
+
SESSION_LIMIT_REACHED: 4006,
|
|
1902
|
+
INVALID_HOST: 4007,
|
|
1903
|
+
SSH_CONNECTION_FAILED: 4008,
|
|
1904
|
+
NETWORK_BLOCKED: 4009
|
|
1905
|
+
};
|
|
1906
|
+
var EnvironmentStatus;
|
|
1907
|
+
((EnvironmentStatus2) => {
|
|
1908
|
+
EnvironmentStatus2["Running"] = "running";
|
|
1909
|
+
EnvironmentStatus2["Stopped"] = "stopped";
|
|
1910
|
+
EnvironmentStatus2["Creating"] = "creating";
|
|
1911
|
+
EnvironmentStatus2["Deleting"] = "deleting";
|
|
1912
|
+
EnvironmentStatus2["Starting"] = "starting";
|
|
1913
|
+
EnvironmentStatus2["Stopping"] = "stopping";
|
|
1914
|
+
EnvironmentStatus2["Initializing"] = "initializing";
|
|
1915
|
+
})(EnvironmentStatus ||= {});
|
|
1916
|
+
var ActionStatus;
|
|
1917
|
+
((ActionStatus2) => {
|
|
1918
|
+
ActionStatus2["Running"] = "running";
|
|
1919
|
+
ActionStatus2["Success"] = "success";
|
|
1920
|
+
ActionStatus2["Error"] = "error";
|
|
1921
|
+
})(ActionStatus ||= {});
|
|
1922
|
+
var VolumeStatus;
|
|
1923
|
+
((VolumeStatus2) => {
|
|
1924
|
+
VolumeStatus2["Creating"] = "creating";
|
|
1925
|
+
VolumeStatus2["Available"] = "available";
|
|
1926
|
+
VolumeStatus2["Deleting"] = "deleting";
|
|
1927
|
+
})(VolumeStatus ||= {});
|
|
1928
|
+
var LoopStatus;
|
|
1929
|
+
((LoopStatus2) => {
|
|
1930
|
+
LoopStatus2["Running"] = "running";
|
|
1931
|
+
LoopStatus2["Stopped"] = "stopped";
|
|
1932
|
+
LoopStatus2["Error"] = "error";
|
|
1933
|
+
LoopStatus2["Completed"] = "completed";
|
|
1934
|
+
})(LoopStatus ||= {});
|
|
1935
|
+
|
|
1936
|
+
// src/sessions.ts
|
|
1993
1937
|
var metadataModule = null;
|
|
1994
1938
|
function loadMetadata() {
|
|
1995
1939
|
if (metadataModule === null) {
|
|
@@ -2030,7 +1974,7 @@ async function closeSession(sessionId) {
|
|
|
2030
1974
|
} catch {}
|
|
2031
1975
|
}
|
|
2032
1976
|
try {
|
|
2033
|
-
session.ws?.close(
|
|
1977
|
+
session.ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, "Session closed");
|
|
2034
1978
|
} catch {}
|
|
2035
1979
|
if (session.bootstrapLogStreamer) {
|
|
2036
1980
|
try {
|
|
@@ -2350,14 +2294,14 @@ async function getOrCreateSession(host, user = "root", sessionId = null, keyPath
|
|
|
2350
2294
|
console.log(`[Terminal] Process for ${newSessionId} exited with code ${exitCode}`);
|
|
2351
2295
|
session.closed = true;
|
|
2352
2296
|
try {
|
|
2353
|
-
session.ws?.close(
|
|
2297
|
+
session.ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, `SSH process exited (code: ${exitCode})`);
|
|
2354
2298
|
} catch {}
|
|
2355
2299
|
terminalSessions.delete(newSessionId);
|
|
2356
2300
|
}).catch((err) => {
|
|
2357
2301
|
console.log(`[Terminal] Process exit error for ${newSessionId}:`, err);
|
|
2358
2302
|
session.closed = true;
|
|
2359
2303
|
try {
|
|
2360
|
-
session.ws?.close(
|
|
2304
|
+
session.ws?.close(WebSocketCloseCode.INTERNAL_ERROR, "SSH process error");
|
|
2361
2305
|
} catch {}
|
|
2362
2306
|
});
|
|
2363
2307
|
return session;
|
|
@@ -2439,6 +2383,12 @@ var DEFAULT_CONFIG3 = {
|
|
|
2439
2383
|
historyLimit: 1e4,
|
|
2440
2384
|
sessionAgeLimit: 30 * 24 * 60 * 60 * 1000
|
|
2441
2385
|
};
|
|
2386
|
+
// src/config.ts
|
|
2387
|
+
import { join, dirname, isAbsolute, resolve } from "path";
|
|
2388
|
+
import { exec as exec2 } from "child_process";
|
|
2389
|
+
import { promisify as promisify3 } from "util";
|
|
2390
|
+
var execAsync3 = promisify3(exec2);
|
|
2391
|
+
var SSH_CONFIG_PATH = join(process.env.HOME || "~", ".ssh", "config");
|
|
2442
2392
|
// src/mcp/index.ts
|
|
2443
2393
|
async function listSessionsTool() {
|
|
2444
2394
|
const sessions = getAllSessionInfo();
|
package/dist/sessions.d.ts
CHANGED
|
@@ -20,7 +20,9 @@ export interface TerminalSession {
|
|
|
20
20
|
createdAt: number;
|
|
21
21
|
lastUsed: number;
|
|
22
22
|
reader: ReadableStreamDefaultReader<Uint8Array> | null;
|
|
23
|
-
stderrReader: ReadableStreamDefaultReader<Uint8Array>
|
|
23
|
+
stderrReader: (ReadableStreamDefaultReader<Uint8Array> & {
|
|
24
|
+
readMany?: () => Promise<any>;
|
|
25
|
+
}) | null;
|
|
24
26
|
writer: WritableStreamDefaultWriter<Uint8Array> | null;
|
|
25
27
|
closed: boolean;
|
|
26
28
|
tmuxSessionName?: string;
|
|
Binary file
|
package/dist/tmux-manager.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface TmuxSession {
|
|
|
46
46
|
/**
|
|
47
47
|
* Detailed session information with windows and panes
|
|
48
48
|
*/
|
|
49
|
-
export interface DetailedTmuxSession extends TmuxSession {
|
|
49
|
+
export interface DetailedTmuxSession extends Omit<TmuxSession, 'windows'> {
|
|
50
50
|
windows: Array<{
|
|
51
51
|
index: string;
|
|
52
52
|
name: string;
|
|
@@ -95,8 +95,8 @@ export interface CreateSessionOptions {
|
|
|
95
95
|
* Options for batch command execution
|
|
96
96
|
*/
|
|
97
97
|
export interface BatchCommandOptions {
|
|
98
|
-
/** Command to execute */
|
|
99
|
-
command
|
|
98
|
+
/** Command to execute (optional - passed separately to sendCommandToNodes) */
|
|
99
|
+
command?: string;
|
|
100
100
|
/** Target pane index (default: "0") */
|
|
101
101
|
paneIndex?: string;
|
|
102
102
|
/** Execute in parallel (default: true) */
|
|
@@ -293,6 +293,7 @@ export declare class TmuxSessionManager {
|
|
|
293
293
|
success: boolean;
|
|
294
294
|
cleaned?: number;
|
|
295
295
|
errors?: string[];
|
|
296
|
+
error?: string;
|
|
296
297
|
}>;
|
|
297
298
|
/**
|
|
298
299
|
* Get resource usage for sessions on a node
|
package/package.json
CHANGED
|
@@ -1,29 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/terminal",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Terminal session management with tmux integration, SSH client, WebSocket support, and MCP interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./mcp": {
|
|
14
|
+
"types": "./dist/mcp/index.d.ts",
|
|
15
|
+
"default": "./dist/mcp/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./dist/types.d.ts",
|
|
19
|
+
"default": "./dist/types.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./dist/client.d.ts",
|
|
23
|
+
"default": "./dist/client.js"
|
|
24
|
+
}
|
|
12
25
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "bun build src/index.ts --outdir dist --target bun --external 'node-ssh' --external 'hono' --external 'zod' --external 'node:*' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external 'node-ssh' --external 'hono' --external 'zod' --external 'node:*' --external '@modelcontextprotocol/*'",
|
|
28
|
+
"prepublishOnly": "bun run build"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"terminal",
|
|
35
|
+
"tmux",
|
|
36
|
+
"ssh",
|
|
37
|
+
"session",
|
|
38
|
+
"websocket",
|
|
39
|
+
"mcp",
|
|
40
|
+
"pty",
|
|
41
|
+
"cli",
|
|
42
|
+
"remote"
|
|
43
|
+
],
|
|
44
|
+
"author": "Ebowwa Labs <labs@ebowwa.com>",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"homepage": "https://github.com/ebowwa/terminal#readme",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/ebowwa/terminal.git",
|
|
50
|
+
"directory": "/"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/ebowwa/terminal/issues"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@ebowwa/codespaces-types": "^1.4.0",
|
|
60
|
+
"@ebowwa/ssh": "^0.2.1",
|
|
61
|
+
"node-ssh": "^13.2.1",
|
|
62
|
+
"hono": "^4.11.3",
|
|
63
|
+
"zod": "^3.24.1"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/bun": "latest",
|
|
67
|
+
"@types/node-ssh": "^11.0.0",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
69
|
+
},
|
|
70
|
+
"ownership": {
|
|
71
|
+
"domain": "terminal",
|
|
72
|
+
"responsibilities": [
|
|
73
|
+
"ssh",
|
|
74
|
+
"tmux",
|
|
75
|
+
"session-management"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
29
78
|
}
|
package/dist/mcp/index.d.ts
DELETED