forkoff 1.0.17 → 1.0.18
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/approval.d.ts +1 -0
- package/dist/approval.js +9 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +62 -16
- package/dist/crypto/e2eeManager.d.ts +49 -52
- package/dist/crypto/e2eeManager.js +256 -181
- package/dist/crypto/encryption.d.ts +8 -10
- package/dist/crypto/encryption.js +29 -94
- package/dist/crypto/index.d.ts +10 -0
- package/dist/crypto/index.js +22 -0
- package/dist/crypto/keyExchange.d.ts +6 -20
- package/dist/crypto/keyExchange.js +18 -110
- package/dist/crypto/keyGeneration.d.ts +2 -13
- package/dist/crypto/keyGeneration.js +14 -88
- package/dist/crypto/keyStorage.d.ts +32 -5
- package/dist/crypto/keyStorage.js +152 -8
- package/dist/crypto/sessionPersistence.d.ts +7 -13
- package/dist/crypto/sessionPersistence.js +108 -33
- package/dist/crypto/types.d.ts +24 -3
- package/dist/crypto/types.js +2 -1
- package/dist/crypto/websocketE2EE.d.ts +6 -17
- package/dist/crypto/websocketE2EE.js +21 -38
- package/dist/index.js +203 -280
- package/dist/integration.d.ts +0 -1
- package/dist/integration.js +2 -4
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +209 -1
- package/dist/server.d.ts +30 -0
- package/dist/server.js +162 -0
- package/dist/startup.js +15 -6
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +94 -1
- package/dist/tools/claude-process.d.ts +8 -0
- package/dist/tools/claude-process.js +199 -26
- package/dist/tools/claude-sessions.d.ts +1 -0
- package/dist/tools/claude-sessions.js +36 -10
- package/dist/tools/detector.js +11 -3
- package/dist/tools/permission-hook.js +94 -27
- package/dist/tools/permission-ipc.d.ts +1 -0
- package/dist/tools/permission-ipc.js +61 -14
- package/dist/transcript-streamer.d.ts +1 -0
- package/dist/transcript-streamer.js +18 -4
- package/dist/usage-tracker.d.ts +45 -0
- package/dist/usage-tracker.js +243 -0
- package/dist/websocket.d.ts +43 -12
- package/dist/websocket.js +418 -214
- package/package.json +4 -3
- package/dist/__tests__/cli-commands.test.d.ts +0 -6
- package/dist/__tests__/cli-commands.test.d.ts.map +0 -1
- package/dist/__tests__/cli-commands.test.js +0 -213
- package/dist/__tests__/cli-commands.test.js.map +0 -1
- package/dist/__tests__/crypto/e2e-integration.test.d.ts +0 -17
- package/dist/__tests__/crypto/e2e-integration.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/e2e-integration.test.js +0 -338
- package/dist/__tests__/crypto/e2e-integration.test.js.map +0 -1
- package/dist/__tests__/crypto/e2eeManager.test.d.ts +0 -2
- package/dist/__tests__/crypto/e2eeManager.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/e2eeManager.test.js +0 -242
- package/dist/__tests__/crypto/e2eeManager.test.js.map +0 -1
- package/dist/__tests__/crypto/encryption.test.d.ts +0 -2
- package/dist/__tests__/crypto/encryption.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/encryption.test.js +0 -116
- package/dist/__tests__/crypto/encryption.test.js.map +0 -1
- package/dist/__tests__/crypto/keyExchange.test.d.ts +0 -2
- package/dist/__tests__/crypto/keyExchange.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/keyExchange.test.js +0 -84
- package/dist/__tests__/crypto/keyExchange.test.js.map +0 -1
- package/dist/__tests__/crypto/keyGeneration.test.d.ts +0 -2
- package/dist/__tests__/crypto/keyGeneration.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/keyGeneration.test.js +0 -61
- package/dist/__tests__/crypto/keyGeneration.test.js.map +0 -1
- package/dist/__tests__/crypto/keyStorage.test.d.ts +0 -2
- package/dist/__tests__/crypto/keyStorage.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/keyStorage.test.js +0 -133
- package/dist/__tests__/crypto/keyStorage.test.js.map +0 -1
- package/dist/__tests__/crypto/websocketIntegration.test.d.ts +0 -2
- package/dist/__tests__/crypto/websocketIntegration.test.d.ts.map +0 -1
- package/dist/__tests__/crypto/websocketIntegration.test.js +0 -259
- package/dist/__tests__/crypto/websocketIntegration.test.js.map +0 -1
- package/dist/__tests__/startup.test.d.ts +0 -11
- package/dist/__tests__/startup.test.d.ts.map +0 -1
- package/dist/__tests__/startup.test.js +0 -241
- package/dist/__tests__/startup.test.js.map +0 -1
- package/dist/__tests__/tools/claude-process.test.d.ts +0 -8
- package/dist/__tests__/tools/claude-process.test.d.ts.map +0 -1
- package/dist/__tests__/tools/claude-process.test.js +0 -430
- package/dist/__tests__/tools/claude-process.test.js.map +0 -1
- package/dist/__tests__/tools/permission-hook.test.d.ts +0 -17
- package/dist/__tests__/tools/permission-hook.test.d.ts.map +0 -1
- package/dist/__tests__/tools/permission-hook.test.js +0 -616
- package/dist/__tests__/tools/permission-hook.test.js.map +0 -1
- package/dist/__tests__/tools/permission-ipc.test.d.ts +0 -11
- package/dist/__tests__/tools/permission-ipc.test.d.ts.map +0 -1
- package/dist/__tests__/tools/permission-ipc.test.js +0 -612
- package/dist/__tests__/tools/permission-ipc.test.js.map +0 -1
- package/dist/__tests__/websocket.test.d.ts +0 -13
- package/dist/__tests__/websocket.test.d.ts.map +0 -1
- package/dist/__tests__/websocket.test.js +0 -204
- package/dist/__tests__/websocket.test.js.map +0 -1
- package/dist/api.d.ts +0 -44
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -76
- package/dist/api.js.map +0 -1
- package/dist/approval.d.ts.map +0 -1
- package/dist/approval.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/crypto/e2eeManager.d.ts.map +0 -1
- package/dist/crypto/e2eeManager.js.map +0 -1
- package/dist/crypto/encryption.d.ts.map +0 -1
- package/dist/crypto/encryption.js.map +0 -1
- package/dist/crypto/keyExchange.d.ts.map +0 -1
- package/dist/crypto/keyExchange.js.map +0 -1
- package/dist/crypto/keyGeneration.d.ts.map +0 -1
- package/dist/crypto/keyGeneration.js.map +0 -1
- package/dist/crypto/keyStorage.d.ts.map +0 -1
- package/dist/crypto/keyStorage.js.map +0 -1
- package/dist/crypto/sessionPersistence.d.ts.map +0 -1
- package/dist/crypto/sessionPersistence.js.map +0 -1
- package/dist/crypto/types.d.ts.map +0 -1
- package/dist/crypto/types.js.map +0 -1
- package/dist/crypto/websocketE2EE.d.ts.map +0 -1
- package/dist/crypto/websocketE2EE.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integration.d.ts.map +0 -1
- package/dist/integration.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/startup.d.ts.map +0 -1
- package/dist/startup.js.map +0 -1
- package/dist/terminal.d.ts.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/tools/__tests__/claude-sessions.test.d.ts +0 -2
- package/dist/tools/__tests__/claude-sessions.test.d.ts.map +0 -1
- package/dist/tools/__tests__/claude-sessions.test.js +0 -306
- package/dist/tools/__tests__/claude-sessions.test.js.map +0 -1
- package/dist/tools/claude-hooks.d.ts.map +0 -1
- package/dist/tools/claude-hooks.js.map +0 -1
- package/dist/tools/claude-process.d.ts.map +0 -1
- package/dist/tools/claude-process.js.map +0 -1
- package/dist/tools/claude-sessions.d.ts.map +0 -1
- package/dist/tools/claude-sessions.js.map +0 -1
- package/dist/tools/detector.d.ts.map +0 -1
- package/dist/tools/detector.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/permission-hook.d.ts.map +0 -1
- package/dist/tools/permission-hook.js.map +0 -1
- package/dist/tools/permission-ipc.d.ts.map +0 -1
- package/dist/tools/permission-ipc.js.map +0 -1
- package/dist/transcript-streamer.d.ts.map +0 -1
- package/dist/transcript-streamer.js.map +0 -1
- package/dist/websocket.d.ts.map +0 -1
- package/dist/websocket.js.map +0 -1
- package/jest.config.js +0 -18
package/dist/integration.d.ts
CHANGED
|
@@ -25,6 +25,5 @@ export { wsClient } from './websocket';
|
|
|
25
25
|
export { approvalManager } from './approval';
|
|
26
26
|
export { terminalManager } from './terminal';
|
|
27
27
|
export { config } from './config';
|
|
28
|
-
export { api } from './api';
|
|
29
28
|
export { claudeSessionDetector } from './tools';
|
|
30
29
|
//# sourceMappingURL=integration.d.ts.map
|
package/dist/integration.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* It handles approval requests and terminal output streaming.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.claudeSessionDetector = exports.
|
|
9
|
+
exports.claudeSessionDetector = exports.config = exports.terminalManager = exports.approvalManager = exports.wsClient = void 0;
|
|
10
10
|
exports.createIntegration = createIntegration;
|
|
11
11
|
const websocket_1 = require("./websocket");
|
|
12
12
|
const approval_1 = require("./approval");
|
|
@@ -30,7 +30,7 @@ function createIntegration() {
|
|
|
30
30
|
if (!config_1.config.isPaired) {
|
|
31
31
|
throw new Error('Device not paired. Run "forkoff pair" and scan the QR code.');
|
|
32
32
|
}
|
|
33
|
-
await websocket_1.wsClient.
|
|
33
|
+
await websocket_1.wsClient.startServer(config_1.config.relayPort);
|
|
34
34
|
},
|
|
35
35
|
disconnect: () => {
|
|
36
36
|
websocket_1.wsClient.disconnect();
|
|
@@ -77,8 +77,6 @@ var terminal_1 = require("./terminal");
|
|
|
77
77
|
Object.defineProperty(exports, "terminalManager", { enumerable: true, get: function () { return terminal_1.terminalManager; } });
|
|
78
78
|
var config_2 = require("./config");
|
|
79
79
|
Object.defineProperty(exports, "config", { enumerable: true, get: function () { return config_2.config; } });
|
|
80
|
-
var api_1 = require("./api");
|
|
81
|
-
Object.defineProperty(exports, "api", { enumerable: true, get: function () { return api_1.api; } });
|
|
82
80
|
var tools_2 = require("./tools");
|
|
83
81
|
Object.defineProperty(exports, "claudeSessionDetector", { enumerable: true, get: function () { return tools_2.claudeSessionDetector; } });
|
|
84
82
|
//# sourceMappingURL=integration.js.map
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
export declare function setQuiet(value: boolean): void;
|
|
3
|
+
/**
|
|
4
|
+
* Enable debug mode: tee all console output to a timestamped log file.
|
|
5
|
+
* Also enables process.env.DEBUG for verbose logging throughout the codebase.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setDebug(value: boolean): void;
|
|
3
8
|
export declare function isQuiet(): boolean;
|
|
9
|
+
export declare function isDebug(): boolean;
|
|
10
|
+
export declare function getLogFilePath(): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Flush and close the debug log stream. Call on process exit.
|
|
13
|
+
*/
|
|
14
|
+
export declare function closeDebugLog(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Clean up old debug log files, keeping only the most recent N.
|
|
17
|
+
*/
|
|
18
|
+
export declare function cleanupOldLogs(keepCount?: number): void;
|
|
4
19
|
export declare function createSpinner(text: string): ora.Ora;
|
|
5
20
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.setQuiet = setQuiet;
|
|
40
|
+
exports.setDebug = setDebug;
|
|
7
41
|
exports.isQuiet = isQuiet;
|
|
42
|
+
exports.isDebug = isDebug;
|
|
43
|
+
exports.getLogFilePath = getLogFilePath;
|
|
44
|
+
exports.closeDebugLog = closeDebugLog;
|
|
45
|
+
exports.cleanupOldLogs = cleanupOldLogs;
|
|
8
46
|
exports.createSpinner = createSpinner;
|
|
9
47
|
const ora_1 = __importDefault(require("ora"));
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
10
51
|
let quiet = false;
|
|
52
|
+
let debugMode = false;
|
|
53
|
+
let logFilePath = null;
|
|
54
|
+
let logStream = null;
|
|
11
55
|
const originalConsole = {
|
|
12
56
|
log: console.log,
|
|
13
57
|
error: console.error,
|
|
@@ -15,9 +59,41 @@ const originalConsole = {
|
|
|
15
59
|
info: console.info,
|
|
16
60
|
debug: console.debug,
|
|
17
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Format a log line with ISO timestamp and level prefix.
|
|
64
|
+
*/
|
|
65
|
+
function formatLogLine(level, args) {
|
|
66
|
+
const timestamp = new Date().toISOString();
|
|
67
|
+
const message = args.map(a => {
|
|
68
|
+
if (typeof a === 'string')
|
|
69
|
+
return a;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(a);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return String(a);
|
|
75
|
+
}
|
|
76
|
+
}).join(' ');
|
|
77
|
+
// Strip ANSI color codes for clean log files
|
|
78
|
+
const clean = message.replace(/\x1B\[[0-9;]*m/g, '');
|
|
79
|
+
return `[${timestamp}] [${level}] ${clean}\n`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Write a line to the debug log file (if open).
|
|
83
|
+
*/
|
|
84
|
+
function writeToLogFile(level, args) {
|
|
85
|
+
if (!logStream)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
logStream.write(formatLogLine(level, args));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Swallow write errors — don't crash the CLI for logging
|
|
92
|
+
}
|
|
93
|
+
}
|
|
18
94
|
function setQuiet(value) {
|
|
19
95
|
quiet = value;
|
|
20
|
-
if (value) {
|
|
96
|
+
if (value && !debugMode) {
|
|
21
97
|
const noop = () => { };
|
|
22
98
|
console.log = noop;
|
|
23
99
|
console.error = noop;
|
|
@@ -25,6 +101,14 @@ function setQuiet(value) {
|
|
|
25
101
|
console.info = noop;
|
|
26
102
|
console.debug = noop;
|
|
27
103
|
}
|
|
104
|
+
else if (value && debugMode) {
|
|
105
|
+
// Quiet + debug: suppress terminal output but still write to log file
|
|
106
|
+
console.log = (...args) => writeToLogFile('LOG', args);
|
|
107
|
+
console.error = (...args) => writeToLogFile('ERROR', args);
|
|
108
|
+
console.warn = (...args) => writeToLogFile('WARN', args);
|
|
109
|
+
console.info = (...args) => writeToLogFile('INFO', args);
|
|
110
|
+
console.debug = (...args) => writeToLogFile('DEBUG', args);
|
|
111
|
+
}
|
|
28
112
|
else {
|
|
29
113
|
console.log = originalConsole.log;
|
|
30
114
|
console.error = originalConsole.error;
|
|
@@ -33,9 +117,133 @@ function setQuiet(value) {
|
|
|
33
117
|
console.debug = originalConsole.debug;
|
|
34
118
|
}
|
|
35
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Enable debug mode: tee all console output to a timestamped log file.
|
|
122
|
+
* Also enables process.env.DEBUG for verbose logging throughout the codebase.
|
|
123
|
+
*/
|
|
124
|
+
function setDebug(value) {
|
|
125
|
+
debugMode = value;
|
|
126
|
+
if (!value) {
|
|
127
|
+
// Disable debug mode
|
|
128
|
+
if (logStream) {
|
|
129
|
+
logStream.end();
|
|
130
|
+
logStream = null;
|
|
131
|
+
}
|
|
132
|
+
logFilePath = null;
|
|
133
|
+
process.env.DEBUG = '';
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Enable DEBUG env var so existing DEBUG-gated logs activate
|
|
137
|
+
process.env.DEBUG = '1';
|
|
138
|
+
// Create log directory
|
|
139
|
+
const logDir = path.join(os.homedir(), '.forkoff-cli', 'logs');
|
|
140
|
+
try {
|
|
141
|
+
fs.mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
originalConsole.error('[Debug] Failed to create log directory:', logDir);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Create timestamped log file
|
|
148
|
+
const now = new Date();
|
|
149
|
+
const stamp = now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
|
|
150
|
+
logFilePath = path.join(logDir, `debug-${stamp}.log`);
|
|
151
|
+
try {
|
|
152
|
+
// SECURITY: Check for pre-existing symlink at the log path
|
|
153
|
+
if (fs.existsSync(logFilePath)) {
|
|
154
|
+
const stat = fs.lstatSync(logFilePath);
|
|
155
|
+
if (stat.isSymbolicLink()) {
|
|
156
|
+
originalConsole.error('[Debug] Symlink detected at log path, refusing to write');
|
|
157
|
+
logFilePath = null;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
logStream = fs.createWriteStream(logFilePath, { flags: 'w', mode: 0o600 });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
originalConsole.error('[Debug] Failed to create log file');
|
|
165
|
+
logFilePath = null;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Write system info header
|
|
169
|
+
const header = [
|
|
170
|
+
`=== ForkOff CLI Debug Log ===`,
|
|
171
|
+
`Date: ${now.toISOString()}`,
|
|
172
|
+
`Platform: ${os.platform()} ${os.release()} (${os.arch()})`,
|
|
173
|
+
`Node: ${process.version}`,
|
|
174
|
+
`PID: ${process.pid}`,
|
|
175
|
+
`User: ${os.userInfo().username}`,
|
|
176
|
+
`Home: ${os.homedir()}`,
|
|
177
|
+
`===`,
|
|
178
|
+
'',
|
|
179
|
+
].join('\n');
|
|
180
|
+
logStream.write(header + '\n');
|
|
181
|
+
// Wrap console methods to tee to both terminal and log file
|
|
182
|
+
console.log = (...args) => {
|
|
183
|
+
originalConsole.log(...args);
|
|
184
|
+
writeToLogFile('LOG', args);
|
|
185
|
+
};
|
|
186
|
+
console.error = (...args) => {
|
|
187
|
+
originalConsole.error(...args);
|
|
188
|
+
writeToLogFile('ERROR', args);
|
|
189
|
+
};
|
|
190
|
+
console.warn = (...args) => {
|
|
191
|
+
originalConsole.warn(...args);
|
|
192
|
+
writeToLogFile('WARN', args);
|
|
193
|
+
};
|
|
194
|
+
console.info = (...args) => {
|
|
195
|
+
originalConsole.info(...args);
|
|
196
|
+
writeToLogFile('INFO', args);
|
|
197
|
+
};
|
|
198
|
+
console.debug = (...args) => {
|
|
199
|
+
originalConsole.debug(...args);
|
|
200
|
+
writeToLogFile('DEBUG', args);
|
|
201
|
+
};
|
|
202
|
+
originalConsole.log(`[Debug] Logging to: ${logFilePath}`);
|
|
203
|
+
}
|
|
36
204
|
function isQuiet() {
|
|
37
205
|
return quiet;
|
|
38
206
|
}
|
|
207
|
+
function isDebug() {
|
|
208
|
+
return debugMode;
|
|
209
|
+
}
|
|
210
|
+
function getLogFilePath() {
|
|
211
|
+
return logFilePath;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Flush and close the debug log stream. Call on process exit.
|
|
215
|
+
*/
|
|
216
|
+
function closeDebugLog() {
|
|
217
|
+
if (logStream) {
|
|
218
|
+
logStream.write(formatLogLine('LOG', ['=== Debug log closed ===']));
|
|
219
|
+
logStream.end();
|
|
220
|
+
logStream = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Clean up old debug log files, keeping only the most recent N.
|
|
225
|
+
*/
|
|
226
|
+
function cleanupOldLogs(keepCount = 10) {
|
|
227
|
+
const logDir = path.join(os.homedir(), '.forkoff-cli', 'logs');
|
|
228
|
+
try {
|
|
229
|
+
const files = fs.readdirSync(logDir)
|
|
230
|
+
.filter(f => f.startsWith('debug-') && f.endsWith('.log'))
|
|
231
|
+
.sort()
|
|
232
|
+
.reverse();
|
|
233
|
+
// Delete files beyond keepCount
|
|
234
|
+
for (let i = keepCount; i < files.length; i++) {
|
|
235
|
+
try {
|
|
236
|
+
fs.unlinkSync(path.join(logDir, files[i]));
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// Ignore deletion errors
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Log directory may not exist
|
|
245
|
+
}
|
|
246
|
+
}
|
|
39
247
|
function createSpinner(text) {
|
|
40
248
|
return (0, ora_1.default)({ text, isSilent: quiet });
|
|
41
249
|
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface EmbeddedServerOptions {
|
|
3
|
+
port: number;
|
|
4
|
+
deviceId: string;
|
|
5
|
+
deviceName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class EmbeddedRelayServer extends EventEmitter {
|
|
8
|
+
private httpServer;
|
|
9
|
+
private io;
|
|
10
|
+
private mobileSocket;
|
|
11
|
+
private port;
|
|
12
|
+
private deviceId;
|
|
13
|
+
private deviceName;
|
|
14
|
+
/** The pairing code the CLI generated — validated in-process */
|
|
15
|
+
private currentPairingCode;
|
|
16
|
+
constructor(options: EmbeddedServerOptions);
|
|
17
|
+
/** Set the pairing code for in-process validation */
|
|
18
|
+
setPairingCode(code: string): void;
|
|
19
|
+
/** Start the HTTP + Socket.io server */
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
/** Emit an event to the connected mobile socket */
|
|
22
|
+
emitToMobile(event: string, data: any): void;
|
|
23
|
+
/** Check if a mobile client is connected */
|
|
24
|
+
hasMobileConnection(): boolean;
|
|
25
|
+
/** Get the connected mobile device ID (for E2EE targeting) */
|
|
26
|
+
getMobileDeviceId(): string | null;
|
|
27
|
+
/** Graceful shutdown */
|
|
28
|
+
stop(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EmbeddedRelayServer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Embedded relay server — turns the CLI into a Socket.io server.
|
|
6
|
+
* Mobile connects directly to the CLI over the local network.
|
|
7
|
+
* No rooms, no multi-device routing — just one CLI serving mobile connections.
|
|
8
|
+
*/
|
|
9
|
+
const http_1 = require("http");
|
|
10
|
+
const socket_io_1 = require("socket.io");
|
|
11
|
+
const events_1 = require("events");
|
|
12
|
+
/** Events the server forwards from mobile → CLI (internal EventEmitter) */
|
|
13
|
+
const MOBILE_EVENTS = [
|
|
14
|
+
'terminal_command', 'terminal_create', 'user_message',
|
|
15
|
+
'claude_start_session', 'claude_resume_session', 'claude_sessions_request',
|
|
16
|
+
'directory_list', 'read_file', 'transcript_fetch', 'transcript_subscribe',
|
|
17
|
+
'transcript_unsubscribe', 'approval_response', 'claude_approval_response',
|
|
18
|
+
'permission_response', 'permission_rules_sync', 'claude_abort', 'tab_complete',
|
|
19
|
+
'subscribe_device', 'unsubscribe_device',
|
|
20
|
+
'encrypted_key_exchange_init', 'encrypted_key_exchange_ack', 'encrypted_message',
|
|
21
|
+
];
|
|
22
|
+
class EmbeddedRelayServer extends events_1.EventEmitter {
|
|
23
|
+
constructor(options) {
|
|
24
|
+
super();
|
|
25
|
+
this.httpServer = null;
|
|
26
|
+
this.io = null;
|
|
27
|
+
this.mobileSocket = null;
|
|
28
|
+
/** The pairing code the CLI generated — validated in-process */
|
|
29
|
+
this.currentPairingCode = null;
|
|
30
|
+
this.port = options.port;
|
|
31
|
+
this.deviceId = options.deviceId;
|
|
32
|
+
this.deviceName = options.deviceName;
|
|
33
|
+
}
|
|
34
|
+
/** Set the pairing code for in-process validation */
|
|
35
|
+
setPairingCode(code) {
|
|
36
|
+
this.currentPairingCode = code;
|
|
37
|
+
}
|
|
38
|
+
/** Start the HTTP + Socket.io server */
|
|
39
|
+
start() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
this.httpServer = (0, http_1.createServer)((req, res) => {
|
|
42
|
+
if (req.url === '/health' && req.method === 'GET') {
|
|
43
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
44
|
+
res.end(JSON.stringify({ status: 'ok', mode: 'embedded' }));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
res.writeHead(404);
|
|
48
|
+
res.end();
|
|
49
|
+
});
|
|
50
|
+
this.io = new socket_io_1.Server(this.httpServer, {
|
|
51
|
+
cors: {
|
|
52
|
+
origin: '*',
|
|
53
|
+
methods: ['GET', 'POST'],
|
|
54
|
+
},
|
|
55
|
+
transports: ['websocket', 'polling'],
|
|
56
|
+
});
|
|
57
|
+
// Auth middleware — only accept mobile clients
|
|
58
|
+
this.io.use((socket, next) => {
|
|
59
|
+
const auth = socket.handshake.auth;
|
|
60
|
+
if (auth.clientType !== 'mobile') {
|
|
61
|
+
return next(new Error('CLI only accepts mobile connections'));
|
|
62
|
+
}
|
|
63
|
+
if (!auth.mobileDeviceId || auth.mobileDeviceId.length < 8) {
|
|
64
|
+
return next(new Error('Invalid mobileDeviceId'));
|
|
65
|
+
}
|
|
66
|
+
socket.data = {
|
|
67
|
+
clientType: 'mobile',
|
|
68
|
+
deviceId: auth.mobileDeviceId,
|
|
69
|
+
deviceName: auth.deviceName || 'Mobile',
|
|
70
|
+
};
|
|
71
|
+
next();
|
|
72
|
+
});
|
|
73
|
+
this.io.on('connection', (socket) => {
|
|
74
|
+
console.log(`[Server] Mobile connected: ${socket.data.deviceId}`);
|
|
75
|
+
// Track the mobile socket (only one active connection)
|
|
76
|
+
if (this.mobileSocket) {
|
|
77
|
+
console.log(`[Server] Replacing existing mobile connection`);
|
|
78
|
+
this.mobileSocket.disconnect(true);
|
|
79
|
+
}
|
|
80
|
+
this.mobileSocket = socket;
|
|
81
|
+
this.emit('mobile_connected', { deviceId: socket.data.deviceId });
|
|
82
|
+
// Handle pairing
|
|
83
|
+
socket.on('pair_device', (data) => {
|
|
84
|
+
if (this.currentPairingCode && data.pairingCode === this.currentPairingCode) {
|
|
85
|
+
console.log(`[Server] Pairing successful`);
|
|
86
|
+
// Notify internal listeners (wsClient)
|
|
87
|
+
this.emit('pair_device', { mobileDeviceId: socket.data.deviceId });
|
|
88
|
+
// Send ack directly to mobile
|
|
89
|
+
socket.emit('pair_device_ack', {
|
|
90
|
+
deviceId: this.deviceId,
|
|
91
|
+
deviceName: this.deviceName,
|
|
92
|
+
platform: process.platform,
|
|
93
|
+
mobileDeviceId: socket.data.deviceId,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(`[Server] Pairing rejected — invalid code`);
|
|
98
|
+
socket.emit('pair_device_reject', { reason: 'Invalid pairing code' });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Forward all mobile events → internal EventEmitter
|
|
102
|
+
for (const event of MOBILE_EVENTS) {
|
|
103
|
+
socket.on(event, (data) => {
|
|
104
|
+
this.emit(event, data);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
socket.on('disconnect', (reason) => {
|
|
108
|
+
console.log(`[Server] Mobile disconnected: ${reason}`);
|
|
109
|
+
if (this.mobileSocket === socket) {
|
|
110
|
+
this.mobileSocket = null;
|
|
111
|
+
}
|
|
112
|
+
this.emit('mobile_disconnected', { deviceId: socket.data.deviceId, reason });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
this.httpServer.on('error', (err) => {
|
|
116
|
+
if (err.code === 'EADDRINUSE') {
|
|
117
|
+
reject(new Error(`Port ${this.port} is already in use. Use "forkoff config --port <port>" to change it.`));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
reject(err);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
this.httpServer.listen(this.port, '0.0.0.0', () => {
|
|
124
|
+
console.log(`[Server] Listening on 0.0.0.0:${this.port}`);
|
|
125
|
+
resolve();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Emit an event to the connected mobile socket */
|
|
130
|
+
emitToMobile(event, data) {
|
|
131
|
+
if (this.mobileSocket) {
|
|
132
|
+
this.mobileSocket.emit(event, data);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Check if a mobile client is connected */
|
|
136
|
+
hasMobileConnection() {
|
|
137
|
+
return this.mobileSocket !== null && this.mobileSocket.connected;
|
|
138
|
+
}
|
|
139
|
+
/** Get the connected mobile device ID (for E2EE targeting) */
|
|
140
|
+
getMobileDeviceId() {
|
|
141
|
+
return this.mobileSocket?.data?.deviceId ?? null;
|
|
142
|
+
}
|
|
143
|
+
/** Graceful shutdown */
|
|
144
|
+
stop() {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
if (this.io) {
|
|
147
|
+
this.io.close(() => {
|
|
148
|
+
this.httpServer?.close(() => resolve());
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (this.httpServer) {
|
|
152
|
+
this.httpServer.close(() => resolve());
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
resolve();
|
|
156
|
+
}
|
|
157
|
+
this.mobileSocket = null;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.EmbeddedRelayServer = EmbeddedRelayServer;
|
|
162
|
+
//# sourceMappingURL=server.js.map
|
package/dist/startup.js
CHANGED
|
@@ -124,7 +124,7 @@ async function disableStartup() {
|
|
|
124
124
|
async function enableStartupWindows(binaryPath) {
|
|
125
125
|
const startupDir = getStartupDir();
|
|
126
126
|
if (!fs.existsSync(startupDir)) {
|
|
127
|
-
fs.mkdirSync(startupDir, { recursive: true });
|
|
127
|
+
fs.mkdirSync(startupDir, { recursive: true, mode: 0o700 });
|
|
128
128
|
}
|
|
129
129
|
// Resolve binaryPath to the .cmd shim if it exists (npm global installs create .cmd on Windows)
|
|
130
130
|
let cmdPath = binaryPath;
|
|
@@ -134,12 +134,21 @@ async function enableStartupWindows(binaryPath) {
|
|
|
134
134
|
cmdPath = candidate;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
+
// SECURITY: Validate binary path exists
|
|
138
|
+
if (!fs.existsSync(cmdPath)) {
|
|
139
|
+
throw new Error(`Startup binary not found: ${cmdPath}`);
|
|
140
|
+
}
|
|
141
|
+
// SECURITY: Reject paths with VBScript metacharacters to prevent injection
|
|
142
|
+
if (/[^a-zA-Z0-9_\-./\\ :]/.test(cmdPath)) {
|
|
143
|
+
throw new Error(`Startup binary path contains disallowed characters: ${cmdPath}`);
|
|
144
|
+
}
|
|
137
145
|
// Write a .vbs (VBScript) wrapper that launches the command with a hidden window.
|
|
138
146
|
// A .bat would open a visible cmd.exe window on every login — bad UX.
|
|
139
147
|
// WScript.Shell.Run with windowStyle 0 = hidden, False = don't wait.
|
|
140
148
|
const vbsPath = getVbsPath();
|
|
141
|
-
const
|
|
142
|
-
|
|
149
|
+
const escapedPath = cmdPath.replace(/"/g, '""');
|
|
150
|
+
const vbsContent = `CreateObject("WScript.Shell").Run """${escapedPath}"" connect --quiet", 0, False\r\n`;
|
|
151
|
+
fs.writeFileSync(vbsPath, vbsContent, { mode: 0o600 });
|
|
143
152
|
// Use HKCU Run key — no admin required, runs on user logon
|
|
144
153
|
(0, child_process_1.execSync)(`reg add "${REG_KEY}" /v ${REG_VALUE} /t REG_SZ /d "\\"${vbsPath}\\"" /f`, { stdio: 'pipe' });
|
|
145
154
|
}
|
|
@@ -170,7 +179,7 @@ async function enableStartupMacOS(binaryPath) {
|
|
|
170
179
|
await disableStartupMacOS();
|
|
171
180
|
const configDir = path.join(os.homedir(), '.config', 'forkoff-cli');
|
|
172
181
|
if (!fs.existsSync(configDir)) {
|
|
173
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
182
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
174
183
|
}
|
|
175
184
|
// Use the current node binary explicitly as the first ProgramArgument.
|
|
176
185
|
// Users with nvm/fnm have node outside the default PATH, so launchd
|
|
@@ -218,9 +227,9 @@ async function enableStartupMacOS(binaryPath) {
|
|
|
218
227
|
const plistPath = getPlistPath();
|
|
219
228
|
const launchAgentsDir = path.dirname(plistPath);
|
|
220
229
|
if (!fs.existsSync(launchAgentsDir)) {
|
|
221
|
-
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
230
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true, mode: 0o700 });
|
|
222
231
|
}
|
|
223
|
-
fs.writeFileSync(plistPath, plist);
|
|
232
|
+
fs.writeFileSync(plistPath, plist, { mode: 0o600 });
|
|
224
233
|
try {
|
|
225
234
|
(0, child_process_1.execSync)(`launchctl load "${plistPath}"`, { stdio: 'pipe' });
|
|
226
235
|
}
|
package/dist/terminal.d.ts
CHANGED
package/dist/terminal.js
CHANGED
|
@@ -38,6 +38,90 @@ const child_process_1 = require("child_process");
|
|
|
38
38
|
const events_1 = require("events");
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
|
+
function getSafeEnv() {
|
|
42
|
+
const sensitivePatterns = [
|
|
43
|
+
/^AWS_/i,
|
|
44
|
+
/^AZURE_/i,
|
|
45
|
+
/^GCP_/i,
|
|
46
|
+
/^GOOGLE_/i,
|
|
47
|
+
/SECRET/i,
|
|
48
|
+
/PASSWORD/i,
|
|
49
|
+
/PRIVATE_KEY/i,
|
|
50
|
+
/^SUPABASE_SERVICE/i,
|
|
51
|
+
/^DATABASE_URL$/i,
|
|
52
|
+
/^ADMIN_API_KEY$/i,
|
|
53
|
+
// Prevent code injection via environment variables
|
|
54
|
+
/^NODE_OPTIONS$/i,
|
|
55
|
+
/^NODE_EXTRA_CA_CERTS$/i,
|
|
56
|
+
/^LD_PRELOAD$/i,
|
|
57
|
+
/^LD_LIBRARY_PATH$/i,
|
|
58
|
+
/^DYLD_INSERT_LIBRARIES$/i,
|
|
59
|
+
/^DYLD_LIBRARY_PATH$/i,
|
|
60
|
+
/^ELECTRON_RUN_AS_NODE$/i,
|
|
61
|
+
// Language-specific code injection vectors
|
|
62
|
+
/^PYTHONPATH$/i,
|
|
63
|
+
/^PYTHONSTARTUP$/i,
|
|
64
|
+
/^RUBYLIB$/i,
|
|
65
|
+
/^PERL5LIB$/i,
|
|
66
|
+
/^PERL5OPT$/i,
|
|
67
|
+
/^JAVA_TOOL_OPTIONS$/i,
|
|
68
|
+
/^_JAVA_OPTIONS$/i,
|
|
69
|
+
// Git/SSH injection
|
|
70
|
+
/^GIT_SSH_COMMAND$/i,
|
|
71
|
+
/^GIT_EXEC_PATH$/i,
|
|
72
|
+
// Pager/editor injection
|
|
73
|
+
/^LESSOPEN$/i,
|
|
74
|
+
/^LESSCLOSE$/i,
|
|
75
|
+
// Shell startup injection
|
|
76
|
+
/^BASH_ENV$/i,
|
|
77
|
+
/^ENV$/i,
|
|
78
|
+
/^PROMPT_COMMAND$/i,
|
|
79
|
+
/^SHELLOPTS$/i,
|
|
80
|
+
// Field separator injection
|
|
81
|
+
/^IFS$/i,
|
|
82
|
+
// Editor/browser auto-launch injection
|
|
83
|
+
/^EDITOR$/i,
|
|
84
|
+
/^VISUAL$/i,
|
|
85
|
+
/^BROWSER$/i,
|
|
86
|
+
// Proxy injection (MITM child process HTTP traffic)
|
|
87
|
+
/^HTTPS?_PROXY$/i,
|
|
88
|
+
/^ALL_PROXY$/i,
|
|
89
|
+
/^NO_PROXY$/i,
|
|
90
|
+
// TLS verification bypass
|
|
91
|
+
/^SSL_CERT_FILE$/i,
|
|
92
|
+
/^SSL_CERT_DIR$/i,
|
|
93
|
+
/^NODE_TLS_REJECT_UNAUTHORIZED$/i,
|
|
94
|
+
// npm config injection
|
|
95
|
+
/^npm_config_/i,
|
|
96
|
+
// Pager injection (git, man, etc.)
|
|
97
|
+
/^PAGER$/i,
|
|
98
|
+
// Zsh startup injection
|
|
99
|
+
/^ZDOTDIR$/i,
|
|
100
|
+
// Curl config injection
|
|
101
|
+
/^CURL_HOME$/i,
|
|
102
|
+
// Third-party API keys (defense-in-depth for child processes)
|
|
103
|
+
/^OPENAI_/i,
|
|
104
|
+
/^ANTHROPIC_/i,
|
|
105
|
+
/^GITHUB_TOKEN$/i,
|
|
106
|
+
/^GITLAB_TOKEN$/i,
|
|
107
|
+
/^NPM_TOKEN$/i,
|
|
108
|
+
/^DOCKER_PASSWORD$/i,
|
|
109
|
+
/^SLACK_TOKEN$/i,
|
|
110
|
+
/^SLACK_BOT_TOKEN$/i,
|
|
111
|
+
/^SENDGRID_/i,
|
|
112
|
+
/^TWILIO_/i,
|
|
113
|
+
/^DATADOG_/i,
|
|
114
|
+
/TOKEN$/i,
|
|
115
|
+
/API_KEY$/i,
|
|
116
|
+
];
|
|
117
|
+
const filtered = {};
|
|
118
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
119
|
+
if (!sensitivePatterns.some(pattern => pattern.test(key))) {
|
|
120
|
+
filtered[key] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return filtered;
|
|
124
|
+
}
|
|
41
125
|
class TerminalManager extends events_1.EventEmitter {
|
|
42
126
|
constructor() {
|
|
43
127
|
super();
|
|
@@ -51,6 +135,14 @@ class TerminalManager extends events_1.EventEmitter {
|
|
|
51
135
|
return process.env.SHELL || '/bin/bash';
|
|
52
136
|
}
|
|
53
137
|
createSession(terminalSessionId, cwd) {
|
|
138
|
+
// Evict oldest session if at cap (FIFO)
|
|
139
|
+
if (this.sessions.size >= TerminalManager.MAX_SESSIONS) {
|
|
140
|
+
const oldestKey = this.sessions.keys().next().value;
|
|
141
|
+
if (oldestKey) {
|
|
142
|
+
console.warn(`[Terminal] MAX_SESSIONS (${TerminalManager.MAX_SESSIONS}) reached, evicting oldest: ${oldestKey}`);
|
|
143
|
+
this.closeSession(oldestKey);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
54
146
|
// Default to home directory, not process.cwd()
|
|
55
147
|
let resolvedCwd = cwd || os.homedir();
|
|
56
148
|
// Resolve ~ to home directory
|
|
@@ -82,7 +174,7 @@ class TerminalManager extends events_1.EventEmitter {
|
|
|
82
174
|
const shellArgs = isWindows ? ['/c', command] : ['-c', command];
|
|
83
175
|
const proc = (0, child_process_1.spawn)(shell, shellArgs, {
|
|
84
176
|
cwd: session.cwd,
|
|
85
|
-
env:
|
|
177
|
+
env: getSafeEnv(),
|
|
86
178
|
shell: false,
|
|
87
179
|
});
|
|
88
180
|
let stdout = '';
|
|
@@ -166,6 +258,7 @@ class TerminalManager extends events_1.EventEmitter {
|
|
|
166
258
|
}
|
|
167
259
|
}
|
|
168
260
|
}
|
|
261
|
+
TerminalManager.MAX_SESSIONS = 50;
|
|
169
262
|
exports.terminalManager = new TerminalManager();
|
|
170
263
|
exports.default = exports.terminalManager;
|
|
171
264
|
//# sourceMappingURL=terminal.js.map
|