@ebowwa/terminal 0.2.1 → 0.3.1

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/tmux.d.ts ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * tmux-based Terminal Sessions
3
+ * Provides persistent terminal sessions using tmux on remote servers
4
+ * Includes automatic tmux installation and session management
5
+ */
6
+ import type { SSHOptions } from "./types.js";
7
+ /**
8
+ * tmux session configuration
9
+ */
10
+ interface TmuxConfig {
11
+ /** Session name prefix for codespaces sessions */
12
+ sessionPrefix: string;
13
+ /** Default shell to use in tmux */
14
+ defaultShell: string;
15
+ /** Terminal type */
16
+ term: string;
17
+ /** Timeout for SSH commands (seconds) */
18
+ timeout: number;
19
+ /** Scrollback limit (lines) */
20
+ historyLimit: number;
21
+ /** Session age limit for cleanup (milliseconds) */
22
+ sessionAgeLimit: number;
23
+ }
24
+ /**
25
+ * Generate a tmux session name for a host
26
+ */
27
+ export declare function generateSessionName(host: string, user?: string): string;
28
+ /**
29
+ * Check if tmux is installed on the remote server
30
+ */
31
+ export declare function isTmuxInstalled(options: SSHOptions): Promise<boolean>;
32
+ /**
33
+ * Install tmux on the remote server
34
+ *
35
+ * FALLBACK MECHANISM: This should NOT be the primary installation method.
36
+ * tmux should be installed via cloud-init during initial node provisioning.
37
+ *
38
+ * This function exists for:
39
+ * - Legacy nodes provisioned before cloud-init included tmux
40
+ * - Manual node provisioning outside cheapspaces
41
+ * - Recovery scenarios where cloud-init failed
42
+ *
43
+ * @see workspace/docs/design/node-agent/TMUX-INSTALLATION.md
44
+ * @see workspace/src/lib/bootstrap/cloud-init.ts - where tmux should be added to packages
45
+ *
46
+ * Supports Debian/Ubuntu (apt) and CentOS/RHEL (yum/dnf)
47
+ */
48
+ export declare function installTmux(options: SSHOptions): Promise<{
49
+ success: boolean;
50
+ message: string;
51
+ }>;
52
+ /**
53
+ * Ensure tmux is available, installing if necessary
54
+ */
55
+ export declare function ensureTmux(options: SSHOptions): Promise<{
56
+ success: boolean;
57
+ message: string;
58
+ }>;
59
+ /**
60
+ * List existing tmux sessions on the remote server
61
+ */
62
+ export declare function listTmuxSessions(options: SSHOptions): Promise<string[]>;
63
+ /**
64
+ * Check if a specific tmux session exists
65
+ */
66
+ export declare function hasTmuxSession(sessionName: string, options: SSHOptions): Promise<boolean>;
67
+ /**
68
+ * Create or attach to a tmux session
69
+ * Returns the SSH command arguments to connect to the tmux session
70
+ */
71
+ export declare function createOrAttachTmuxSession(host: string, user?: string, keyPath?: string, config?: Partial<TmuxConfig>): Promise<{
72
+ sshArgs: string[];
73
+ sessionName: string;
74
+ newlyCreated: boolean;
75
+ }>;
76
+ /**
77
+ * Kill a tmux session on the remote server
78
+ */
79
+ export declare function killTmuxSession(sessionName: string, options: SSHOptions): Promise<boolean>;
80
+ /**
81
+ * Get tmux session information
82
+ */
83
+ export declare function getTmuxSessionInfo(sessionName: string, options: SSHOptions): Promise<{
84
+ exists: boolean;
85
+ windows?: number;
86
+ panes?: number;
87
+ } | null>;
88
+ /**
89
+ * Cleanup old tmux sessions on a remote server
90
+ * Kills sessions with matching prefix that are older than specified age limit
91
+ * @param options SSH connection options
92
+ * @param config Optional configuration (uses default age limit if not provided)
93
+ * @returns Object with cleaned count and errors
94
+ */
95
+ export declare function cleanupOldTmuxSessions(options: SSHOptions, config?: Partial<TmuxConfig>): Promise<{
96
+ cleaned: number;
97
+ errors: string[];
98
+ }>;
99
+ /**
100
+ * Get resource usage information for tmux sessions on a remote server
101
+ * @param options SSH connection options
102
+ * @returns Resource usage summary
103
+ */
104
+ export declare function getTmuxResourceUsage(options: SSHOptions): Promise<{
105
+ totalSessions: number;
106
+ codespacesSessions: number;
107
+ estimatedMemoryMB: number;
108
+ } | null>;
109
+ /**
110
+ * Send a command to a specific pane in a tmux session
111
+ * @param sessionName Target tmux session name
112
+ * @param paneIndex Pane index (default: 0 for first pane)
113
+ * @param command Command to execute (sent as keystrokes)
114
+ * @param options SSH connection options
115
+ */
116
+ export declare function sendCommandToPane(sessionName: string, command: string, paneIndex: string, options: SSHOptions): Promise<boolean>;
117
+ /**
118
+ * Split a pane horizontally or vertically in a tmux session
119
+ * @param sessionName Target tmux session name
120
+ * @param windowIndex Window index (default: 0)
121
+ * @param direction Split direction: "h" (horizontal) or "v" (vertical)
122
+ * @param command Optional command to run in the new pane
123
+ * @param options SSH connection options
124
+ * @returns The new pane index
125
+ */
126
+ export declare function splitPane(sessionName: string, direction: "h" | "v", command: string | null, options: SSHOptions): Promise<string | null>;
127
+ /**
128
+ * List all windows in a tmux session
129
+ * @param sessionName Target tmux session name
130
+ * @param options SSH connection options
131
+ */
132
+ export declare function listSessionWindows(sessionName: string, options: SSHOptions): Promise<Array<{
133
+ index: string;
134
+ name: string;
135
+ active: boolean;
136
+ }>>;
137
+ /**
138
+ * List all panes in a tmux session window
139
+ * @param sessionName Target tmux session name
140
+ * @param windowIndex Window index (default: 0)
141
+ * @param options SSH connection options
142
+ */
143
+ export declare function listWindowPanes(sessionName: string, windowIndex: string, options: SSHOptions): Promise<Array<{
144
+ index: string;
145
+ currentPath: string;
146
+ pid: string;
147
+ active: boolean;
148
+ }>>;
149
+ /**
150
+ * Capture the current output of a pane
151
+ * @param sessionName Target tmux session name
152
+ * @param paneIndex Pane index (default: 0)
153
+ * @param options SSH connection options
154
+ */
155
+ export declare function capturePane(sessionName: string, paneIndex: string, options: SSHOptions): Promise<string | null>;
156
+ /**
157
+ * Get scrollback/history from a pane
158
+ * @param sessionName Target tmux session name
159
+ * @param paneIndex Pane index (default: 0)
160
+ * @param lines Number of lines to retrieve (default: all)
161
+ * @param options SSH connection options
162
+ */
163
+ export declare function getPaneHistory(sessionName: string, paneIndex: string, lines: number, options: SSHOptions): Promise<string | null>;
164
+ /**
165
+ * Switch to a specific window in a tmux session
166
+ * @param sessionName Target tmux session name
167
+ * @param windowIndex Target window index
168
+ * @param options SSH connection options
169
+ */
170
+ export declare function switchWindow(sessionName: string, windowIndex: string, options: SSHOptions): Promise<boolean>;
171
+ /**
172
+ * Switch to a specific pane in a tmux session window
173
+ * @param sessionName Target tmux session name
174
+ * @param paneIndex Target pane index (e.g., "0", "1", "0.1" for window.pane)
175
+ * @param options SSH connection options
176
+ */
177
+ export declare function switchPane(sessionName: string, paneIndex: string, options: SSHOptions): Promise<boolean>;
178
+ /**
179
+ * Rename a window in a tmux session
180
+ * @param sessionName Target tmux session name
181
+ * @param windowIndex Window index (default: 0)
182
+ * @param newName New window name
183
+ * @param options SSH connection options
184
+ */
185
+ export declare function renameWindow(sessionName: string, windowIndex: string, newName: string, options: SSHOptions): Promise<boolean>;
186
+ /**
187
+ * Kill a specific pane in a tmux session
188
+ * @param sessionName Target tmux session name
189
+ * @param paneIndex Pane index to kill
190
+ * @param options SSH connection options
191
+ */
192
+ export declare function killPane(sessionName: string, paneIndex: string, options: SSHOptions): Promise<boolean>;
193
+ /**
194
+ * Get detailed information about all panes in a session
195
+ * @param sessionName Target tmux session name
196
+ * @param options SSH connection options
197
+ */
198
+ export declare function getDetailedSessionInfo(sessionName: string, options: SSHOptions): Promise<{
199
+ exists: boolean;
200
+ windows: Array<{
201
+ index: string;
202
+ name: string;
203
+ active: boolean;
204
+ panes: Array<{
205
+ index: string;
206
+ currentPath: string;
207
+ pid: string;
208
+ active: boolean;
209
+ }>;
210
+ }>;
211
+ } | null>;
212
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SSH type definitions
3
+ */
4
+ export interface SSHOptions {
5
+ host: string;
6
+ user?: string;
7
+ timeout?: number;
8
+ port?: number;
9
+ keyPath?: string;
10
+ password?: string;
11
+ }
12
+ export interface SCPOptions extends SSHOptions {
13
+ source: string;
14
+ destination: string;
15
+ recursive?: boolean;
16
+ preserve?: boolean;
17
+ }
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/terminal-mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Terminal session management MCP server with SSH, tmux, and file operations",
5
5
  "type": "module",
6
6
  "main": "stdio.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/terminal",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",
@@ -76,10 +76,8 @@
76
76
  }
77
77
  },
78
78
  "scripts": {
79
- "build": "bun build src/index.ts --outdir dist --target node && bun build src/mcp/index.ts --outdir dist/mcp --target node",
80
- "mcp": "bun run src/mcp/index.ts",
81
- "mcp-dev": "MCP_PORT=8913 bun run src/mcp/index.ts",
82
- "mcp-stdio": "bun run src/mcp/stdio.ts"
79
+ "build": "bun build src/index.ts --outdir dist --target bun --external '@ebowwa/*' --external 'node-ssh' --external 'hono' --external 'zod' && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external '@ebowwa/*' --external 'node-ssh' --external 'hono' --external 'zod' --external '@modelcontextprotocol/*'",
80
+ "prepublishOnly": "bun run build"
83
81
  },
84
82
  "keywords": [
85
83
  "terminal",
@@ -107,13 +105,15 @@
107
105
  "node": ">=18.0.0"
108
106
  },
109
107
  "dependencies": {
110
- "@ebowwa/codespaces-types": "^1.1.0",
108
+ "@ebowwa/codespaces-types": "^1.3.0",
109
+ "@ebowwa/ssh": "^0.1.0",
111
110
  "node-ssh": "^13.2.1",
112
111
  "hono": "^4.11.3",
113
112
  "zod": "^3.24.1"
114
113
  },
115
114
  "devDependencies": {
116
115
  "@types/bun": "latest",
117
- "@types/node-ssh": "^11.0.0"
116
+ "@types/node-ssh": "^11.0.0",
117
+ "typescript": "^5.9.3"
118
118
  }
119
119
  }
package/src/mcp/stdio.js CHANGED
@@ -51,7 +51,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  // Re-export terminal functions
54
- var index_js_1 = require("../index.js");
54
+ var terminal_1 = require("@ebowwa/terminal");
55
55
  // ==============
56
56
  // Tool Implementations
57
57
  //=============
@@ -59,7 +59,7 @@ function listSessionsTool() {
59
59
  return __awaiter(this, void 0, void 0, function () {
60
60
  var sessions, lines, _i, sessions_1, session, active;
61
61
  return __generator(this, function (_a) {
62
- sessions = (0, index_js_1.getAllSessionInfo)();
62
+ sessions = (0, terminal_1.getAllSessionInfo)();
63
63
  lines = [
64
64
  "🖥️ Active Terminal Sessions",
65
65
  "=".repeat(50),
@@ -90,7 +90,7 @@ function getSessionInfoTool(sessionId) {
90
90
  switch (_a.label) {
91
91
  case 0:
92
92
  _a.trys.push([0, 2, , 3]);
93
- return [4 /*yield*/, (0, index_js_1.getSessionInfo)(sessionId)];
93
+ return [4 /*yield*/, (0, terminal_1.getSessionInfo)(sessionId)];
94
94
  case 1:
95
95
  info = _a.sent();
96
96
  lines = [
@@ -105,7 +105,7 @@ function getSessionInfoTool(sessionId) {
105
105
  if (info.lastActivity) {
106
106
  lines.push("Last activity: ".concat(new Date(info.lastActivity).toLocaleString()));
107
107
  }
108
- session = (0, index_js_1.getSession)(sessionId);
108
+ session = (0, terminal_1.getSession)(sessionId);
109
109
  if (session) {
110
110
  lines.push("");
111
111
  lines.push("PTY session: ".concat(session.ptyId || "N/A"));
@@ -128,7 +128,7 @@ function createSessionTool(host_1) {
128
128
  switch (_a.label) {
129
129
  case 0:
130
130
  _a.trys.push([0, 2, , 3]);
131
- return [4 /*yield*/, (0, index_js_1.getOrCreateSession)(host, {
131
+ return [4 /*yield*/, (0, terminal_1.getOrCreateSession)(host, {
132
132
  type: type,
133
133
  command: command,
134
134
  })];
@@ -150,11 +150,11 @@ function writeCommandTool(sessionId, command) {
150
150
  switch (_a.label) {
151
151
  case 0:
152
152
  _a.trys.push([0, 2, , 3]);
153
- session = (0, index_js_1.getSession)(sessionId);
153
+ session = (0, terminal_1.getSession)(sessionId);
154
154
  if (!session) {
155
155
  throw new Error("Session ".concat(sessionId, " not found"));
156
156
  }
157
- return [4 /*yield*/, (0, index_js_1.writeToSession)(sessionId, command)];
157
+ return [4 /*yield*/, (0, terminal_1.writeToSession)(sessionId, command)];
158
158
  case 1:
159
159
  _a.sent();
160
160
  return [2 /*return*/, "\u2713 Command sent to session ".concat(sessionId)];
@@ -173,7 +173,7 @@ function resizeSessionTool(sessionId, rows, cols) {
173
173
  switch (_a.label) {
174
174
  case 0:
175
175
  _a.trys.push([0, 2, , 3]);
176
- return [4 /*yield*/, (0, index_js_1.resizeSession)(sessionId, rows, cols)];
176
+ return [4 /*yield*/, (0, terminal_1.resizeSession)(sessionId, rows, cols)];
177
177
  case 1:
178
178
  _a.sent();
179
179
  return [2 /*return*/, "\u2713 Resized session ".concat(sessionId, " to ").concat(rows, "x").concat(cols)];
@@ -192,7 +192,7 @@ function closeSessionTool(sessionId) {
192
192
  switch (_a.label) {
193
193
  case 0:
194
194
  _a.trys.push([0, 2, , 3]);
195
- return [4 /*yield*/, (0, index_js_1.closeSession)(sessionId)];
195
+ return [4 /*yield*/, (0, terminal_1.closeSession)(sessionId)];
196
196
  case 1:
197
197
  _a.sent();
198
198
  return [2 /*return*/, "\u2713 Closed session ".concat(sessionId)];
@@ -211,7 +211,7 @@ function execSSHTool(host, command, options) {
211
211
  switch (_a.label) {
212
212
  case 0:
213
213
  _a.trys.push([0, 2, , 3]);
214
- return [4 /*yield*/, (0, index_js_1.execSSH)(host, command, options)];
214
+ return [4 /*yield*/, (0, terminal_1.execSSH)(host, command, options)];
215
215
  case 1:
216
216
  result = _a.sent();
217
217
  return [2 /*return*/, result.stdout || result.stderr || "Command executed"];
@@ -230,7 +230,7 @@ function testConnectionTool(host) {
230
230
  switch (_a.label) {
231
231
  case 0:
232
232
  _a.trys.push([0, 2, , 3]);
233
- return [4 /*yield*/, (0, index_js_1.testSSHConnection)(host)];
233
+ return [4 /*yield*/, (0, terminal_1.testSSHConnection)(host)];
234
234
  case 1:
235
235
  result = _a.sent();
236
236
  if (result.success) {
@@ -255,10 +255,10 @@ function listTmuxSessionsTool() {
255
255
  switch (_a.label) {
256
256
  case 0:
257
257
  _a.trys.push([0, 3, , 4]);
258
- return [4 /*yield*/, (0, index_js_1.ensureTmux)()];
258
+ return [4 /*yield*/, (0, terminal_1.ensureTmux)()];
259
259
  case 1:
260
260
  _a.sent();
261
- return [4 /*yield*/, (0, index_js_1.listTmuxSessions)()];
261
+ return [4 /*yield*/, (0, terminal_1.listTmuxSessions)()];
262
262
  case 2:
263
263
  sessions = _a.sent();
264
264
  lines = [
@@ -295,7 +295,7 @@ function listFilesTool(host_1) {
295
295
  switch (_a.label) {
296
296
  case 0:
297
297
  _a.trys.push([0, 2, , 3]);
298
- return [4 /*yield*/, (0, index_js_1.listFiles)(host, path)];
298
+ return [4 /*yield*/, (0, terminal_1.listFiles)(host, path)];
299
299
  case 1:
300
300
  files = _a.sent();
301
301
  lines = [
@@ -325,7 +325,7 @@ function uploadFileTool(host, localPath, remotePath) {
325
325
  switch (_a.label) {
326
326
  case 0:
327
327
  _a.trys.push([0, 2, , 3]);
328
- return [4 /*yield*/, (0, index_js_1.scpUpload)(host, { localPath: localPath, remotePath: remotePath })];
328
+ return [4 /*yield*/, (0, terminal_1.scpUpload)(host, { localPath: localPath, remotePath: remotePath })];
329
329
  case 1:
330
330
  _a.sent();
331
331
  return [2 /*return*/, "\u2713 Uploaded ".concat(localPath, " to ").concat(host, ":").concat(remotePath)];
@@ -344,7 +344,7 @@ function downloadFileTool(host, remotePath, localPath) {
344
344
  switch (_a.label) {
345
345
  case 0:
346
346
  _a.trys.push([0, 2, , 3]);
347
- return [4 /*yield*/, (0, index_js_1.scpDownload)(host, { remotePath: remotePath, localPath: localPath })];
347
+ return [4 /*yield*/, (0, terminal_1.scpDownload)(host, { remotePath: remotePath, localPath: localPath })];
348
348
  case 1:
349
349
  _a.sent();
350
350
  return [2 /*return*/, "\u2713 Downloaded ".concat(host, ":").concat(remotePath, " to ").concat(localPath)];
@@ -361,7 +361,7 @@ function getActiveConnectionsTool() {
361
361
  var connections, lines, _i, connections_1, conn;
362
362
  return __generator(this, function (_a) {
363
363
  try {
364
- connections = (0, index_js_1.getActiveSSHConnections)();
364
+ connections = (0, terminal_1.getActiveSSHConnections)();
365
365
  lines = [
366
366
  "🔗 Active SSH Connections",
367
367
  "=".repeat(50),
@@ -642,7 +642,7 @@ function handleToolCall(name, args) {
642
642
  case 12: return [2 /*return*/, _b.sent()];
643
643
  case 13: return [4 /*yield*/, execSSHTool(args.host, args.command, args.options)];
644
644
  case 14: return [2 /*return*/, _b.sent()];
645
- case 15: return [4 /*yield*/, testConnection(args.host)];
645
+ case 15: return [4 /*yield*/, testConnectionTool(args.host)];
646
646
  case 16: return [2 /*return*/, _b.sent()];
647
647
  case 17: return [4 /*yield*/, listTmuxSessionsTool()];
648
648
  case 18: return [2 /*return*/, _b.sent()];
package/src/tmux.js CHANGED
@@ -84,7 +84,7 @@ exports.renameWindow = renameWindow;
84
84
  exports.killPane = killPane;
85
85
  exports.getDetailedSessionInfo = getDetailedSessionInfo;
86
86
  var pool_js_1 = require("./pool.js");
87
- var ssh_1 = require("@codespaces/ssh");
87
+ var flags_1 = require("@ebowwa/ssh/flags");
88
88
  var DEFAULT_CONFIG = {
89
89
  sessionPrefix: "codespaces",
90
90
  defaultShell: "/bin/bash",
@@ -273,14 +273,14 @@ function createOrAttachTmuxSession(host_1) {
273
273
  return [4 /*yield*/, hasTmuxSession(sessionName, sshOptions)];
274
274
  case 2:
275
275
  sessionExists = _b.sent();
276
- flags = __spreadArray(__spreadArray([], ssh_1.SSHPresets.default, true), [
277
- ssh_1.SSHFlags.port(22),
278
- ssh_1.SSHFlags.forceTTY(2), // -tt for forced PTY
276
+ flags = __spreadArray(__spreadArray([], flags_1.SSHPresets.default, true), [
277
+ flags_1.SSHFlags.port(22),
278
+ flags_1.SSHFlags.forceTTY(2), // -tt for forced PTY
279
279
  ], false);
280
280
  if (keyPath) {
281
- flags.push(ssh_1.SSHFlags.identity(String(keyPath)));
281
+ flags.push(flags_1.SSHFlags.identity(String(keyPath)));
282
282
  }
283
- sshArgs = (0, ssh_1.buildSSHArgs)(flags, host, user);
283
+ sshArgs = (0, flags_1.buildSSHArgs)(flags, host, user);
284
284
  tmuxCmd = [
285
285
  "tmux",
286
286
  "new-session",
package/src/tmux.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { getSSHPool } from "./pool.js";
8
8
  import type { SSHOptions } from "./types.js";
9
- import { SSHFlags, SSHPresets, buildSSHArgs, sshConfig, type PTYOptions, type SSHConfigFlag } from "@codespaces/ssh";
9
+ import { SSHFlags, SSHPresets, buildSSHArgs, sshConfig, type PTYOptions, type SSHConfigFlag } from "@ebowwa/ssh/flags";
10
10
 
11
11
  /**
12
12
  * tmux session configuration
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["node"],
8
+ "declaration": true,
9
+ "declarationDir": "./dist",
10
+ "emitDeclarationOnly": true,
11
+ "strict": false,
12
+ "skipLibCheck": true,
13
+ "noImplicitAny": false,
14
+ "esModuleInterop": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "forceConsistentCasingInFileNames": false
18
+ },
19
+ "include": [
20
+ "src/**/*.ts"
21
+ ],
22
+ "exclude": [
23
+ "node_modules",
24
+ "dist",
25
+ "src/**/*.js",
26
+ "src/mcp"
27
+ ]
28
+ }