nterminal 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/.env.example +12 -0
  2. package/LICENSE +674 -0
  3. package/README.md +181 -0
  4. package/assets/brand/app-icon-1024.png +0 -0
  5. package/assets/brand/app-icon-384.png +0 -0
  6. package/assets/brand/apple-touch-icon-360.png +0 -0
  7. package/assets/brand/favicon-32.png +0 -0
  8. package/assets/brand/favicon-64.png +0 -0
  9. package/assets/brand/favicon-96.png +0 -0
  10. package/assets/brand/favicon.svg +4 -0
  11. package/assets/brand/nterminal-mark-64.png +0 -0
  12. package/assets/brand/nterminal-mark.svg +4 -0
  13. package/assets/brand/nterminal-wordmark-486x68.png +0 -0
  14. package/assets/brand/nterminal-wordmark.svg +3 -0
  15. package/assets/screenshot/scr.png +0 -0
  16. package/bin/nterminal.js +114 -0
  17. package/dist/client/apple-touch-icon.png +0 -0
  18. package/dist/client/assets/MarkdownPreview-BeDi-V7k.js +29 -0
  19. package/dist/client/assets/MesloLGS-NF-Bold-Italic-DwFsXcwX.ttf +0 -0
  20. package/dist/client/assets/MesloLGS-NF-Bold-kN-HYz-g.ttf +0 -0
  21. package/dist/client/assets/MesloLGS-NF-Italic-CMg1T6-G.ttf +0 -0
  22. package/dist/client/assets/MesloLGS-NF-Regular-Cxr8pvCI.ttf +0 -0
  23. package/dist/client/assets/index-BQkKYjXb.js +33 -0
  24. package/dist/client/assets/index-WqeS39wU.css +1 -0
  25. package/dist/client/assets/notifications/character-2258.mp4 +0 -0
  26. package/dist/client/assets/notifications/character-2260.mp4 +0 -0
  27. package/dist/client/assets/notifications/character-2272.mp4 +0 -0
  28. package/dist/client/brand/nterminal-mark-64.png +0 -0
  29. package/dist/client/brand/nterminal-mark.svg +4 -0
  30. package/dist/client/brand/nterminal-wordmark-486x68.png +0 -0
  31. package/dist/client/brand/nterminal-wordmark.svg +3 -0
  32. package/dist/client/icons/app-icon-1024.png +0 -0
  33. package/dist/client/icons/app-icon-384.png +0 -0
  34. package/dist/client/icons/favicon-32.png +0 -0
  35. package/dist/client/icons/favicon-64.png +0 -0
  36. package/dist/client/icons/favicon-96.png +0 -0
  37. package/dist/client/icons/favicon.svg +4 -0
  38. package/dist/client/index.html +21 -0
  39. package/dist/client/manifest.webmanifest +24 -0
  40. package/dist/scripts/generate-secrets.js +3 -0
  41. package/dist/scripts/generate-secrets.js.map +1 -0
  42. package/dist/scripts/onboarding.js +814 -0
  43. package/dist/scripts/onboarding.js.map +1 -0
  44. package/dist/scripts/proxySetup.js +1007 -0
  45. package/dist/scripts/proxySetup.js.map +1 -0
  46. package/dist/server/agent/agentAuth.d.ts +6 -0
  47. package/dist/server/agent/agentAuth.js +35 -0
  48. package/dist/server/agent/agentAuth.js.map +1 -0
  49. package/dist/server/agent/agentProxy.d.ts +5 -0
  50. package/dist/server/agent/agentProxy.js +63 -0
  51. package/dist/server/agent/agentProxy.js.map +1 -0
  52. package/dist/server/agent/agentRoutes.d.ts +9 -0
  53. package/dist/server/agent/agentRoutes.js +327 -0
  54. package/dist/server/agent/agentRoutes.js.map +1 -0
  55. package/dist/server/agent/agentWebSocketProxy.d.ts +3 -0
  56. package/dist/server/agent/agentWebSocketProxy.js +65 -0
  57. package/dist/server/agent/agentWebSocketProxy.js.map +1 -0
  58. package/dist/server/auth/authService.d.ts +100 -0
  59. package/dist/server/auth/authService.js +415 -0
  60. package/dist/server/auth/authService.js.map +1 -0
  61. package/dist/server/auth/cookies.d.ts +11 -0
  62. package/dist/server/auth/cookies.js +39 -0
  63. package/dist/server/auth/cookies.js.map +1 -0
  64. package/dist/server/auth/ipMatch.d.ts +14 -0
  65. package/dist/server/auth/ipMatch.js +103 -0
  66. package/dist/server/auth/ipMatch.js.map +1 -0
  67. package/dist/server/auth/rateLimit.d.ts +17 -0
  68. package/dist/server/auth/rateLimit.js +25 -0
  69. package/dist/server/auth/rateLimit.js.map +1 -0
  70. package/dist/server/auth/totpService.d.ts +10 -0
  71. package/dist/server/auth/totpService.js +37 -0
  72. package/dist/server/auth/totpService.js.map +1 -0
  73. package/dist/server/config.d.ts +27 -0
  74. package/dist/server/config.js +138 -0
  75. package/dist/server/config.js.map +1 -0
  76. package/dist/server/files/fileExplorerService.d.ts +38 -0
  77. package/dist/server/files/fileExplorerService.js +551 -0
  78. package/dist/server/files/fileExplorerService.js.map +1 -0
  79. package/dist/server/files/rootToken.d.ts +51 -0
  80. package/dist/server/files/rootToken.js +139 -0
  81. package/dist/server/files/rootToken.js.map +1 -0
  82. package/dist/server/http.d.ts +13 -0
  83. package/dist/server/http.js +69 -0
  84. package/dist/server/http.js.map +1 -0
  85. package/dist/server/index.d.ts +1 -0
  86. package/dist/server/index.js +45 -0
  87. package/dist/server/index.js.map +1 -0
  88. package/dist/server/routes/agentManagementRoutes.d.ts +9 -0
  89. package/dist/server/routes/agentManagementRoutes.js +304 -0
  90. package/dist/server/routes/agentManagementRoutes.js.map +1 -0
  91. package/dist/server/routes/authRoutes.d.ts +10 -0
  92. package/dist/server/routes/authRoutes.js +95 -0
  93. package/dist/server/routes/authRoutes.js.map +1 -0
  94. package/dist/server/routes/fileRoutes.d.ts +11 -0
  95. package/dist/server/routes/fileRoutes.js +185 -0
  96. package/dist/server/routes/fileRoutes.js.map +1 -0
  97. package/dist/server/routes/notificationAssetRoutes.d.ts +9 -0
  98. package/dist/server/routes/notificationAssetRoutes.js +280 -0
  99. package/dist/server/routes/notificationAssetRoutes.js.map +1 -0
  100. package/dist/server/routes/securityRoutes.d.ts +7 -0
  101. package/dist/server/routes/securityRoutes.js +53 -0
  102. package/dist/server/routes/securityRoutes.js.map +1 -0
  103. package/dist/server/routes/socketBackpressure.d.ts +26 -0
  104. package/dist/server/routes/socketBackpressure.js +63 -0
  105. package/dist/server/routes/socketBackpressure.js.map +1 -0
  106. package/dist/server/routes/terminalLayoutRoutes.d.ts +9 -0
  107. package/dist/server/routes/terminalLayoutRoutes.js +108 -0
  108. package/dist/server/routes/terminalLayoutRoutes.js.map +1 -0
  109. package/dist/server/routes/terminalRoutes.d.ts +14 -0
  110. package/dist/server/routes/terminalRoutes.js +177 -0
  111. package/dist/server/routes/terminalRoutes.js.map +1 -0
  112. package/dist/server/routes/terminalWebSocket.d.ts +9 -0
  113. package/dist/server/routes/terminalWebSocket.js +129 -0
  114. package/dist/server/routes/terminalWebSocket.js.map +1 -0
  115. package/dist/server/routes/totpRoutes.d.ts +7 -0
  116. package/dist/server/routes/totpRoutes.js +46 -0
  117. package/dist/server/routes/totpRoutes.js.map +1 -0
  118. package/dist/server/routes/updateRoutes.d.ts +7 -0
  119. package/dist/server/routes/updateRoutes.js +24 -0
  120. package/dist/server/routes/updateRoutes.js.map +1 -0
  121. package/dist/server/routes/uploadRoutes.d.ts +9 -0
  122. package/dist/server/routes/uploadRoutes.js +95 -0
  123. package/dist/server/routes/uploadRoutes.js.map +1 -0
  124. package/dist/server/storage/fileStore.d.ts +90 -0
  125. package/dist/server/storage/fileStore.js +275 -0
  126. package/dist/server/storage/fileStore.js.map +1 -0
  127. package/dist/server/system/stats.d.ts +2 -0
  128. package/dist/server/system/stats.js +37 -0
  129. package/dist/server/system/stats.js.map +1 -0
  130. package/dist/server/terminal/NodePtyAdapter.d.ts +4 -0
  131. package/dist/server/terminal/NodePtyAdapter.js +14 -0
  132. package/dist/server/terminal/NodePtyAdapter.js.map +1 -0
  133. package/dist/server/terminal/PtyAdapter.d.ts +57 -0
  134. package/dist/server/terminal/PtyAdapter.js +2 -0
  135. package/dist/server/terminal/PtyAdapter.js.map +1 -0
  136. package/dist/server/terminal/TerminalManager.d.ts +74 -0
  137. package/dist/server/terminal/TerminalManager.js +561 -0
  138. package/dist/server/terminal/TerminalManager.js.map +1 -0
  139. package/dist/server/terminal/TmuxPtyAdapter.d.ts +25 -0
  140. package/dist/server/terminal/TmuxPtyAdapter.js +543 -0
  141. package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -0
  142. package/dist/server/terminal/codexTranscriptSource.d.ts +9 -0
  143. package/dist/server/terminal/codexTranscriptSource.js +144 -0
  144. package/dist/server/terminal/codexTranscriptSource.js.map +1 -0
  145. package/dist/server/terminal/cwdResolver.d.ts +8 -0
  146. package/dist/server/terminal/cwdResolver.js +37 -0
  147. package/dist/server/terminal/cwdResolver.js.map +1 -0
  148. package/dist/server/terminal/outputBuffer.d.ts +7 -0
  149. package/dist/server/terminal/outputBuffer.js +17 -0
  150. package/dist/server/terminal/outputBuffer.js.map +1 -0
  151. package/dist/server/terminal/transcriptHistory.d.ts +7 -0
  152. package/dist/server/terminal/transcriptHistory.js +315 -0
  153. package/dist/server/terminal/transcriptHistory.js.map +1 -0
  154. package/dist/server/update/gitUpdate.d.ts +27 -0
  155. package/dist/server/update/gitUpdate.js +241 -0
  156. package/dist/server/update/gitUpdate.js.map +1 -0
  157. package/dist/server/uploads/uploadPaths.d.ts +18 -0
  158. package/dist/server/uploads/uploadPaths.js +116 -0
  159. package/dist/server/uploads/uploadPaths.js.map +1 -0
  160. package/dist/server/uploads/uploadService.d.ts +21 -0
  161. package/dist/server/uploads/uploadService.js +230 -0
  162. package/dist/server/uploads/uploadService.js.map +1 -0
  163. package/dist/shared/layoutState.d.ts +6 -0
  164. package/dist/shared/layoutState.js +115 -0
  165. package/dist/shared/layoutState.js.map +1 -0
  166. package/dist/shared/notificationAssets.d.ts +9 -0
  167. package/dist/shared/notificationAssets.js +27 -0
  168. package/dist/shared/notificationAssets.js.map +1 -0
  169. package/dist/shared/protocol.d.ts +308 -0
  170. package/dist/shared/protocol.js +29 -0
  171. package/dist/shared/protocol.js.map +1 -0
  172. package/dist/shared/types.d.ts +56 -0
  173. package/dist/shared/types.js +2 -0
  174. package/dist/shared/types.js.map +1 -0
  175. package/docs/assets/nterminal-workspace.png +0 -0
  176. package/docs/configuration.md +97 -0
  177. package/docs/features.md +126 -0
  178. package/docs/onboarding.md +122 -0
  179. package/docs/operations.md +112 -0
  180. package/docs/terminal-history.md +54 -0
  181. package/package.json +85 -0
  182. package/public/apple-touch-icon.png +0 -0
  183. package/public/assets/notifications/character-2258.mp4 +0 -0
  184. package/public/assets/notifications/character-2260.mp4 +0 -0
  185. package/public/assets/notifications/character-2272.mp4 +0 -0
  186. package/public/brand/nterminal-mark-64.png +0 -0
  187. package/public/brand/nterminal-mark.svg +4 -0
  188. package/public/brand/nterminal-wordmark-486x68.png +0 -0
  189. package/public/brand/nterminal-wordmark.svg +3 -0
  190. package/public/icons/app-icon-1024.png +0 -0
  191. package/public/icons/app-icon-384.png +0 -0
  192. package/public/icons/favicon-32.png +0 -0
  193. package/public/icons/favicon-64.png +0 -0
  194. package/public/icons/favicon-96.png +0 -0
  195. package/public/icons/favicon.svg +4 -0
  196. package/public/manifest.webmanifest +24 -0
  197. package/scripts/nterminalctl +588 -0
@@ -0,0 +1,177 @@
1
+ import { requireAllowedOrigin } from './authRoutes.js';
2
+ export async function authenticateTerminalRequest(authService, request, reply) {
3
+ const auth = authService;
4
+ const methodNames = ['requireSession', 'requireAuth', 'authenticateRequest', 'validateRequest', 'isAuthenticated', 'validateSession'];
5
+ for (const methodName of methodNames) {
6
+ const method = auth[methodName];
7
+ if (typeof method !== 'function') {
8
+ continue;
9
+ }
10
+ if (method.length > 2) {
11
+ continue;
12
+ }
13
+ const result = await method.call(authService, request, reply);
14
+ return normalizeAuthResult(result, reply);
15
+ }
16
+ if (reply && !reply.sent) {
17
+ await reply.code(401).send({ error: 'unauthorized' });
18
+ }
19
+ return null;
20
+ }
21
+ export async function registerTerminalRoutes(app, config, services) {
22
+ app.get('/api/terminals', async (request, reply) => {
23
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
24
+ if (!auth) {
25
+ return reply;
26
+ }
27
+ return { terminals: services.terminalManager.listTerminals() };
28
+ });
29
+ app.get('/api/terminals/:id/history', async (request, reply) => {
30
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
31
+ if (!auth) {
32
+ return reply;
33
+ }
34
+ const beforeLine = parseOptionalInt(request.query.beforeLine, 'beforeLine', reply);
35
+ if (beforeLine === null) {
36
+ return reply;
37
+ }
38
+ const limit = parseOptionalInt(request.query.limit, 'limit', reply);
39
+ if (limit === null) {
40
+ return reply;
41
+ }
42
+ const history = services.terminalManager.getTerminalHistory(request.params.id, {
43
+ ...(beforeLine !== undefined ? { beforeLine } : {}),
44
+ ...(limit !== undefined ? { limit } : {})
45
+ });
46
+ if (!history) {
47
+ return reply.code(404).send({ error: 'terminal_history_unavailable' });
48
+ }
49
+ return history;
50
+ });
51
+ app.get('/api/terminals/:id/transcript-history', async (request, reply) => {
52
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
53
+ if (!auth) {
54
+ return reply;
55
+ }
56
+ const limit = parseOptionalInt(request.query.limit, 'limit', reply);
57
+ if (limit === null) {
58
+ return reply;
59
+ }
60
+ const history = services.terminalManager.getTerminalTranscriptHistory(request.params.id, {
61
+ ...(typeof request.query.cursor === 'string' && request.query.cursor ? { cursor: request.query.cursor } : {}),
62
+ ...(limit !== undefined ? { limit } : {})
63
+ });
64
+ if (!history) {
65
+ if (!services.terminalManager.getTerminal(request.params.id)) {
66
+ return reply.code(404).send({ error: 'terminal_not_found' });
67
+ }
68
+ return {
69
+ terminalId: request.params.id,
70
+ source: null,
71
+ cursor: null,
72
+ hasMore: false,
73
+ output: []
74
+ };
75
+ }
76
+ return history;
77
+ });
78
+ app.get('/api/terminals/:id/transcript-source', async (request, reply) => {
79
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
80
+ if (!auth) {
81
+ return reply;
82
+ }
83
+ const source = services.terminalManager.getTerminalTranscriptSource(request.params.id);
84
+ if (!source) {
85
+ if (!services.terminalManager.getTerminal(request.params.id)) {
86
+ return reply.code(404).send({ error: 'terminal_not_found' });
87
+ }
88
+ return { terminalId: request.params.id, source: null };
89
+ }
90
+ return source;
91
+ });
92
+ app.post('/api/terminals', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
93
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
94
+ if (!auth) {
95
+ return reply;
96
+ }
97
+ const body = (request.body ?? {});
98
+ const createOptions = {
99
+ ...(body.parentTerminalId !== undefined ? { parentTerminalId: body.parentTerminalId } : {}),
100
+ ...(body.cols !== undefined ? { cols: body.cols } : {}),
101
+ ...(body.rows !== undefined ? { rows: body.rows } : {})
102
+ };
103
+ const terminal = await services.terminalManager.createTerminal(createOptions);
104
+ return reply.code(201).send({ terminal });
105
+ });
106
+ app.delete('/api/terminals/:id', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
107
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
108
+ if (!auth) {
109
+ return reply;
110
+ }
111
+ if (!services.terminalManager.closeTerminal(request.params.id)) {
112
+ return reply.code(404).send({ error: 'terminal_not_found' });
113
+ }
114
+ return reply.code(204).send();
115
+ });
116
+ // Update an existing terminal's title. We use PATCH so the request body
117
+ // only carries the fields the client wants to change — currently just
118
+ // `title`, but the shape lets us add more fields (cwd hint, color, etc.)
119
+ // later without a new endpoint.
120
+ app.patch('/api/terminals/:id', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
121
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
122
+ if (!auth) {
123
+ return reply;
124
+ }
125
+ const titleField = (request.body ?? {}).title;
126
+ if (typeof titleField !== 'string' || !titleField.trim()) {
127
+ return reply.code(400).send({ error: 'title_required' });
128
+ }
129
+ const summary = services.terminalManager.renameTerminal(request.params.id, titleField);
130
+ if (!summary) {
131
+ return reply.code(404).send({ error: 'terminal_not_found' });
132
+ }
133
+ return { terminal: summary };
134
+ });
135
+ app.delete('/api/terminals', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
136
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
137
+ if (!auth) {
138
+ return reply;
139
+ }
140
+ services.terminalManager.closeAll();
141
+ return reply.code(204).send();
142
+ });
143
+ }
144
+ function normalizeAuthResult(result, reply) {
145
+ if (result === true) {
146
+ return {};
147
+ }
148
+ if (typeof result === 'string') {
149
+ return { sessionId: result };
150
+ }
151
+ if (result && typeof result === 'object') {
152
+ if (result.authenticated === false) {
153
+ sendUnauthorized(reply);
154
+ return null;
155
+ }
156
+ return typeof result.sessionId === 'string' ? { sessionId: result.sessionId } : {};
157
+ }
158
+ sendUnauthorized(reply);
159
+ return null;
160
+ }
161
+ function sendUnauthorized(reply) {
162
+ if (reply && !reply.sent) {
163
+ void reply.code(401).send({ error: 'unauthorized' });
164
+ }
165
+ }
166
+ function parseOptionalInt(value, name, reply) {
167
+ if (value === undefined || value === '') {
168
+ return undefined;
169
+ }
170
+ const parsed = Number(value);
171
+ if (!Number.isInteger(parsed)) {
172
+ reply.code(400).send({ error: `invalid_${name}` });
173
+ return null;
174
+ }
175
+ return parsed;
176
+ }
177
+ //# sourceMappingURL=terminalRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalRoutes.js","sourceRoot":"","sources":["../../../src/server/routes/terminalRoutes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type AppConfig } from '../config.js';
3
+ import type { AuthService } from '../auth/authService.js';
4
+ import type { TerminalManager } from '../terminal/TerminalManager.js';
5
+ export interface TerminalWebSocketServices {
6
+ authService: AuthService;
7
+ terminalManager: TerminalManager;
8
+ }
9
+ export declare function registerTerminalWebSocket(app: FastifyInstance, config: AppConfig, services: TerminalWebSocketServices): Promise<void>;
@@ -0,0 +1,129 @@
1
+ import WebSocket from 'ws';
2
+ import { isAllowedOrigin } from '../config.js';
3
+ import { parseClientTerminalMessage } from '../../shared/protocol.js';
4
+ import { authenticateTerminalRequest } from './terminalRoutes.js';
5
+ import { SocketBackpressure } from './socketBackpressure.js';
6
+ // Bytes pending in this socket's outbound buffer before we ask the PTY to
7
+ // pause. 1 MB is enough to absorb normal interactive spikes but small enough
8
+ // that a stalled client can't grow it unbounded.
9
+ const PAUSE_HIGH_WATER_BYTES = 1 << 20;
10
+ // Resume once the buffer drains back below this — gives the socket headroom
11
+ // before the next pause/resume cycle.
12
+ const PAUSE_LOW_WATER_BYTES = 64 << 10;
13
+ // How often to poll bufferedAmount while paused. ws doesn't emit a drain
14
+ // event we can hook here, so we sample on a coarse interval.
15
+ const DRAIN_POLL_MS = 50;
16
+ export async function registerTerminalWebSocket(app, config, services) {
17
+ app.get('/api/terminals/:id/ws', { websocket: true }, async (socket, request) => {
18
+ if (!request.headers.origin) {
19
+ socket.close(1008, 'origin required');
20
+ return;
21
+ }
22
+ if (!isAllowedOrigin(config, request.headers.origin)) {
23
+ socket.close(1008, 'origin not allowed');
24
+ return;
25
+ }
26
+ const auth = await authenticateTerminalRequest(services.authService, request);
27
+ if (!auth) {
28
+ socket.close(1008, 'unauthorized');
29
+ return;
30
+ }
31
+ const terminalId = request.params.id;
32
+ let snapshotSent = false;
33
+ const pendingMessages = [];
34
+ // Forward-declared so the subscriber closure (registered below) and the
35
+ // backpressure sink can both reference it. Assigned exactly once before
36
+ // any send occurs, so the optional chains in the sink stay null-safe
37
+ // without ever actually hitting null in practice.
38
+ let attachment = null;
39
+ const backpressure = new SocketBackpressure({
40
+ get bufferedAmount() {
41
+ return socket.bufferedAmount;
42
+ },
43
+ isOpen: () => socket.readyState === WebSocket.OPEN
44
+ }, {
45
+ pause: () => attachment?.pause(),
46
+ resume: () => attachment?.resume()
47
+ }, {
48
+ highWaterBytes: PAUSE_HIGH_WATER_BYTES,
49
+ lowWaterBytes: PAUSE_LOW_WATER_BYTES,
50
+ pollMs: DRAIN_POLL_MS
51
+ });
52
+ const sendWithBackpressure = (message) => {
53
+ sendJson(socket, message);
54
+ backpressure.observe();
55
+ };
56
+ attachment = services.terminalManager.attachTerminal(terminalId, (message) => {
57
+ if (!snapshotSent) {
58
+ pendingMessages.push(message);
59
+ return;
60
+ }
61
+ sendWithBackpressure(message);
62
+ }, { replay: false });
63
+ if (!attachment) {
64
+ backpressure.dispose();
65
+ socket.close(1008, 'terminal not found');
66
+ return;
67
+ }
68
+ sendWithBackpressure({ type: 'snapshot', terminal: attachment.terminal, output: attachment.output, history: attachment.history });
69
+ snapshotSent = true;
70
+ for (const message of pendingMessages) {
71
+ sendWithBackpressure(message);
72
+ }
73
+ pendingMessages.length = 0;
74
+ let isAlive = true;
75
+ const heartbeat = setInterval(() => {
76
+ if (socket.readyState !== WebSocket.OPEN) {
77
+ return;
78
+ }
79
+ if (!isAlive) {
80
+ socket.terminate();
81
+ return;
82
+ }
83
+ isAlive = false;
84
+ socket.ping();
85
+ }, 30_000);
86
+ socket.on('pong', () => {
87
+ isAlive = true;
88
+ });
89
+ socket.on('message', (raw) => {
90
+ const message = parseClientTerminalMessage(raw.toString());
91
+ if (!message) {
92
+ sendJson(socket, { type: 'error', message: 'invalid terminal message' });
93
+ return;
94
+ }
95
+ if (message.type === 'ping') {
96
+ sendJson(socket, { type: 'pong', nonce: message.nonce });
97
+ return;
98
+ }
99
+ if (message.terminalId !== terminalId) {
100
+ sendJson(socket, { type: 'error', message: 'terminal id mismatch' });
101
+ return;
102
+ }
103
+ if (message.type === 'refresh_cwd') {
104
+ void services.terminalManager.refreshTerminalCwd(message.terminalId);
105
+ return;
106
+ }
107
+ if (message.type === 'input') {
108
+ if (!services.terminalManager.writeToTerminal(message.terminalId, message.data)) {
109
+ sendJson(socket, { type: 'error', message: 'terminal is not writable' });
110
+ }
111
+ return;
112
+ }
113
+ if (!services.terminalManager.resizeTerminal(message.terminalId, message.cols, message.rows)) {
114
+ sendJson(socket, { type: 'error', message: 'terminal is not resizable' });
115
+ }
116
+ });
117
+ socket.on('close', () => {
118
+ clearInterval(heartbeat);
119
+ backpressure.dispose();
120
+ attachment?.dispose();
121
+ });
122
+ });
123
+ }
124
+ function sendJson(socket, payload) {
125
+ if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) {
126
+ socket.send(JSON.stringify(payload));
127
+ }
128
+ }
129
+ //# sourceMappingURL=terminalWebSocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalWebSocket.js","sourceRoot":"","sources":["../../../src/server/routes/terminalWebSocket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { AppConfig } from '../config.js';
3
+ import type { AuthService } from '../auth/authService.js';
4
+ export interface TotpRouteServices {
5
+ authService: AuthService;
6
+ }
7
+ export declare function registerTotpRoutes(app: FastifyInstance, config: AppConfig, services: TotpRouteServices): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import { cookieNames } from '../auth/cookies.js';
2
+ import { requireAllowedOrigin } from './authRoutes.js';
3
+ export async function registerTotpRoutes(app, config, services) {
4
+ app.post('/api/auth/totp/setup', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
5
+ const cookies = request.cookies;
6
+ try {
7
+ const result = await services.authService.setupTotp(cookies[cookieNames.session], request.ip);
8
+ return result;
9
+ }
10
+ catch (error) {
11
+ return sendAuthError(reply, error);
12
+ }
13
+ });
14
+ app.post('/api/auth/totp/activate', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
15
+ const cookies = request.cookies;
16
+ const { secret, otp } = request.body ?? {};
17
+ if (typeof secret !== 'string' || typeof otp !== 'string') {
18
+ return reply.code(400).send({ error: 'invalid_request' });
19
+ }
20
+ try {
21
+ await services.authService.activateTotp(cookies[cookieNames.session], secret, otp, request.ip);
22
+ return { totpEnabled: true };
23
+ }
24
+ catch (error) {
25
+ return sendAuthError(reply, error);
26
+ }
27
+ });
28
+ app.delete('/api/auth/totp', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
29
+ const cookies = request.cookies;
30
+ try {
31
+ await services.authService.disableTotp(cookies[cookieNames.session], request.ip);
32
+ return { totpEnabled: false };
33
+ }
34
+ catch (error) {
35
+ return sendAuthError(reply, error);
36
+ }
37
+ });
38
+ }
39
+ function sendAuthError(reply, error) {
40
+ if (error instanceof Error && 'statusCode' in error && 'code' in error) {
41
+ const authError = error;
42
+ return reply.code(authError.statusCode).send({ error: authError.code.toLowerCase() });
43
+ }
44
+ throw error;
45
+ }
46
+ //# sourceMappingURL=totpRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totpRoutes.js","sourceRoot":"","sources":["../../../src/server/routes/totpRoutes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { AppConfig } from '../config.js';
3
+ import type { AuthService } from '../auth/authService.js';
4
+ export interface UpdateRouteServices {
5
+ authService: AuthService;
6
+ }
7
+ export declare function registerUpdateRoutes(app: FastifyInstance, config: AppConfig, services: UpdateRouteServices): Promise<void>;
@@ -0,0 +1,24 @@
1
+ import { requireAllowedOrigin } from './authRoutes.js';
2
+ import { authenticateTerminalRequest } from './terminalRoutes.js';
3
+ import { checkBehind, getVersionInfo, runUpdate } from '../update/gitUpdate.js';
4
+ export async function registerUpdateRoutes(app, config, services) {
5
+ app.get('/api/version', async (request, reply) => {
6
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
7
+ if (!auth)
8
+ return reply;
9
+ return getVersionInfo();
10
+ });
11
+ app.post('/api/update/check', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
12
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
13
+ if (!auth)
14
+ return reply;
15
+ return checkBehind();
16
+ });
17
+ app.post('/api/update', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
18
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
19
+ if (!auth)
20
+ return reply;
21
+ return runUpdate();
22
+ });
23
+ }
24
+ //# sourceMappingURL=updateRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateRoutes.js","sourceRoot":"","sources":["../../../src/server/routes/updateRoutes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAMhF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAoB,EAAE,MAAiB,EAAE,QAA6B;IAC/G,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,2BAA2B,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,cAAc,EAAE,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvI,MAAM,IAAI,GAAG,MAAM,2BAA2B,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACjI,MAAM,IAAI,GAAG,MAAM,2BAA2B,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,SAAS,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { AppConfig } from '../config.js';
3
+ import type { AuthService } from '../auth/authService.js';
4
+ import type { TerminalManager } from '../terminal/TerminalManager.js';
5
+ export interface UploadRouteServices {
6
+ authService: AuthService;
7
+ terminalManager: TerminalManager;
8
+ }
9
+ export declare function registerUploadRoutes(app: FastifyInstance, config: AppConfig, services: UploadRouteServices): Promise<void>;
@@ -0,0 +1,95 @@
1
+ import { requireAllowedOrigin } from './authRoutes.js';
2
+ import { authenticateTerminalRequest } from './terminalRoutes.js';
3
+ import { createUploadSession, parseUploadManifest, UploadRequestError } from '../uploads/uploadService.js';
4
+ export async function registerUploadRoutes(app, config, services) {
5
+ app.post('/api/uploads', { preHandler: (request, reply) => requireAllowedOrigin(config, request, reply) }, async (request, reply) => {
6
+ const auth = await authenticateTerminalRequest(services.authService, request, reply);
7
+ if (!auth) {
8
+ return reply;
9
+ }
10
+ if (!request.isMultipart()) {
11
+ return reply.code(400).send({ error: 'multipart_required' });
12
+ }
13
+ let session = null;
14
+ let responseError = null;
15
+ try {
16
+ for await (const part of request.parts({
17
+ limits: {
18
+ files: config.uploadMaxFiles,
19
+ fileSize: config.uploadMaxFileBytes + 1,
20
+ parts: config.uploadMaxFiles + 1,
21
+ fields: 1
22
+ }
23
+ })) {
24
+ if (responseError) {
25
+ if (part.type === 'file') {
26
+ await drain(part.file);
27
+ }
28
+ continue;
29
+ }
30
+ if (part.type === 'field') {
31
+ if (part.fieldname === 'manifest' && !session) {
32
+ session = await createSessionFromManifest(part.value, config, services);
33
+ }
34
+ continue;
35
+ }
36
+ if (!session) {
37
+ responseError = { statusCode: 400, code: 'manifest_required' };
38
+ await drain(part.file);
39
+ continue;
40
+ }
41
+ await session.writeFile(part.fieldname, part.file);
42
+ }
43
+ }
44
+ catch (error) {
45
+ await session?.abort();
46
+ if (error instanceof UploadRequestError) {
47
+ return reply.code(error.statusCode).send({ error: error.code });
48
+ }
49
+ throw error;
50
+ }
51
+ if (responseError) {
52
+ await session?.abort();
53
+ return reply.code(responseError.statusCode).send({ error: responseError.code });
54
+ }
55
+ if (!session) {
56
+ return reply.code(400).send({ error: 'manifest_required' });
57
+ }
58
+ return session.finish();
59
+ });
60
+ }
61
+ async function createSessionFromManifest(value, config, services) {
62
+ const manifest = parseUploadManifest(parseManifestValue(value));
63
+ if (!manifest) {
64
+ throw new UploadRequestError(400, 'invalid_upload_manifest');
65
+ }
66
+ if (!services.terminalManager.getTerminal(manifest.terminalId)) {
67
+ throw new UploadRequestError(404, 'terminal_not_found');
68
+ }
69
+ const destinationCwd = await services.terminalManager.resolveTerminalCwd(manifest.terminalId);
70
+ if (!destinationCwd) {
71
+ throw new UploadRequestError(409, 'terminal_cwd_unavailable');
72
+ }
73
+ return createUploadSession({
74
+ destinationCwd,
75
+ limits: config,
76
+ manifest
77
+ });
78
+ }
79
+ function parseManifestValue(value) {
80
+ if (typeof value !== 'string') {
81
+ return value;
82
+ }
83
+ try {
84
+ return JSON.parse(value);
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ async function drain(stream) {
91
+ for await (const _chunk of stream) {
92
+ // Consume the stream so multipart parsing can continue before returning.
93
+ }
94
+ }
95
+ //# sourceMappingURL=uploadRoutes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadRoutes.js","sourceRoot":"","sources":["../../../src/server/routes/uploadRoutes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import type { TerminalLayoutState } from '../../shared/types.js';
2
+ export interface StoredPasswordCredential {
3
+ algorithm: 'scrypt';
4
+ hash: string;
5
+ salt: string;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ }
9
+ export interface StoredTotpCredential {
10
+ secret: string;
11
+ algorithm: 'SHA1';
12
+ digits: 6;
13
+ period: 30;
14
+ createdAt: string;
15
+ }
16
+ export interface StoredAgent {
17
+ id: string;
18
+ name: string;
19
+ url: string;
20
+ token: string;
21
+ createdAt: string;
22
+ }
23
+ export interface StoredTrustedDevice {
24
+ id: string;
25
+ tokenHash: string;
26
+ label: string;
27
+ createdAt: string;
28
+ lastSeenAt: string;
29
+ expiresAt: string;
30
+ }
31
+ export interface StoredIpRule {
32
+ id: string;
33
+ value: string;
34
+ label: string;
35
+ createdAt: string;
36
+ }
37
+ export interface StoredTerminalLayout {
38
+ layout: TerminalLayoutState;
39
+ revision: number;
40
+ updatedAt: string;
41
+ }
42
+ export interface StoredNotificationAsset {
43
+ id: string;
44
+ filename: string;
45
+ storedFilename: string;
46
+ contentType: 'video/mp4' | 'video/webm';
47
+ size: number;
48
+ createdAt: string;
49
+ }
50
+ export interface StoredState {
51
+ passwordCredential: StoredPasswordCredential | null;
52
+ totpCredential: StoredTotpCredential | null;
53
+ mainName: string | null;
54
+ agents: StoredAgent[];
55
+ trustedDevices: StoredTrustedDevice[];
56
+ ipWhitelist: StoredIpRule[];
57
+ terminalLayout: StoredTerminalLayout | null;
58
+ agentLayouts: Record<string, StoredTerminalLayout>;
59
+ notificationAssets: StoredNotificationAsset[];
60
+ notificationAssetDisabledIds: string[];
61
+ }
62
+ export interface FileStoreOptions {
63
+ coalesceWindowMs?: number;
64
+ onError?: (err: unknown) => void;
65
+ }
66
+ export interface UpdateOptions {
67
+ flush?: 'immediate' | 'coalesced';
68
+ }
69
+ export declare class FileStore {
70
+ readonly path: string;
71
+ private memoryState;
72
+ private dirty;
73
+ private flushTimer;
74
+ private updateQueue;
75
+ private readonly coalesceWindowMs;
76
+ private onError;
77
+ constructor(path: string, options?: FileStoreOptions);
78
+ setErrorHandler(handler: (err: unknown) => void): void;
79
+ init(): Promise<void>;
80
+ read(): Promise<StoredState>;
81
+ update(updater: (state: StoredState) => StoredState | Promise<StoredState>, options?: UpdateOptions): Promise<StoredState>;
82
+ flush(): Promise<void>;
83
+ flushSync(): void;
84
+ private flushDirty;
85
+ private scheduleFlush;
86
+ private cancelTimer;
87
+ private readFromDisk;
88
+ private writeToDisk;
89
+ private enqueue;
90
+ }