aparavi-client 1.0.2

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