nikcli-remote 1.0.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.
@@ -0,0 +1,372 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ /**
4
+ * @nikcli/remote - Type definitions
5
+ */
6
+ type SessionStatus = 'starting' | 'waiting' | 'connected' | 'stopped' | 'error';
7
+ type TunnelProvider = 'localtunnel' | 'cloudflared' | 'ngrok' | 'none';
8
+ interface DeviceInfo {
9
+ id: string;
10
+ userAgent?: string;
11
+ connectedAt: Date;
12
+ lastActivity: Date;
13
+ ip?: string;
14
+ }
15
+ interface RemoteSession {
16
+ id: string;
17
+ name: string;
18
+ qrCode: string;
19
+ qrUrl: string;
20
+ localUrl: string;
21
+ tunnelUrl?: string;
22
+ status: SessionStatus;
23
+ connectedDevices: DeviceInfo[];
24
+ startedAt: Date;
25
+ lastActivity: Date;
26
+ error?: string;
27
+ port: number;
28
+ }
29
+ interface ServerConfig {
30
+ /** Port to listen on (0 = auto-assign) */
31
+ port: number;
32
+ /** Host to bind to */
33
+ host: string;
34
+ /** Enable tunnel for public access */
35
+ enableTunnel: boolean;
36
+ /** Tunnel provider */
37
+ tunnelProvider: TunnelProvider;
38
+ /** Session secret for authentication */
39
+ sessionSecret?: string;
40
+ /** Maximum concurrent connections */
41
+ maxConnections: number;
42
+ /** Heartbeat interval in ms */
43
+ heartbeatInterval: number;
44
+ /** Shell command to spawn */
45
+ shell: string;
46
+ /** Initial terminal columns */
47
+ cols: number;
48
+ /** Initial terminal rows */
49
+ rows: number;
50
+ /** Working directory for shell */
51
+ cwd?: string;
52
+ /** Environment variables */
53
+ env?: Record<string, string>;
54
+ /** Enable terminal (PTY) */
55
+ enableTerminal: boolean;
56
+ /** Session timeout in ms (0 = no timeout) */
57
+ sessionTimeout: number;
58
+ }
59
+ interface BroadcastMessage {
60
+ type: string;
61
+ payload: unknown;
62
+ timestamp?: number;
63
+ }
64
+ interface RemoteNotification {
65
+ type: 'success' | 'error' | 'warning' | 'info';
66
+ title: string;
67
+ body: string;
68
+ data?: unknown;
69
+ }
70
+ interface ClientMessage {
71
+ type: string;
72
+ [key: string]: unknown;
73
+ }
74
+ interface ServerMessage {
75
+ type: string;
76
+ payload?: unknown;
77
+ timestamp: number;
78
+ }
79
+ interface TerminalData {
80
+ data: string;
81
+ }
82
+ interface TerminalResize {
83
+ cols: number;
84
+ rows: number;
85
+ }
86
+ interface CommandMessage {
87
+ command: string;
88
+ args?: string[];
89
+ }
90
+ interface ClientConnection {
91
+ id: string;
92
+ authenticated: boolean;
93
+ device: DeviceInfo;
94
+ lastPing: number;
95
+ }
96
+ declare const DEFAULT_CONFIG: ServerConfig;
97
+ declare const MessageTypes: {
98
+ readonly AUTH_REQUIRED: "auth:required";
99
+ readonly AUTH: "auth";
100
+ readonly AUTH_SUCCESS: "auth:success";
101
+ readonly AUTH_FAILED: "auth:failed";
102
+ readonly TERMINAL_OUTPUT: "terminal:output";
103
+ readonly TERMINAL_INPUT: "terminal:input";
104
+ readonly TERMINAL_RESIZE: "terminal:resize";
105
+ readonly TERMINAL_EXIT: "terminal:exit";
106
+ readonly TERMINAL_CLEAR: "terminal:clear";
107
+ readonly NOTIFICATION: "notification";
108
+ readonly PING: "ping";
109
+ readonly PONG: "pong";
110
+ readonly SESSION_INFO: "session:info";
111
+ readonly SESSION_END: "session:end";
112
+ readonly COMMAND: "command";
113
+ readonly COMMAND_RESULT: "command:result";
114
+ readonly AGENT_START: "agent:start";
115
+ readonly AGENT_PROGRESS: "agent:progress";
116
+ readonly AGENT_COMPLETE: "agent:complete";
117
+ readonly AGENT_ERROR: "agent:error";
118
+ };
119
+
120
+ /**
121
+ * @nikcli/remote - Native Remote Server
122
+ * WebSocket-based remote terminal with mobile-friendly web client
123
+ */
124
+
125
+ interface RemoteServerEvents {
126
+ started: (session: RemoteSession) => void;
127
+ stopped: () => void;
128
+ 'client:connected': (device: DeviceInfo) => void;
129
+ 'client:disconnected': (device: DeviceInfo) => void;
130
+ 'client:error': (clientId: string, error: Error) => void;
131
+ 'tunnel:connected': (url: string) => void;
132
+ 'tunnel:error': (error: Error) => void;
133
+ command: (cmd: {
134
+ clientId: string;
135
+ command: string;
136
+ args?: string[];
137
+ }) => void;
138
+ message: (client: ClientConnection, message: ClientMessage) => void;
139
+ error: (error: Error) => void;
140
+ }
141
+ declare class RemoteServer extends EventEmitter {
142
+ private config;
143
+ private httpServer;
144
+ private wss;
145
+ private clients;
146
+ private session;
147
+ private terminal;
148
+ private tunnel;
149
+ private heartbeatTimer;
150
+ private sessionTimeoutTimer;
151
+ private isRunning;
152
+ private sessionSecret;
153
+ constructor(config?: Partial<ServerConfig>);
154
+ /**
155
+ * Start the remote server
156
+ */
157
+ start(options?: {
158
+ name?: string;
159
+ }): Promise<RemoteSession>;
160
+ /**
161
+ * Stop the server
162
+ */
163
+ stop(): Promise<void>;
164
+ /**
165
+ * Broadcast message to all authenticated clients
166
+ */
167
+ broadcast(message: BroadcastMessage): void;
168
+ /**
169
+ * Send notification to clients
170
+ */
171
+ notify(notification: RemoteNotification): void;
172
+ /**
173
+ * Get current session
174
+ */
175
+ getSession(): RemoteSession | null;
176
+ /**
177
+ * Check if server is running
178
+ */
179
+ isActive(): boolean;
180
+ /**
181
+ * Get connected client count
182
+ */
183
+ getConnectedCount(): number;
184
+ /**
185
+ * Write to terminal
186
+ */
187
+ writeToTerminal(data: string): void;
188
+ /**
189
+ * Resize terminal
190
+ */
191
+ resizeTerminal(cols: number, rows: number): void;
192
+ /**
193
+ * Setup WebSocket handlers
194
+ */
195
+ private setupWebSocketHandlers;
196
+ /**
197
+ * Handle client message
198
+ */
199
+ private handleClientMessage;
200
+ /**
201
+ * Handle authentication
202
+ */
203
+ private handleAuth;
204
+ /**
205
+ * Handle HTTP request
206
+ */
207
+ private handleHttpRequest;
208
+ /**
209
+ * Start heartbeat
210
+ */
211
+ private startHeartbeat;
212
+ /**
213
+ * Start session timeout
214
+ */
215
+ private startSessionTimeout;
216
+ /**
217
+ * Reset session timeout
218
+ */
219
+ private resetSessionTimeout;
220
+ /**
221
+ * Get local IP
222
+ */
223
+ private getLocalIP;
224
+ /**
225
+ * Generate session ID
226
+ */
227
+ private generateSessionId;
228
+ /**
229
+ * Generate client ID
230
+ */
231
+ private generateClientId;
232
+ /**
233
+ * Generate secret
234
+ */
235
+ private generateSecret;
236
+ }
237
+
238
+ /**
239
+ * @nikcli/remote - Terminal Manager
240
+ * PTY-based terminal with fallback to basic spawn
241
+ */
242
+
243
+ interface TerminalConfig {
244
+ shell: string;
245
+ cols: number;
246
+ rows: number;
247
+ cwd?: string;
248
+ env?: Record<string, string>;
249
+ }
250
+ declare class TerminalManager extends EventEmitter {
251
+ private config;
252
+ private ptyProcess;
253
+ private process;
254
+ private running;
255
+ constructor(config: TerminalConfig);
256
+ /**
257
+ * Start the terminal
258
+ */
259
+ start(): void;
260
+ /**
261
+ * Write data to terminal
262
+ */
263
+ write(data: string): void;
264
+ /**
265
+ * Resize terminal
266
+ */
267
+ resize(cols: number, rows: number): void;
268
+ /**
269
+ * Clear terminal
270
+ */
271
+ clear(): void;
272
+ /**
273
+ * Kill terminal process
274
+ */
275
+ destroy(): void;
276
+ /**
277
+ * Check if terminal is running
278
+ */
279
+ isRunning(): boolean;
280
+ /**
281
+ * Check if using PTY
282
+ */
283
+ hasPty(): boolean;
284
+ }
285
+
286
+ /**
287
+ * @nikcli/remote - Tunnel Manager
288
+ * Provides public URL access via various tunnel providers
289
+ */
290
+
291
+ declare class TunnelManager {
292
+ private provider;
293
+ private process;
294
+ private url;
295
+ private tunnelInstance;
296
+ constructor(provider: TunnelProvider);
297
+ /**
298
+ * Create tunnel and return public URL
299
+ */
300
+ create(port: number): Promise<string>;
301
+ /**
302
+ * Close tunnel
303
+ */
304
+ close(): Promise<void>;
305
+ /**
306
+ * Get tunnel URL
307
+ */
308
+ getUrl(): string | null;
309
+ /**
310
+ * Create localtunnel
311
+ */
312
+ private createLocaltunnel;
313
+ /**
314
+ * Create localtunnel via CLI
315
+ */
316
+ private createLocaltunnelCli;
317
+ /**
318
+ * Create cloudflared tunnel
319
+ */
320
+ private createCloudflared;
321
+ /**
322
+ * Create ngrok tunnel
323
+ */
324
+ private createNgrok;
325
+ }
326
+ /**
327
+ * Check if a tunnel provider is available
328
+ */
329
+ declare function checkTunnelAvailability(provider: TunnelProvider): Promise<boolean>;
330
+ /**
331
+ * Find best available tunnel provider
332
+ */
333
+ declare function findAvailableTunnel(): Promise<TunnelProvider | null>;
334
+
335
+ /**
336
+ * @nikcli/remote - QR Code Generator
337
+ * Terminal QR code rendering for session URLs
338
+ */
339
+
340
+ interface QROptions {
341
+ small?: boolean;
342
+ margin?: number;
343
+ }
344
+ /**
345
+ * Generate QR code string for terminal display
346
+ */
347
+ declare function generateQR(url: string, options?: QROptions): Promise<string>;
348
+ /**
349
+ * Generate QR code as data URL (for web/image)
350
+ */
351
+ declare function generateQRDataURL(url: string): Promise<string | null>;
352
+ /**
353
+ * Render session info with QR code
354
+ */
355
+ declare function renderSessionCard(session: RemoteSession): Promise<string>;
356
+ /**
357
+ * Simple progress bar
358
+ */
359
+ declare function progressBar(current: number, total: number, width?: number): string;
360
+
361
+ /**
362
+ * @nikcli/remote - Mobile Web Client
363
+ * Touch-friendly terminal interface for mobile devices
364
+ */
365
+ declare function getWebClient(): string;
366
+
367
+ declare function createRemoteServer(config?: Partial<ServerConfig>): Promise<{
368
+ server: RemoteServer;
369
+ session: RemoteSession;
370
+ }>;
371
+
372
+ export { type BroadcastMessage, type ClientConnection, type ClientMessage, type CommandMessage, DEFAULT_CONFIG, type DeviceInfo, MessageTypes, type RemoteNotification, RemoteServer, type RemoteServerEvents, type RemoteSession, type ServerConfig, type ServerMessage, type SessionStatus, type TerminalConfig, type TerminalData, TerminalManager, type TerminalResize, TunnelManager, type TunnelProvider, checkTunnelAvailability, createRemoteServer, findAvailableTunnel, generateQR, generateQRDataURL, getWebClient, progressBar, renderSessionCard };
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ import {
2
+ DEFAULT_CONFIG,
3
+ MessageTypes,
4
+ RemoteServer,
5
+ TerminalManager,
6
+ TunnelManager,
7
+ checkTunnelAvailability,
8
+ findAvailableTunnel,
9
+ getWebClient
10
+ } from "./chunk-TJTKYXIZ.js";
11
+ import {
12
+ __require
13
+ } from "./chunk-MCKGQKYU.js";
14
+
15
+ // src/qrcode.ts
16
+ var QRCode = null;
17
+ try {
18
+ QRCode = __require("qrcode");
19
+ } catch {
20
+ }
21
+ async function generateQR(url, options = {}) {
22
+ if (!QRCode) {
23
+ return generateFallbackQR(url);
24
+ }
25
+ try {
26
+ const qrString = await QRCode.toString(url, {
27
+ type: "terminal",
28
+ small: options.small ?? true,
29
+ margin: options.margin ?? 1
30
+ });
31
+ return qrString;
32
+ } catch {
33
+ return generateFallbackQR(url);
34
+ }
35
+ }
36
+ async function generateQRDataURL(url) {
37
+ if (!QRCode) return null;
38
+ try {
39
+ return await QRCode.toDataURL(url, {
40
+ margin: 2,
41
+ width: 256,
42
+ color: {
43
+ dark: "#000000",
44
+ light: "#ffffff"
45
+ }
46
+ });
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function generateFallbackQR(url) {
52
+ return `
53
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
54
+ \u2502 \u2502
55
+ \u2502 QR Code generation unavailable \u2502
56
+ \u2502 \u2502
57
+ \u2502 Install 'qrcode' package or \u2502
58
+ \u2502 visit the URL directly: \u2502
59
+ \u2502 \u2502
60
+ \u2502 ${url.substring(0, 35)}${url.length > 35 ? "..." : ""}
61
+ \u2502 \u2502
62
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
63
+ `;
64
+ }
65
+ async function renderSessionCard(session) {
66
+ const qr = await generateQR(session.qrUrl);
67
+ const statusIcon = getStatusIcon(session.status);
68
+ const statusColor = getStatusColor(session.status);
69
+ const lines = [
70
+ "",
71
+ "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
72
+ "\u2502 NikCLI Remote Session \u2502",
73
+ "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
74
+ ""
75
+ ];
76
+ const qrLines = qr.split("\n").filter((l) => l.trim());
77
+ for (const line of qrLines) {
78
+ lines.push(" " + line);
79
+ }
80
+ lines.push("");
81
+ lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
82
+ lines.push("");
83
+ lines.push(` Session: ${session.id}`);
84
+ lines.push(` Status: ${statusColor}${statusIcon} ${session.status}\x1B[0m`);
85
+ lines.push(` Devices: ${session.connectedDevices.length} connected`);
86
+ lines.push("");
87
+ if (session.tunnelUrl) {
88
+ lines.push(` \x1B[36mPublic URL:\x1B[0m`);
89
+ lines.push(` ${session.tunnelUrl}`);
90
+ } else {
91
+ lines.push(` \x1B[36mLocal URL:\x1B[0m`);
92
+ lines.push(` ${session.localUrl}`);
93
+ }
94
+ lines.push("");
95
+ lines.push(` \x1B[90mScan QR code or open URL on your phone\x1B[0m`);
96
+ lines.push("");
97
+ lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
98
+ lines.push(" [q] Stop [r] Refresh [c] Copy URL");
99
+ lines.push("");
100
+ return lines.join("\n");
101
+ }
102
+ function getStatusIcon(status) {
103
+ const icons = {
104
+ starting: "\u25EF",
105
+ waiting: "\u25C9",
106
+ connected: "\u25CF",
107
+ stopped: "\u25CB",
108
+ error: "\u2716"
109
+ };
110
+ return icons[status] || "?";
111
+ }
112
+ function getStatusColor(status) {
113
+ const colors = {
114
+ starting: "\x1B[33m",
115
+ // Yellow
116
+ waiting: "\x1B[33m",
117
+ // Yellow
118
+ connected: "\x1B[32m",
119
+ // Green
120
+ stopped: "\x1B[90m",
121
+ // Gray
122
+ error: "\x1B[31m"
123
+ // Red
124
+ };
125
+ return colors[status] || "";
126
+ }
127
+ function progressBar(current, total, width = 30) {
128
+ const percent = Math.round(current / total * 100);
129
+ const filled = Math.round(current / total * width);
130
+ const empty = width - filled;
131
+ return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${percent}%`;
132
+ }
133
+
134
+ // src/index.ts
135
+ async function createRemoteServer(config = {}) {
136
+ const { RemoteServer: RemoteServer2 } = await import("./server-XW6FLG7E.js");
137
+ const server = new RemoteServer2(config);
138
+ const session = await server.start();
139
+ return { server, session };
140
+ }
141
+ export {
142
+ DEFAULT_CONFIG,
143
+ MessageTypes,
144
+ RemoteServer,
145
+ TerminalManager,
146
+ TunnelManager,
147
+ checkTunnelAvailability,
148
+ createRemoteServer,
149
+ findAvailableTunnel,
150
+ generateQR,
151
+ generateQRDataURL,
152
+ getWebClient,
153
+ progressBar,
154
+ renderSessionCard
155
+ };