rocketride 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.
Files changed (166) hide show
  1. package/README.md +476 -0
  2. package/dist/cjs/client.js +1745 -0
  3. package/dist/cjs/client.js.map +1 -0
  4. package/dist/cjs/constants.js +48 -0
  5. package/dist/cjs/constants.js.map +1 -0
  6. package/dist/cjs/core/DAPBase.js +328 -0
  7. package/dist/cjs/core/DAPBase.js.map +1 -0
  8. package/dist/cjs/core/DAPClient.js +226 -0
  9. package/dist/cjs/core/DAPClient.js.map +1 -0
  10. package/dist/cjs/core/TransportBase.js +152 -0
  11. package/dist/cjs/core/TransportBase.js.map +1 -0
  12. package/dist/cjs/core/TransportWebSocket.js +646 -0
  13. package/dist/cjs/core/TransportWebSocket.js.map +1 -0
  14. package/dist/cjs/exceptions/index.js +119 -0
  15. package/dist/cjs/exceptions/index.js.map +1 -0
  16. package/dist/cjs/index.js +56 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/package.json +3 -0
  19. package/dist/cjs/schema/Doc.js +79 -0
  20. package/dist/cjs/schema/Doc.js.map +1 -0
  21. package/dist/cjs/schema/DocFilter.js +133 -0
  22. package/dist/cjs/schema/DocFilter.js.map +1 -0
  23. package/dist/cjs/schema/DocGroup.js +230 -0
  24. package/dist/cjs/schema/DocGroup.js.map +1 -0
  25. package/dist/cjs/schema/DocMetadata.js +58 -0
  26. package/dist/cjs/schema/DocMetadata.js.map +1 -0
  27. package/dist/cjs/schema/Question.js +414 -0
  28. package/dist/cjs/schema/Question.js.map +1 -0
  29. package/dist/cjs/schema/index.js +50 -0
  30. package/dist/cjs/schema/index.js.map +1 -0
  31. package/dist/cjs/types/client.js +26 -0
  32. package/dist/cjs/types/client.js.map +1 -0
  33. package/dist/cjs/types/data.js +26 -0
  34. package/dist/cjs/types/data.js.map +1 -0
  35. package/dist/cjs/types/events.js +119 -0
  36. package/dist/cjs/types/events.js.map +1 -0
  37. package/dist/cjs/types/index.js +50 -0
  38. package/dist/cjs/types/index.js.map +1 -0
  39. package/dist/cjs/types/pipeline.js +26 -0
  40. package/dist/cjs/types/pipeline.js.map +1 -0
  41. package/dist/cjs/types/task.js +115 -0
  42. package/dist/cjs/types/task.js.map +1 -0
  43. package/dist/cli/cli/rocketride.js +1399 -0
  44. package/dist/cli/cli/rocketride.js.map +1 -0
  45. package/dist/cli/client/client.js +1745 -0
  46. package/dist/cli/client/client.js.map +1 -0
  47. package/dist/cli/client/constants.js +48 -0
  48. package/dist/cli/client/constants.js.map +1 -0
  49. package/dist/cli/client/core/DAPBase.js +328 -0
  50. package/dist/cli/client/core/DAPBase.js.map +1 -0
  51. package/dist/cli/client/core/DAPClient.js +226 -0
  52. package/dist/cli/client/core/DAPClient.js.map +1 -0
  53. package/dist/cli/client/core/TransportBase.js +152 -0
  54. package/dist/cli/client/core/TransportBase.js.map +1 -0
  55. package/dist/cli/client/core/TransportWebSocket.js +646 -0
  56. package/dist/cli/client/core/TransportWebSocket.js.map +1 -0
  57. package/dist/cli/client/exceptions/index.js +119 -0
  58. package/dist/cli/client/exceptions/index.js.map +1 -0
  59. package/dist/cli/client/index.js +56 -0
  60. package/dist/cli/client/index.js.map +1 -0
  61. package/dist/cli/client/schema/Doc.js +79 -0
  62. package/dist/cli/client/schema/Doc.js.map +1 -0
  63. package/dist/cli/client/schema/DocFilter.js +133 -0
  64. package/dist/cli/client/schema/DocFilter.js.map +1 -0
  65. package/dist/cli/client/schema/DocGroup.js +230 -0
  66. package/dist/cli/client/schema/DocGroup.js.map +1 -0
  67. package/dist/cli/client/schema/DocMetadata.js +58 -0
  68. package/dist/cli/client/schema/DocMetadata.js.map +1 -0
  69. package/dist/cli/client/schema/Question.js +414 -0
  70. package/dist/cli/client/schema/Question.js.map +1 -0
  71. package/dist/cli/client/schema/index.js +50 -0
  72. package/dist/cli/client/schema/index.js.map +1 -0
  73. package/dist/cli/client/types/client.js +26 -0
  74. package/dist/cli/client/types/client.js.map +1 -0
  75. package/dist/cli/client/types/data.js +26 -0
  76. package/dist/cli/client/types/data.js.map +1 -0
  77. package/dist/cli/client/types/events.js +119 -0
  78. package/dist/cli/client/types/events.js.map +1 -0
  79. package/dist/cli/client/types/index.js +50 -0
  80. package/dist/cli/client/types/index.js.map +1 -0
  81. package/dist/cli/client/types/pipeline.js +26 -0
  82. package/dist/cli/client/types/pipeline.js.map +1 -0
  83. package/dist/cli/client/types/task.js +115 -0
  84. package/dist/cli/client/types/task.js.map +1 -0
  85. package/dist/esm/client.js +1740 -0
  86. package/dist/esm/client.js.map +1 -0
  87. package/dist/esm/constants.js +45 -0
  88. package/dist/esm/constants.js.map +1 -0
  89. package/dist/esm/core/DAPBase.js +324 -0
  90. package/dist/esm/core/DAPBase.js.map +1 -0
  91. package/dist/esm/core/DAPClient.js +222 -0
  92. package/dist/esm/core/DAPClient.js.map +1 -0
  93. package/dist/esm/core/TransportBase.js +148 -0
  94. package/dist/esm/core/TransportBase.js.map +1 -0
  95. package/dist/esm/core/TransportWebSocket.js +609 -0
  96. package/dist/esm/core/TransportWebSocket.js.map +1 -0
  97. package/dist/esm/exceptions/index.js +109 -0
  98. package/dist/esm/exceptions/index.js.map +1 -0
  99. package/dist/esm/index.js +40 -0
  100. package/dist/esm/index.js.map +1 -0
  101. package/dist/esm/package.json +3 -0
  102. package/dist/esm/schema/Doc.js +75 -0
  103. package/dist/esm/schema/Doc.js.map +1 -0
  104. package/dist/esm/schema/DocFilter.js +129 -0
  105. package/dist/esm/schema/DocFilter.js.map +1 -0
  106. package/dist/esm/schema/DocGroup.js +226 -0
  107. package/dist/esm/schema/DocGroup.js.map +1 -0
  108. package/dist/esm/schema/DocMetadata.js +54 -0
  109. package/dist/esm/schema/DocMetadata.js.map +1 -0
  110. package/dist/esm/schema/Question.js +409 -0
  111. package/dist/esm/schema/Question.js.map +1 -0
  112. package/dist/esm/schema/index.js +34 -0
  113. package/dist/esm/schema/index.js.map +1 -0
  114. package/dist/esm/types/client.js +25 -0
  115. package/dist/esm/types/client.js.map +1 -0
  116. package/dist/esm/types/data.js +25 -0
  117. package/dist/esm/types/data.js.map +1 -0
  118. package/dist/esm/types/events.js +116 -0
  119. package/dist/esm/types/events.js.map +1 -0
  120. package/dist/esm/types/index.js +34 -0
  121. package/dist/esm/types/index.js.map +1 -0
  122. package/dist/esm/types/pipeline.js +25 -0
  123. package/dist/esm/types/pipeline.js.map +1 -0
  124. package/dist/esm/types/task.js +112 -0
  125. package/dist/esm/types/task.js.map +1 -0
  126. package/dist/types/client.d.ts +798 -0
  127. package/dist/types/client.d.ts.map +1 -0
  128. package/dist/types/constants.d.ts +45 -0
  129. package/dist/types/constants.d.ts.map +1 -0
  130. package/dist/types/core/DAPBase.d.ts +152 -0
  131. package/dist/types/core/DAPBase.d.ts.map +1 -0
  132. package/dist/types/core/DAPClient.d.ts +93 -0
  133. package/dist/types/core/DAPClient.d.ts.map +1 -0
  134. package/dist/types/core/TransportBase.d.ts +113 -0
  135. package/dist/types/core/TransportBase.d.ts.map +1 -0
  136. package/dist/types/core/TransportWebSocket.d.ts +100 -0
  137. package/dist/types/core/TransportWebSocket.d.ts.map +1 -0
  138. package/dist/types/exceptions/index.d.ts +87 -0
  139. package/dist/types/exceptions/index.d.ts.map +1 -0
  140. package/dist/types/index.d.ts +36 -0
  141. package/dist/types/index.d.ts.map +1 -0
  142. package/dist/types/schema/Doc.d.ts +69 -0
  143. package/dist/types/schema/Doc.d.ts.map +1 -0
  144. package/dist/types/schema/DocFilter.d.ts +101 -0
  145. package/dist/types/schema/DocFilter.d.ts.map +1 -0
  146. package/dist/types/schema/DocGroup.d.ts +113 -0
  147. package/dist/types/schema/DocGroup.d.ts.map +1 -0
  148. package/dist/types/schema/DocMetadata.d.ts +77 -0
  149. package/dist/types/schema/DocMetadata.d.ts.map +1 -0
  150. package/dist/types/schema/Question.d.ts +163 -0
  151. package/dist/types/schema/Question.d.ts.map +1 -0
  152. package/dist/types/schema/index.d.ts +34 -0
  153. package/dist/types/schema/index.d.ts.map +1 -0
  154. package/dist/types/types/client.d.ts +149 -0
  155. package/dist/types/types/client.d.ts.map +1 -0
  156. package/dist/types/types/data.d.ts +95 -0
  157. package/dist/types/types/data.d.ts.map +1 -0
  158. package/dist/types/types/events.d.ts +246 -0
  159. package/dist/types/types/events.d.ts.map +1 -0
  160. package/dist/types/types/index.d.ts +34 -0
  161. package/dist/types/types/index.d.ts.map +1 -0
  162. package/dist/types/types/pipeline.d.ts +83 -0
  163. package/dist/types/types/pipeline.d.ts.map +1 -0
  164. package/dist/types/types/task.d.ts +314 -0
  165. package/dist/types/types/task.d.ts.map +1 -0
  166. package/package.json +61 -0
@@ -0,0 +1,1399 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 RocketRide, Inc.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ */
26
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ var desc = Object.getOwnPropertyDescriptor(m, k);
29
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
30
+ desc = { enumerable: true, get: function() { return m[k]; } };
31
+ }
32
+ Object.defineProperty(o, k2, desc);
33
+ }) : (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ o[k2] = m[k];
36
+ }));
37
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
38
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
39
+ }) : function(o, v) {
40
+ o["default"] = v;
41
+ });
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.RocketRideCLI = void 0;
61
+ exports.main = main;
62
+ /**
63
+ * RocketRide Unified CLI Client.
64
+ *
65
+ * This module provides a comprehensive command-line interface for managing RocketRide pipelines,
66
+ * uploading files, monitoring task status, and controlling pipeline execution using the
67
+ * Debug Adapter Protocol (DAP).
68
+ *
69
+ * Features:
70
+ * - Start and manage RocketRide data processing pipelines
71
+ * - Upload files with parallel processing and progress tracking
72
+ * - Monitor real-time pipeline status and metrics
73
+ * - Stop running pipelines gracefully
74
+ * - Environment-based configuration via .env files
75
+ *
76
+ * Configuration:
77
+ * The client supports configuration via .env file with the following variables:
78
+ * - ROCKETRIDE_APIKEY: Your RocketRide API key (required for authentication)
79
+ * - ROCKETRIDE_URI: The RocketRide server URI (defaults to wss://cloud.rocketride.ai)
80
+ * - ROCKETRIDE_PIPELINE: Path to your default pipeline configuration file
81
+ * - ROCKETRIDE_TOKEN: Task token for existing pipelines
82
+ *
83
+ * Commands:
84
+ * - start: Start a new pipeline from configuration file
85
+ * - upload: Upload files to a pipeline (with --pipeline or --token)
86
+ * - status: Monitor real-time status of a running pipeline
87
+ * - stop: Terminate a running pipeline gracefully
88
+ *
89
+ * @example
90
+ * ```bash
91
+ * # Start a pipeline
92
+ * rocketride start --pipeline ./my-pipeline.json --apikey YOUR_KEY
93
+ *
94
+ * # Upload files with progress tracking
95
+ * rocketride upload files/*.csv --pipeline ./pipeline.json --max-concurrent 10
96
+ *
97
+ * # Monitor pipeline status
98
+ * rocketride status --token TASK_TOKEN
99
+ *
100
+ * # Stop a pipeline
101
+ * rocketride stop --token TASK_TOKEN
102
+ * ```
103
+ */
104
+ const fs = __importStar(require("fs"));
105
+ const path = __importStar(require("path"));
106
+ const glob = __importStar(require("glob"));
107
+ const process = __importStar(require("process"));
108
+ const commander_1 = require("commander");
109
+ const client_1 = require("../client/client");
110
+ // ANSI Color and Control Codes for terminal formatting
111
+ const ANSI_RESET = '\x1b[0m';
112
+ const ANSI_RED = '\x1b[91m';
113
+ const ANSI_GREEN = '\x1b[92m';
114
+ const ANSI_YELLOW = '\x1b[93m';
115
+ const ANSI_BLUE = '\x1b[94m';
116
+ const ANSI_GRAY = '\x1b[90m';
117
+ const ANSI_CLEAR_SCREEN = '\x1b[2J';
118
+ const ANSI_CURSOR_HOME = '\x1b[1;1H';
119
+ // Global character mapping
120
+ const CHR_TL = '┌';
121
+ const CHR_TR = '┐';
122
+ const CHR_BL = '└';
123
+ const CHR_BR = '┘';
124
+ const CHR_HORIZ = '─';
125
+ const CHR_VERT = '│';
126
+ const CHR_BLOCK = '█';
127
+ const CHR_LIGHT_BLOCK = '░';
128
+ const CHR_CHECK = '✓';
129
+ const CHR_CROSS = '✗';
130
+ const ESC = String.fromCharCode(27);
131
+ const ANSI_ESCAPE_PATTERN = new RegExp(ESC + '\\[[0-9;]*[mK]', 'g');
132
+ class Box {
133
+ constructor(title, lines, width = 75) {
134
+ this.title = title;
135
+ this.lines = lines || [];
136
+ this.width = width;
137
+ }
138
+ visualLength(text) {
139
+ return text.replace(ANSI_ESCAPE_PATTERN, '').length;
140
+ }
141
+ boxTop() {
142
+ const titlePart = ` ${this.title} `;
143
+ const remainingWidth = (this.width - 3) - titlePart.length;
144
+ return CHR_TL + CHR_HORIZ + titlePart + CHR_HORIZ.repeat(Math.max(0, remainingWidth)) + CHR_TR;
145
+ }
146
+ boxMiddle(content) {
147
+ const visualWidth = this.visualLength(content);
148
+ const availableWidth = this.width - 3;
149
+ let finalContent = content;
150
+ if (visualWidth > availableWidth) {
151
+ finalContent = content.substring(0, availableWidth - 3) + '...';
152
+ }
153
+ const padding = availableWidth - this.visualLength(finalContent);
154
+ return CHR_VERT + ' ' + finalContent + ' '.repeat(Math.max(0, padding)) + CHR_VERT;
155
+ }
156
+ boxBottom() {
157
+ return CHR_BL + CHR_HORIZ.repeat(this.width - 2) + CHR_BR;
158
+ }
159
+ render() {
160
+ if (this.lines.length === 0) {
161
+ return [];
162
+ }
163
+ const output = [];
164
+ output.push(this.boxTop());
165
+ for (const line of this.lines) {
166
+ output.push(this.boxMiddle(line));
167
+ }
168
+ output.push(this.boxBottom());
169
+ return output;
170
+ }
171
+ }
172
+ class BoxMonitor {
173
+ constructor(cli, commandTitle, width, height) {
174
+ this.boxes = [];
175
+ this.lastLineCount = 0;
176
+ this.screenCleared = false;
177
+ this.commandStatus = ['Initializing...'];
178
+ this.cli = cli;
179
+ this.commandTitle = commandTitle;
180
+ if (width === undefined || height === undefined) {
181
+ const [detectedWidth, detectedHeight] = this.detectTerminalSize();
182
+ this.width = width ?? detectedWidth;
183
+ this.height = height ?? detectedHeight;
184
+ }
185
+ else {
186
+ this.width = width;
187
+ this.height = height;
188
+ }
189
+ this.isTerminal = this.isTerminalCheck();
190
+ }
191
+ detectTerminalSize() {
192
+ try {
193
+ const size = process.stdout.getWindowSize();
194
+ const width = size[0];
195
+ const height = size[1];
196
+ if (width >= 20 && width <= 300 && height >= 10 && height <= 100) {
197
+ return [Math.max(width - 1, 20), Math.max(height - 2, 10)];
198
+ }
199
+ }
200
+ catch {
201
+ // Ignore errors
202
+ }
203
+ return [79, 41];
204
+ }
205
+ isTerminalCheck() {
206
+ return process.stdout.isTTY && process.stderr.isTTY;
207
+ }
208
+ updateTerminalSize() {
209
+ const [width, height] = this.detectTerminalSize();
210
+ this.width = width;
211
+ this.height = height;
212
+ }
213
+ clear() {
214
+ this.boxes = [];
215
+ }
216
+ clearScreen() {
217
+ this.screenCleared = false;
218
+ this.lastLineCount = 0;
219
+ }
220
+ setCommandStatus(status) {
221
+ if (typeof status === 'string') {
222
+ this.commandStatus = [status];
223
+ }
224
+ else {
225
+ this.commandStatus = status;
226
+ }
227
+ }
228
+ addBox(title, lines) {
229
+ if (lines.length > 0) {
230
+ const box = new Box(title, lines, this.width);
231
+ this.boxes.push(box);
232
+ }
233
+ }
234
+ positionToTop() {
235
+ process.stdout.write(ANSI_CURSOR_HOME);
236
+ }
237
+ draw() {
238
+ this.updateTerminalSize();
239
+ if (!this.screenCleared) {
240
+ process.stdout.write(ANSI_CLEAR_SCREEN);
241
+ this.screenCleared = true;
242
+ }
243
+ this.positionToTop();
244
+ const allBoxes = [];
245
+ if (this.commandStatus.length > 0) {
246
+ const commandBox = new Box(this.commandTitle, this.commandStatus, this.width);
247
+ allBoxes.push(commandBox);
248
+ }
249
+ allBoxes.push(...this.boxes);
250
+ const allLines = [];
251
+ for (let i = 0; i < allBoxes.length; i++) {
252
+ const boxLines = allBoxes[i].render();
253
+ allLines.push(...boxLines);
254
+ if (i < allBoxes.length - 1 && boxLines.length > 0) {
255
+ allLines.push(' '.repeat(this.width));
256
+ }
257
+ }
258
+ for (const line of allLines) {
259
+ console.log(line + ' ');
260
+ }
261
+ const currentLineCount = allLines.length;
262
+ if (currentLineCount < this.lastLineCount) {
263
+ for (let i = currentLineCount; i < this.lastLineCount; i++) {
264
+ console.log(' '.repeat(this.width));
265
+ }
266
+ }
267
+ this.lastLineCount = currentLineCount;
268
+ process.stdout.write('');
269
+ }
270
+ formatSize(sizeBytes) {
271
+ if (sizeBytes === 0)
272
+ return '0 B';
273
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
274
+ let unitIndex = 0;
275
+ let size = sizeBytes;
276
+ while (size >= 1024 && unitIndex < units.length - 1) {
277
+ size /= 1024;
278
+ unitIndex++;
279
+ }
280
+ if (unitIndex === 0) {
281
+ return `${Math.floor(size)} ${units[unitIndex]}`;
282
+ }
283
+ else {
284
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
285
+ }
286
+ }
287
+ onEvent(_message) {
288
+ // Override in subclasses
289
+ }
290
+ }
291
+ class GenericMonitor extends BoxMonitor {
292
+ constructor(cli, commandTitle, width, height) {
293
+ super(cli, commandTitle, width, height);
294
+ }
295
+ }
296
+ class StatusMonitor extends BoxMonitor {
297
+ constructor(cli, token, width, height) {
298
+ super(cli, 'RocketRide Task Monitor', width, height);
299
+ this.token = token;
300
+ this.setCommandStatus(`Token: ${this.token}`);
301
+ }
302
+ formatDuration(startTime, endTime) {
303
+ if (startTime === 0)
304
+ return 'Not started';
305
+ const end = endTime || Date.now() / 1000;
306
+ const totalSeconds = Math.floor(end - startTime);
307
+ if (totalSeconds < 60) {
308
+ return `${totalSeconds}secs`;
309
+ }
310
+ else if (totalSeconds < 3600) {
311
+ const minutes = Math.floor(totalSeconds / 60);
312
+ const seconds = totalSeconds % 60;
313
+ return `${minutes}min, ${seconds}secs`;
314
+ }
315
+ else {
316
+ const hours = Math.floor(totalSeconds / 3600);
317
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
318
+ const seconds = totalSeconds % 60;
319
+ return `${hours}hr, ${minutes}min, ${seconds}secs`;
320
+ }
321
+ }
322
+ getStateDisplay(state) {
323
+ const stateMap = {
324
+ 0: ['Offline', ANSI_GRAY],
325
+ 1: ['Offline', ANSI_GRAY],
326
+ 2: ['Initializing', ANSI_BLUE],
327
+ 3: ['Online', ANSI_GREEN],
328
+ 4: ['Stopping', ANSI_YELLOW],
329
+ 5: ['Offline', ANSI_GRAY],
330
+ 6: ['Offline', ANSI_GRAY]
331
+ };
332
+ return stateMap[state] || ['Unknown', ANSI_RESET];
333
+ }
334
+ hasCountData(status) {
335
+ return ((Number(status.totalSize) || 0) > 0 ||
336
+ (Number(status.totalCount) || 0) > 0 ||
337
+ (Number(status.completedSize) || 0) > 0 ||
338
+ (Number(status.completedCount) || 0) > 0 ||
339
+ (Number(status.failedSize) || 0) > 0 ||
340
+ (Number(status.failedCount) || 0) > 0 ||
341
+ (Number(status.rateSize) || 0) > 0 ||
342
+ (Number(status.rateCount) || 0) > 0);
343
+ }
344
+ hasMetricsData(status) {
345
+ const metrics = (status.metrics || {});
346
+ return Object.values(metrics).some(value => typeof value === 'number' && value > 0);
347
+ }
348
+ onEvent(message) {
349
+ const eventType = message.event || '';
350
+ if (eventType !== 'apaevt_status_update') {
351
+ return;
352
+ }
353
+ const status = (message.body || {});
354
+ this.displayStatus(status);
355
+ }
356
+ displayStatus(status) {
357
+ this.clear();
358
+ if (status) {
359
+ const pipelineLines = this.buildPipelineLines(status);
360
+ this.addBox('Pipeline Status', pipelineLines);
361
+ const metricsLines = this.buildMetricsLines(status);
362
+ this.addBox('Metrics', metricsLines);
363
+ const errorLines = this.buildErrorLines((status.errors || []), 'Error');
364
+ this.addBox('Errors', errorLines);
365
+ const warningLines = this.buildErrorLines((status.warnings || []), 'Warning');
366
+ this.addBox('Warnings', warningLines);
367
+ const noteLines = this.buildNoteLines((status.notes || []));
368
+ this.addBox('Notes', noteLines);
369
+ }
370
+ else {
371
+ this.addBox('Status', ['No status available']);
372
+ }
373
+ this.draw();
374
+ }
375
+ buildPipelineLines(status) {
376
+ const lines = [];
377
+ if (status.name) {
378
+ lines.push(String(status.name));
379
+ lines.push('');
380
+ }
381
+ if (status.status) {
382
+ lines.push(String(status.status));
383
+ lines.push('');
384
+ }
385
+ const state = Number(status.state) || 0;
386
+ const [stateName, stateColor] = this.getStateDisplay(state);
387
+ lines.push(`State: ${stateColor}${stateName}${ANSI_RESET}`);
388
+ const startTime = Number(status.startTime) || 0;
389
+ if (startTime > 0) {
390
+ const startStr = new Date(startTime * 1000).toLocaleString();
391
+ lines.push(`Started: ${startStr}`);
392
+ const endTime = status.completed ? (Number(status.endTime) || 0) : undefined;
393
+ const duration = this.formatDuration(startTime, endTime);
394
+ lines.push(`Elapsed: ${duration}`);
395
+ }
396
+ if (this.hasCountData(status)) {
397
+ lines.push('');
398
+ const dataTypes = [
399
+ ['total', 'Total'],
400
+ ['completed', 'Completed'],
401
+ ['failed', 'Failed']
402
+ ];
403
+ for (const [keyBase, label] of dataTypes) {
404
+ const count = Number(status[`${keyBase}Count`]) || 0;
405
+ const size = Number(status[`${keyBase}Size`]) || 0;
406
+ if (count > 0 || size > 0) {
407
+ lines.push(`${label}: ${count} items (${this.formatSize(size)})`);
408
+ }
409
+ }
410
+ const rateSize = Number(status.rateSize) || 0;
411
+ const rateCount = Number(status.rateCount) || 0;
412
+ if (rateSize > 0 || rateCount > 0) {
413
+ lines.push(`Rate: ${this.formatSize(rateSize)}/s (${rateCount}/s items)`);
414
+ }
415
+ }
416
+ return lines.length > 0 ? lines : ['No pipeline data available'];
417
+ }
418
+ buildMetricsLines(status) {
419
+ if (!this.hasMetricsData(status)) {
420
+ return [];
421
+ }
422
+ const lines = [];
423
+ const metrics = (status.metrics || {});
424
+ for (const [key, value] of Object.entries(metrics)) {
425
+ if (typeof value === 'number' && value > 0) {
426
+ const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
427
+ lines.push(`${label}: ${value}`);
428
+ }
429
+ }
430
+ return lines;
431
+ }
432
+ buildErrorLines(items, errorType) {
433
+ if (!items || items.length === 0) {
434
+ return [];
435
+ }
436
+ const lines = [];
437
+ const color = errorType === 'Error' ? ANSI_RED : ANSI_YELLOW;
438
+ for (const item of items.slice(-5)) {
439
+ const parts = item.split('*');
440
+ if (parts.length >= 3) {
441
+ const errType = parts[0].trim();
442
+ const message = parts[1].replace(/`/g, '').trim();
443
+ const fileInfo = parts[2].trim();
444
+ const filename = fileInfo.includes('\\') || fileInfo.includes('/') ?
445
+ path.basename(fileInfo) : fileInfo;
446
+ lines.push(`${color}${errType}${ANSI_RESET}: ${message}`);
447
+ if (filename) {
448
+ lines.push(` -> ${filename}`);
449
+ }
450
+ }
451
+ else {
452
+ lines.push(`${color}• ${ANSI_RESET}${item}`);
453
+ }
454
+ }
455
+ return lines;
456
+ }
457
+ buildNoteLines(items) {
458
+ if (!items || items.length === 0) {
459
+ return [];
460
+ }
461
+ return items.slice(-5).map(item => `• ${item}`);
462
+ }
463
+ displayConnecting(url, attempt) {
464
+ this.clear();
465
+ const retry = attempt > 0 ? ` (attempt ${attempt})` : '';
466
+ const connectionLines = [`Connecting to ${url}${retry}...`];
467
+ this.addBox('Connection Status', connectionLines);
468
+ this.addBox('Controls', ['Press Ctrl+C to stop monitoring...']);
469
+ this.draw();
470
+ }
471
+ }
472
+ class UploadProgressMonitor extends BoxMonitor {
473
+ constructor(cli, width, height) {
474
+ super(cli, 'RocketRide File Upload', width, height);
475
+ this.totalFiles = 0;
476
+ this.activeUploads = new Map();
477
+ this.completedUploads = new Map();
478
+ this.failedUploads = new Map();
479
+ this.setCommandStatus('Preparing upload...');
480
+ }
481
+ createProgressBar(percent, width = 30) {
482
+ const filledLength = Math.floor(width * percent / 100);
483
+ const bar = CHR_BLOCK.repeat(filledLength) + CHR_LIGHT_BLOCK.repeat(width - filledLength);
484
+ return `[${bar}] ${percent.toFixed(1).padStart(5)}%`;
485
+ }
486
+ truncateFilename(filename, maxLength) {
487
+ if (filename.length <= maxLength) {
488
+ return filename;
489
+ }
490
+ const ext = path.extname(filename);
491
+ const name = path.basename(filename, ext);
492
+ if (ext.length < maxLength - 3) {
493
+ const available = maxLength - ext.length - 3;
494
+ return `${name.substring(0, available)}...${ext}`;
495
+ }
496
+ else {
497
+ return `${filename.substring(0, maxLength - 3)}...`;
498
+ }
499
+ }
500
+ setTotalFiles(totalFiles) {
501
+ this.totalFiles = totalFiles;
502
+ }
503
+ onUploadProgress(result) {
504
+ // Process UPLOAD_RESULT from sendFiles
505
+ const filepath = String(result.filepath || 'unknown');
506
+ const bytesSent = Number(result.bytes_sent) || 0;
507
+ const fileSize = Number(result.file_size) || 0;
508
+ const action = String(result.action || '');
509
+ const filename = path.basename(filepath);
510
+ if (action === 'open') {
511
+ // Don't show pending opens
512
+ }
513
+ else if (action === 'write') {
514
+ this.activeUploads.set(filename, {
515
+ filepath,
516
+ action,
517
+ bytes_sent: bytesSent,
518
+ file_size: fileSize
519
+ });
520
+ }
521
+ else if (action === 'close') {
522
+ const existing = this.activeUploads.get(filename);
523
+ if (existing) {
524
+ this.activeUploads.set(filename, {
525
+ ...existing,
526
+ action,
527
+ bytes_sent: bytesSent,
528
+ file_size: fileSize
529
+ });
530
+ }
531
+ }
532
+ else if (action === 'complete') {
533
+ this.activeUploads.delete(filename);
534
+ this.completedUploads.set(filename, {
535
+ filepath,
536
+ action,
537
+ file_size: fileSize
538
+ });
539
+ }
540
+ else if (action === 'error') {
541
+ this.activeUploads.delete(filename);
542
+ const errorMessage = String(result.error || 'Unknown error');
543
+ this.failedUploads.set(filename, {
544
+ filepath,
545
+ action,
546
+ file_size: fileSize,
547
+ error: errorMessage
548
+ });
549
+ }
550
+ this.draw();
551
+ }
552
+ onEvent(message) {
553
+ const eventType = message.event || '';
554
+ if (eventType !== 'apaevt_status_upload') {
555
+ return;
556
+ }
557
+ const body = (message.body || {});
558
+ const filepath = String(body.filepath || 'unknown');
559
+ const bytesSent = Number(body.bytes_sent) || 0;
560
+ const fileSize = Number(body.file_size) || 0;
561
+ const action = String(body.action || '');
562
+ const filename = path.basename(filepath);
563
+ if (action === 'open') {
564
+ // Don't show pending opens
565
+ }
566
+ else if (action === 'write') {
567
+ this.activeUploads.set(filename, {
568
+ filepath,
569
+ action,
570
+ bytes_sent: bytesSent,
571
+ file_size: fileSize
572
+ });
573
+ }
574
+ else if (action === 'close') {
575
+ const existing = this.activeUploads.get(filename);
576
+ if (existing) {
577
+ this.activeUploads.set(filename, {
578
+ ...existing,
579
+ action,
580
+ bytes_sent: bytesSent,
581
+ file_size: fileSize
582
+ });
583
+ }
584
+ }
585
+ else if (action === 'complete') {
586
+ this.activeUploads.delete(filename);
587
+ this.completedUploads.set(filename, {
588
+ filepath,
589
+ action,
590
+ file_size: fileSize
591
+ });
592
+ }
593
+ else if (action === 'error') {
594
+ this.activeUploads.delete(filename);
595
+ const errorMessage = String(body.error || 'Unknown error');
596
+ this.failedUploads.set(filename, {
597
+ filepath,
598
+ action,
599
+ file_size: fileSize,
600
+ error: errorMessage
601
+ });
602
+ }
603
+ if (this.cli.isCancelled()) {
604
+ this.setCommandStatus(`${ANSI_RED}Upload cancelling...${ANSI_RESET}`);
605
+ }
606
+ else {
607
+ const totalProcessed = this.completedUploads.size + this.failedUploads.size;
608
+ this.setCommandStatus(`Processed ${totalProcessed} of ${this.totalFiles} files...`);
609
+ }
610
+ this.renderUploadStatus();
611
+ if (this.cli.isCancelled()) {
612
+ throw new Error('Upload cancelled');
613
+ }
614
+ }
615
+ renderUploadStatus() {
616
+ this.clear();
617
+ // Add active uploads box
618
+ if (this.activeUploads.size > 0) {
619
+ const uploadLines = [];
620
+ const activeEntries = Array.from(this.activeUploads.entries());
621
+ for (const [filename, data] of activeEntries.slice(0, 10)) {
622
+ const displayName = this.truncateFilename(filename, 20).padEnd(20);
623
+ const action = String(data.action || '');
624
+ const phase = action === 'write' ? 'Writing ' : action === 'close' ? 'Finalize' : ' ';
625
+ const bytesSent = Number(data.bytes_sent) || 0;
626
+ const fileSize = Number(data.file_size) || 1;
627
+ const percent = fileSize > 0 ? (bytesSent / fileSize * 100) : 0;
628
+ const progressBar = this.createProgressBar(percent, 12);
629
+ const sizeInfo = `${this.formatSize(bytesSent)}/${this.formatSize(fileSize)}`;
630
+ uploadLines.push(`${displayName} ${phase} ${progressBar} ${sizeInfo}`);
631
+ }
632
+ if (this.activeUploads.size > 10) {
633
+ const remaining = this.activeUploads.size - 10;
634
+ uploadLines.push(`... and ${remaining} more uploads in progress`);
635
+ }
636
+ this.addBox(`Active Uploads (${this.activeUploads.size})`, uploadLines);
637
+ }
638
+ // Add summary box
639
+ if (this.completedUploads.size > 0 || this.failedUploads.size > 0) {
640
+ const summaryLines = [];
641
+ if (this.completedUploads.size > 0) {
642
+ summaryLines.push(`Completed: ${this.completedUploads.size} files`);
643
+ }
644
+ if (this.failedUploads.size > 0) {
645
+ summaryLines.push(`Failed: ${this.failedUploads.size} files`);
646
+ }
647
+ const totalBytes = Array.from(this.completedUploads.values())
648
+ .reduce((sum, data) => sum + (Number(data.file_size) || 0), 0);
649
+ summaryLines.push(`Total size: ${this.formatSize(totalBytes)}`);
650
+ this.addBox('Upload Summary', summaryLines);
651
+ }
652
+ // Add failed uploads box
653
+ if (this.failedUploads.size > 0) {
654
+ const failedLines = [];
655
+ const failedEntries = Array.from(this.failedUploads.entries());
656
+ const displayCount = failedEntries.length > 5 ? 4 : 5;
657
+ for (const [filename, data] of failedEntries.slice(-displayCount)) {
658
+ const displayName = this.truncateFilename(filename, 25);
659
+ const errorStr = String(data.error || '');
660
+ const errorMsg = errorStr.length > 30 ?
661
+ `${errorStr.substring(0, 30)}...` : errorStr;
662
+ failedLines.push(`${ANSI_RED}${CHR_CROSS}${ANSI_RESET} ${displayName} - ${errorMsg}`);
663
+ }
664
+ if (failedEntries.length > 5) {
665
+ const remaining = failedEntries.length - 4;
666
+ failedLines.push(`... and ${remaining} more files have failed`);
667
+ }
668
+ this.addBox(`Failed Uploads (${this.failedUploads.size})`, failedLines);
669
+ }
670
+ // Add recently completed box
671
+ if (this.completedUploads.size > 0) {
672
+ const completedLines = [];
673
+ const completedEntries = Array.from(this.completedUploads.entries());
674
+ for (const [filename, data] of completedEntries.slice(-3)) {
675
+ const displayName = this.truncateFilename(filename, 35);
676
+ const sizeStr = this.formatSize(Number(data.file_size) || 0);
677
+ completedLines.push(`${ANSI_GREEN}${CHR_CHECK}${ANSI_RESET} ${displayName} (${sizeStr})`);
678
+ }
679
+ this.addBox('Recently Completed', completedLines);
680
+ }
681
+ this.draw();
682
+ }
683
+ reset() {
684
+ this.activeUploads.clear();
685
+ this.completedUploads.clear();
686
+ this.failedUploads.clear();
687
+ this.setCommandStatus('Preparing upload...');
688
+ this.clearScreen();
689
+ }
690
+ }
691
+ class RocketRideCLI {
692
+ constructor() {
693
+ this.args = {};
694
+ this.uploadStats = {
695
+ files_processed: 0,
696
+ total_bytes: 0,
697
+ successful_uploads: 0,
698
+ failed_uploads: 0,
699
+ upload_times: []
700
+ };
701
+ this.uri = '';
702
+ this.connected = false;
703
+ this.attempt = 0;
704
+ this.cancelled = false;
705
+ this.setupSignalHandlers();
706
+ }
707
+ cancel() {
708
+ this.cancelled = true;
709
+ }
710
+ isCancelled() {
711
+ return this.cancelled;
712
+ }
713
+ setupSignalHandlers() {
714
+ // TODO: Enable proper signal handling
715
+ // const signalHandler = () => {
716
+ // this.cancel();
717
+ // };
718
+ // process.on('SIGINT', signalHandler);
719
+ // process.on('SIGTERM', signalHandler);
720
+ }
721
+ createProgram() {
722
+ const program = new commander_1.Command();
723
+ program
724
+ .name('rocketride')
725
+ .description('RocketRide Unified Pipeline and File Management CLI')
726
+ .version('1.3.0');
727
+ // Common options
728
+ const addCommonOptions = (cmd) => {
729
+ return cmd
730
+ .option('--host <hostname>', 'RocketRide DAP server hostname', 'localhost')
731
+ .option('--port <port>', 'RocketRide DAP server port', '5565')
732
+ .option('--apikey <key>', 'API key for RocketRide server authentication (can use ROCKETRIDE_APIKEY in .env or env var)', process.env.ROCKETRIDE_APIKEY);
733
+ };
734
+ // Start command
735
+ const startCmd = program
736
+ .command('start')
737
+ .description('Start a new pipeline')
738
+ .option('--pipeline <file>', 'Path to .pipeline file containing pipeline configuration (can use ROCKETRIDE_PIPELINE in .env or env var)', process.env.ROCKETRIDE_PIPELINE)
739
+ .option('--token <token>', 'Optional existing task token for pipeline resume/control (can use ROCKETRIDE_TOKEN in .env or env var)', process.env.ROCKETRIDE_TOKEN)
740
+ .option('--threads <num>', 'Number of threads to use for pipeline execution', '4')
741
+ .option('--args <args...>', 'Additional arguments to pass to pipeline execution')
742
+ .action(async (options) => {
743
+ // Validate required arguments - validation will happen in createAndConnectClient
744
+ if (!options.pipeline) {
745
+ console.error('Error: Pipeline file is required for start command. Use --pipeline or set ROCKETRIDE_PIPELINE in .env file');
746
+ process.exit(1);
747
+ }
748
+ this.args = {
749
+ command: 'start',
750
+ ...options,
751
+ pipeline: options.pipeline,
752
+ port: parseInt(options.port),
753
+ threads: parseInt(options.threads)
754
+ };
755
+ this.uri = `ws://${this.args.host}:${this.args.port}`;
756
+ try {
757
+ const exitCode = await this.cmdStart();
758
+ process.exit(exitCode);
759
+ }
760
+ finally {
761
+ this.cancel();
762
+ await this.cleanupClient();
763
+ }
764
+ });
765
+ addCommonOptions(startCmd);
766
+ // Upload command
767
+ const uploadCmd = program
768
+ .command('upload')
769
+ .description('Upload files using --pipeline or an existing task token')
770
+ .argument('<files...>', 'Files, wildcards, or directories to upload')
771
+ .option('--pipeline <file>', 'Pipeline file to start new task (can use ROCKETRIDE_PIPELINE in .env or env var)', process.env.ROCKETRIDE_PIPELINE)
772
+ .option('--token <token>', 'Existing task token to use for uploads (can use ROCKETRIDE_TOKEN in .env or env var)', process.env.ROCKETRIDE_TOKEN)
773
+ .option('--threads <num>', 'Number of threads to use for pipeline execution', '4')
774
+ .option('--max-concurrent <num>', 'Maximum number of concurrent file uploads', '5')
775
+ .option('--args <args...>', 'Additional arguments to pass to pipeline execution')
776
+ .action(async (files, options) => {
777
+ // Validate required arguments - validation will happen in createAndConnectClient
778
+ if (!options.pipeline && !options.token) {
779
+ console.error('Error: Either --pipeline or --token must be specified for upload command. Use --pipeline/--token or set ROCKETRIDE_PIPELINE/ROCKETRIDE_TOKEN in .env file');
780
+ process.exit(1);
781
+ }
782
+ this.args = {
783
+ command: 'upload',
784
+ ...options,
785
+ files,
786
+ port: parseInt(options.port),
787
+ threads: parseInt(options.threads),
788
+ max_concurrent: parseInt(options.maxConcurrent || '5'),
789
+ pipeline_args: options.args
790
+ };
791
+ this.uri = `ws://${this.args.host}:${this.args.port}`;
792
+ try {
793
+ const exitCode = await this.cmdUpload();
794
+ process.exit(exitCode);
795
+ }
796
+ finally {
797
+ this.cancel();
798
+ await this.cleanupClient();
799
+ }
800
+ });
801
+ addCommonOptions(uploadCmd);
802
+ // Status command
803
+ const statusCmd = program
804
+ .command('status')
805
+ .description('Monitor task status continuously')
806
+ .option('--token <token>', 'Task token to monitor (can use ROCKETRIDE_TOKEN in .env or env var)', process.env.ROCKETRIDE_TOKEN)
807
+ .action(async (options) => {
808
+ // Validate required arguments - validation will happen in createAndConnectClient
809
+ if (!options.token) {
810
+ console.error('Error: Token is required for status command. Use --token or set ROCKETRIDE_TOKEN in .env file');
811
+ process.exit(1);
812
+ }
813
+ this.args = {
814
+ command: 'status',
815
+ ...options,
816
+ port: parseInt(options.port)
817
+ };
818
+ this.uri = `ws://${this.args.host}:${this.args.port}`;
819
+ try {
820
+ const exitCode = await this.cmdStatus();
821
+ process.exit(exitCode);
822
+ }
823
+ finally {
824
+ this.cancel();
825
+ await this.cleanupClient();
826
+ }
827
+ });
828
+ addCommonOptions(statusCmd);
829
+ // Stop command
830
+ const stopCmd = program
831
+ .command('stop')
832
+ .description('Stop a running task')
833
+ .option('--token <token>', 'Task token to stop (can use ROCKETRIDE_TOKEN in .env or env var)', process.env.ROCKETRIDE_TOKEN)
834
+ .action(async (options) => {
835
+ // Validate required arguments - validation will happen in createAndConnectClient
836
+ if (!options.token) {
837
+ console.error('Error: Token is required for stop command. Use --token or set ROCKETRIDE_TOKEN in .env file');
838
+ process.exit(1);
839
+ }
840
+ this.args = {
841
+ command: 'stop',
842
+ ...options,
843
+ port: parseInt(options.port)
844
+ };
845
+ this.uri = `ws://${this.args.host}:${this.args.port}`;
846
+ try {
847
+ const exitCode = await this.cmdStop();
848
+ process.exit(exitCode);
849
+ }
850
+ finally {
851
+ this.cancel();
852
+ await this.cleanupClient();
853
+ }
854
+ });
855
+ addCommonOptions(stopCmd);
856
+ return program;
857
+ }
858
+ async handleEvent(message) {
859
+ if (this.monitor) {
860
+ this.monitor.onEvent(message);
861
+ }
862
+ }
863
+ async createAndConnectClient(onConnected, onDisconnected) {
864
+ const uri = `${this.uri}/task/service`;
865
+ this.client = new client_1.RocketRideClient({
866
+ uri,
867
+ auth: this.args.apikey,
868
+ onEvent: this.handleEvent.bind(this),
869
+ onConnected,
870
+ onDisconnected
871
+ });
872
+ await this.client.connect();
873
+ return this.client;
874
+ }
875
+ async sendMonitorCommand(subscribe, token) {
876
+ try {
877
+ if (!this.client)
878
+ return false;
879
+ const arguments_ = { subscribe };
880
+ const monitorRequest = this.client.buildRequest('rrext_monitor', {
881
+ token,
882
+ arguments: arguments_
883
+ });
884
+ const monitorResponse = await this.client.request(monitorRequest);
885
+ return !this.client.didFail(monitorResponse);
886
+ }
887
+ catch {
888
+ return false;
889
+ }
890
+ }
891
+ async cleanupClient() {
892
+ if (this.client) {
893
+ try {
894
+ await this.client.disconnect();
895
+ }
896
+ catch {
897
+ // Ignore cleanup errors
898
+ }
899
+ finally {
900
+ this.client = undefined;
901
+ }
902
+ }
903
+ }
904
+ loadPipelineConfig(pipelineFile) {
905
+ if (!fs.existsSync(pipelineFile) || !fs.statSync(pipelineFile).isFile()) {
906
+ throw new Error(`Pipeline file not found: ${pipelineFile}`);
907
+ }
908
+ try {
909
+ const content = fs.readFileSync(pipelineFile, 'utf-8');
910
+ try {
911
+ return JSON.parse(content);
912
+ }
913
+ catch (error) {
914
+ throw new Error(`Invalid JSON format in ${pipelineFile}: ${error}`);
915
+ }
916
+ }
917
+ catch (error) {
918
+ if (error instanceof Error && (error.message.includes('not found') || error.message.includes('Invalid'))) {
919
+ throw error;
920
+ }
921
+ else {
922
+ throw new Error(`Error reading ${pipelineFile}: ${error}`);
923
+ }
924
+ }
925
+ }
926
+ async cmdStart() {
927
+ try {
928
+ this.monitor = new GenericMonitor(this, 'RocketRide Pipeline Execution');
929
+ this.monitor.setCommandStatus('Loading pipeline configuration...');
930
+ this.monitor.draw();
931
+ const pipelineData = this.loadPipelineConfig(this.args.pipeline);
932
+ this.monitor.setCommandStatus(['Pipeline loaded successfully', 'Connecting to server...']);
933
+ this.monitor.draw();
934
+ await this.createAndConnectClient();
935
+ this.monitor.setCommandStatus(['Connected to server', 'Starting pipeline execution...']);
936
+ this.monitor.draw();
937
+ const taskToken = await this.client.use({
938
+ pipeline: pipelineData,
939
+ threads: this.args.threads,
940
+ token: this.args.token,
941
+ args: this.args.pipeline_args || []
942
+ });
943
+ const executionLines = [
944
+ 'Pipeline execution started successfully',
945
+ `Task token: ${taskToken}`,
946
+ '',
947
+ 'Use the following command to monitor status:',
948
+ `rocketride status --token ${taskToken} --apikey ${this.args.apikey}`
949
+ ];
950
+ this.monitor.setCommandStatus(executionLines);
951
+ this.monitor.draw();
952
+ return 0;
953
+ }
954
+ catch (error) {
955
+ if (!this.monitor) {
956
+ this.monitor = new GenericMonitor(this, 'RocketRide Pipeline Execution');
957
+ }
958
+ if (error instanceof Error && (error.message.includes('not found') || error.message.includes('Invalid'))) {
959
+ this.monitor.setCommandStatus('Configuration error occurred');
960
+ this.monitor.addBox('Configuration Error', [error.message]);
961
+ }
962
+ else {
963
+ this.monitor.setCommandStatus('Execution failed');
964
+ this.monitor.addBox('Execution Error', [String(error)]);
965
+ }
966
+ this.monitor.draw();
967
+ return 1;
968
+ }
969
+ finally {
970
+ await this.cleanupClient();
971
+ }
972
+ }
973
+ async cmdUpload() {
974
+ try {
975
+ this.monitor = new UploadProgressMonitor(this);
976
+ this.uploadStats = {
977
+ files_processed: 0,
978
+ total_bytes: 0,
979
+ successful_uploads: 0,
980
+ failed_uploads: 0,
981
+ upload_times: []
982
+ };
983
+ let pipelineConfig;
984
+ let taskToken;
985
+ let shouldManagePipeline = false;
986
+ if (this.args.pipeline) {
987
+ this.monitor.setCommandStatus('Loading pipeline configuration...');
988
+ this.monitor.draw();
989
+ pipelineConfig = this.loadPipelineConfig(this.args.pipeline);
990
+ shouldManagePipeline = true;
991
+ }
992
+ else if (this.args.token) {
993
+ taskToken = this.args.token;
994
+ this.monitor.setCommandStatus('Using existing task token...');
995
+ this.monitor.draw();
996
+ }
997
+ else {
998
+ this.monitor.setCommandStatus('Configuration error');
999
+ this.monitor.addBox('Upload Error', ['Either --pipeline or --token must be specified for upload command']);
1000
+ this.monitor.draw();
1001
+ return 1;
1002
+ }
1003
+ // Find and validate files
1004
+ this.monitor.setCommandStatus(`Discovering files from ${(this.args.files || []).length} patterns...`);
1005
+ this.monitor.draw();
1006
+ const allFiles = this.findFiles(this.args.files || []);
1007
+ if (allFiles.length === 0) {
1008
+ this.monitor.setCommandStatus('File discovery failed');
1009
+ this.monitor.addBox('Upload Error', ['No files found matching the specified patterns!']);
1010
+ this.monitor.draw();
1011
+ return 1;
1012
+ }
1013
+ this.monitor.setCommandStatus(`Validating ${allFiles.length} files...`);
1014
+ this.monitor.draw();
1015
+ const [validFiles, invalidFiles] = this.validateFiles(allFiles);
1016
+ if (invalidFiles.length > 0) {
1017
+ const validationErrorLines = [];
1018
+ const displayCount = Math.min(invalidFiles.length, 15);
1019
+ for (const error of invalidFiles.slice(0, displayCount)) {
1020
+ validationErrorLines.push(`${ANSI_RED}${CHR_CROSS}${ANSI_RESET} ${error}`);
1021
+ }
1022
+ if (invalidFiles.length > 15) {
1023
+ const remaining = invalidFiles.length - 15;
1024
+ validationErrorLines.push(`... and ${remaining} more validation errors`);
1025
+ }
1026
+ this.monitor.setCommandStatus('File validation completed with errors');
1027
+ this.monitor.addBox('File Validation Errors', validationErrorLines);
1028
+ this.monitor.draw();
1029
+ // Wait briefly to show errors
1030
+ await new Promise(resolve => setTimeout(resolve, 3000));
1031
+ }
1032
+ if (validFiles.length === 0) {
1033
+ this.monitor.setCommandStatus('File validation failed');
1034
+ this.monitor.addBox('Upload Error', ['No valid files found!']);
1035
+ this.monitor.draw();
1036
+ return 1;
1037
+ }
1038
+ // Connect and start
1039
+ this.monitor.setCommandStatus('Connecting to RocketRide server...');
1040
+ this.monitor.draw();
1041
+ await this.createAndConnectClient();
1042
+ if (shouldManagePipeline && pipelineConfig) {
1043
+ this.monitor.setCommandStatus('Starting pipeline...');
1044
+ this.monitor.draw();
1045
+ const result = await this.client.use({
1046
+ pipeline: pipelineConfig,
1047
+ threads: this.args.threads,
1048
+ token: 'UPLOAD_TASK',
1049
+ args: this.args.pipeline_args || []
1050
+ });
1051
+ taskToken = result.token;
1052
+ }
1053
+ // Start upload
1054
+ this.monitor.setTotalFiles(validFiles.length);
1055
+ this.monitor.draw();
1056
+ const startTime = Date.now();
1057
+ // Convert file paths to File objects for sendFiles
1058
+ const fileObjects = validFiles.map(filePath => {
1059
+ const stats = fs.statSync(filePath);
1060
+ const content = fs.readFileSync(filePath);
1061
+ return {
1062
+ file: new File([content], path.basename(filePath), {
1063
+ type: 'application/octet-stream',
1064
+ lastModified: stats.mtimeMs
1065
+ }),
1066
+ objinfo: {
1067
+ filepath: filePath,
1068
+ size: stats.size
1069
+ }
1070
+ };
1071
+ });
1072
+ // Upload files - progress events come through event subscription
1073
+ // Server handles concurrency automatically
1074
+ const results = await this.client.sendFiles(fileObjects, taskToken);
1075
+ const endTime = Date.now();
1076
+ // Analyze and show results
1077
+ this.analyzeUploadResults(results, startTime / 1000, endTime / 1000);
1078
+ // Cleanup pipeline if we created it
1079
+ if (shouldManagePipeline && taskToken) {
1080
+ try {
1081
+ this.monitor.setCommandStatus([
1082
+ 'Upload completed successfully',
1083
+ 'Cleaning up...',
1084
+ 'Terminating pipeline...'
1085
+ ]);
1086
+ this.monitor.draw();
1087
+ await this.client.terminate(taskToken);
1088
+ // Re-show results after cleanup
1089
+ this.analyzeUploadResults(results, startTime / 1000, endTime / 1000);
1090
+ }
1091
+ catch (error) {
1092
+ this.monitor.setCommandStatus('Upload completed with cleanup warning');
1093
+ this.monitor.addBox('Cleanup Warning', [`Failed to terminate pipeline: ${error}`]);
1094
+ this.monitor.draw();
1095
+ }
1096
+ }
1097
+ return 0;
1098
+ }
1099
+ catch (error) {
1100
+ if (!this.monitor) {
1101
+ this.monitor = new UploadProgressMonitor(this);
1102
+ }
1103
+ if (error instanceof Error && (error.message.includes('not found') || error.message.includes('Invalid'))) {
1104
+ this.monitor.setCommandStatus('Configuration error occurred');
1105
+ this.monitor.addBox('Configuration Error', [error.message]);
1106
+ }
1107
+ else {
1108
+ this.monitor.setCommandStatus('Upload operation failed');
1109
+ this.monitor.addBox('Upload Error', [String(error)]);
1110
+ }
1111
+ this.monitor.draw();
1112
+ return 1;
1113
+ }
1114
+ finally {
1115
+ await this.cleanupClient();
1116
+ }
1117
+ }
1118
+ findFiles(patterns) {
1119
+ const files = [];
1120
+ for (const pattern of patterns) {
1121
+ const fullPath = path.resolve(pattern);
1122
+ try {
1123
+ const stat = fs.statSync(fullPath);
1124
+ if (stat.isFile()) {
1125
+ files.push(fullPath);
1126
+ }
1127
+ else if (stat.isDirectory()) {
1128
+ const dirFiles = glob.sync(path.join(fullPath, '**/*'), { nodir: true });
1129
+ files.push(...dirFiles.map(f => path.resolve(f)));
1130
+ }
1131
+ }
1132
+ catch {
1133
+ // Try glob pattern
1134
+ const matches = glob.sync(pattern, { nodir: true });
1135
+ files.push(...matches.map(f => path.resolve(f)));
1136
+ }
1137
+ }
1138
+ // Remove duplicates
1139
+ return [...new Set(files)];
1140
+ }
1141
+ validateFiles(filesList) {
1142
+ const validFiles = [];
1143
+ const invalidFiles = [];
1144
+ for (const filepath of filesList) {
1145
+ try {
1146
+ if (fs.existsSync(filepath) && fs.statSync(filepath).isFile()) {
1147
+ // Try to read a byte to check accessibility
1148
+ const fd = fs.openSync(filepath, 'r');
1149
+ fs.closeSync(fd);
1150
+ validFiles.push(filepath);
1151
+ }
1152
+ else {
1153
+ invalidFiles.push(`File not found: ${path.basename(filepath)}`);
1154
+ }
1155
+ }
1156
+ catch (error) {
1157
+ invalidFiles.push(`Cannot read ${path.basename(filepath)}: ${error}`);
1158
+ }
1159
+ }
1160
+ return [validFiles, invalidFiles];
1161
+ }
1162
+ analyzeUploadResults(results, startTime, endTime) {
1163
+ if (!this.monitor)
1164
+ return;
1165
+ this.monitor.clear();
1166
+ const successfulFiles = [];
1167
+ const failedFiles = [];
1168
+ for (const result of results) {
1169
+ const filename = path.basename(result.filepath || '');
1170
+ if (result.action === 'complete') {
1171
+ this.uploadStats.successful_uploads++;
1172
+ this.uploadStats.total_bytes += result.file_size || 0;
1173
+ this.uploadStats.upload_times.push(result.upload_time || 0);
1174
+ successfulFiles.push({
1175
+ name: filename,
1176
+ size: result.file_size || 0,
1177
+ time: result.upload_time || 0
1178
+ });
1179
+ }
1180
+ else {
1181
+ failedFiles.push({
1182
+ name: filename,
1183
+ error: result.error || 'Unknown error'
1184
+ });
1185
+ this.uploadStats.failed_uploads++;
1186
+ }
1187
+ this.uploadStats.files_processed++;
1188
+ }
1189
+ // Create summary
1190
+ const successful = this.uploadStats.successful_uploads;
1191
+ const failed = this.uploadStats.failed_uploads;
1192
+ const totalBytes = this.uploadStats.total_bytes;
1193
+ const summaryLines = [
1194
+ `Total files processed: ${successful + failed}`,
1195
+ `Successful uploads: ${ANSI_GREEN}${successful}${ANSI_RESET}`
1196
+ ];
1197
+ if (failed > 0) {
1198
+ summaryLines.push(`Failed uploads: ${ANSI_RED}${failed}${ANSI_RESET}`);
1199
+ }
1200
+ summaryLines.push(`Total data uploaded: ${this.monitor.formatSize(totalBytes)}`);
1201
+ if (startTime && endTime && endTime > startTime) {
1202
+ const elapsedSeconds = endTime - startTime;
1203
+ let elapsedStr;
1204
+ if (elapsedSeconds < 60) {
1205
+ elapsedStr = `${elapsedSeconds.toFixed(1)} seconds`;
1206
+ }
1207
+ else if (elapsedSeconds < 3600) {
1208
+ const minutes = Math.floor(elapsedSeconds / 60);
1209
+ const seconds = elapsedSeconds % 60;
1210
+ elapsedStr = `${minutes}m ${seconds.toFixed(1)}s`;
1211
+ }
1212
+ else {
1213
+ const hours = Math.floor(elapsedSeconds / 3600);
1214
+ const minutes = Math.floor((elapsedSeconds % 3600) / 60);
1215
+ const seconds = elapsedSeconds % 60;
1216
+ elapsedStr = `${hours}h ${minutes}m ${seconds.toFixed(1)}s`;
1217
+ }
1218
+ summaryLines.push(`Total elapsed time: ${elapsedStr}`);
1219
+ if (totalBytes > 0 && elapsedSeconds > 0) {
1220
+ const throughputBps = totalBytes / elapsedSeconds;
1221
+ const throughputStr = this.monitor.formatSize(Math.floor(throughputBps));
1222
+ summaryLines.push(`Average throughput: ${throughputStr}/s`);
1223
+ }
1224
+ }
1225
+ this.monitor.addBox('Upload Summary', summaryLines);
1226
+ // Show failed files if any
1227
+ if (failedFiles.length > 0) {
1228
+ const failureLines = [];
1229
+ for (const failedFile of failedFiles.slice(0, 10)) {
1230
+ const filename = failedFile.name.length > 25 ?
1231
+ `${failedFile.name.substring(0, 25)}...` : failedFile.name;
1232
+ const errorMsg = failedFile.error.length > 40 ?
1233
+ `${failedFile.error.substring(0, 40)}...` : failedFile.error;
1234
+ failureLines.push(`${ANSI_RED}${CHR_CROSS}${ANSI_RESET} ${filename} - ${errorMsg}`);
1235
+ }
1236
+ if (failedFiles.length > 10) {
1237
+ failureLines.push(`... and ${failedFiles.length - 10} more failures`);
1238
+ }
1239
+ this.monitor.addBox(`Failed Uploads (${failedFiles.length})`, failureLines);
1240
+ }
1241
+ // Show successful files summary
1242
+ if (successfulFiles.length > 0) {
1243
+ const successLines = [];
1244
+ for (const successFile of successfulFiles.slice(-5)) {
1245
+ const truncatedName = successFile.name.length > 35 ?
1246
+ `${successFile.name.substring(0, 35)}...` : successFile.name;
1247
+ const sizeStr = this.monitor.formatSize(successFile.size);
1248
+ const timeStr = `${successFile.time.toFixed(1)}s`;
1249
+ successLines.push(`${ANSI_GREEN}${CHR_CHECK}${ANSI_RESET} ${truncatedName} (${sizeStr}, ${timeStr})`);
1250
+ }
1251
+ if (successfulFiles.length > 5) {
1252
+ successLines.push(`... and ${successfulFiles.length - 5} more successful uploads`);
1253
+ }
1254
+ this.monitor.addBox('Recent Successful Uploads', successLines);
1255
+ }
1256
+ this.monitor.setCommandStatus('Completed');
1257
+ this.monitor.draw();
1258
+ }
1259
+ async cmdStatus() {
1260
+ try {
1261
+ if (!this.args.token) {
1262
+ console.error('Error: --token is required for status command');
1263
+ return 1;
1264
+ }
1265
+ this.monitor = new StatusMonitor(this, this.args.token);
1266
+ const onConnected = async (_uri) => {
1267
+ this.connected = true;
1268
+ this.attempt = 0;
1269
+ this.monitor.displayStatus({});
1270
+ await this.sendMonitorCommand(true, this.args.token);
1271
+ };
1272
+ const onDisconnected = async (_reason, _hasError) => {
1273
+ this.connected = false;
1274
+ };
1275
+ // Auto-reconnection loop
1276
+ while (!this.isCancelled()) {
1277
+ if (!this.connected) {
1278
+ this.monitor.displayConnecting(this.uri, this.attempt);
1279
+ try {
1280
+ await this.createAndConnectClient(onConnected, onDisconnected);
1281
+ }
1282
+ catch {
1283
+ this.attempt++;
1284
+ await new Promise(resolve => setTimeout(resolve, 5000));
1285
+ continue;
1286
+ }
1287
+ }
1288
+ await new Promise(resolve => setTimeout(resolve, 1000));
1289
+ }
1290
+ return 0;
1291
+ }
1292
+ catch {
1293
+ return 0;
1294
+ }
1295
+ finally {
1296
+ // Make sure we unsubscribe
1297
+ if (this.client && this.connected) {
1298
+ try {
1299
+ await this.sendMonitorCommand(false, this.args.token);
1300
+ }
1301
+ catch {
1302
+ // Ignore unsubscribe errors
1303
+ }
1304
+ }
1305
+ await this.cleanupClient();
1306
+ }
1307
+ }
1308
+ async cmdStop() {
1309
+ try {
1310
+ if (!this.args.token) {
1311
+ console.error('Error: --token is required for stop command');
1312
+ return 1;
1313
+ }
1314
+ this.monitor = new GenericMonitor(this, 'RocketRide Task Management');
1315
+ this.monitor.setCommandStatus('Connecting to server...');
1316
+ this.monitor.draw();
1317
+ await this.createAndConnectClient();
1318
+ this.monitor.setCommandStatus(`Terminating task: ${this.args.token}`);
1319
+ this.monitor.draw();
1320
+ await this.client.terminate(this.args.token);
1321
+ const stopLines = [
1322
+ `Task ${this.args.token} terminated successfully`,
1323
+ '',
1324
+ 'The task has been stopped and resources cleaned up.'
1325
+ ];
1326
+ this.monitor.setCommandStatus(stopLines);
1327
+ this.monitor.draw();
1328
+ return 0;
1329
+ }
1330
+ catch (error) {
1331
+ if (!this.monitor) {
1332
+ this.monitor = new GenericMonitor(this, 'RocketRide Task Management');
1333
+ }
1334
+ this.monitor.setCommandStatus('Stop operation failed');
1335
+ this.monitor.addBox('Stop Error', [String(error)]);
1336
+ this.monitor.draw();
1337
+ return 1;
1338
+ }
1339
+ finally {
1340
+ await this.cleanupClient();
1341
+ }
1342
+ }
1343
+ async run() {
1344
+ const program = this.createProgram();
1345
+ // Parse command line arguments - commander will handle command routing
1346
+ try {
1347
+ await program.parseAsync(process.argv);
1348
+ return 0; // If we get here, a command was executed successfully
1349
+ }
1350
+ catch (error) {
1351
+ if (error instanceof Error && error.message.includes('interrupted')) {
1352
+ console.log('\nOperation interrupted by user');
1353
+ return 1;
1354
+ }
1355
+ else {
1356
+ console.error(`Error: ${error}`);
1357
+ return 1;
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+ exports.RocketRideCLI = RocketRideCLI;
1363
+ function formatError(e) {
1364
+ const stack = e.stack?.split('\n');
1365
+ if (stack && stack.length > 1) {
1366
+ const frame = stack[1];
1367
+ const match = frame.match(/at .+?\((.+):(\d+):\d+\)/) || frame.match(/at (.+):(\d+):\d+/);
1368
+ if (match) {
1369
+ const filename = path.basename(match[1]);
1370
+ const lineno = match[2];
1371
+ return `${e.constructor.name}: ${e.message} (in ${filename}:${lineno})`;
1372
+ }
1373
+ }
1374
+ return `${e.constructor.name}: ${e.message}`;
1375
+ }
1376
+ async function main() {
1377
+ try {
1378
+ const cli = new RocketRideCLI();
1379
+ const exitCode = await cli.run();
1380
+ process.exit(exitCode);
1381
+ }
1382
+ catch (error) {
1383
+ if (error instanceof Error && error.message.includes('interrupted')) {
1384
+ console.log('\n\nOperation interrupted by user');
1385
+ }
1386
+ else {
1387
+ console.log(`\nOperation failed: ${formatError(error)}`);
1388
+ process.exit(1);
1389
+ }
1390
+ }
1391
+ }
1392
+ // Entry point when script is run directly
1393
+ if (require.main === module) {
1394
+ main().catch(error => {
1395
+ console.error('Fatal error:', error);
1396
+ process.exit(1);
1397
+ });
1398
+ }
1399
+ //# sourceMappingURL=rocketride.js.map