@grepr/cli 1.1.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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/build/dist/commands/base-command.d.ts +13 -0
  4. package/build/dist/commands/base-command.d.ts.map +1 -0
  5. package/build/dist/commands/base-command.js +28 -0
  6. package/build/dist/commands/base-command.js.map +1 -0
  7. package/build/dist/commands/config-command.d.ts +19 -0
  8. package/build/dist/commands/config-command.d.ts.map +1 -0
  9. package/build/dist/commands/config-command.js +108 -0
  10. package/build/dist/commands/config-command.js.map +1 -0
  11. package/build/dist/commands/crud-command.d.ts +117 -0
  12. package/build/dist/commands/crud-command.d.ts.map +1 -0
  13. package/build/dist/commands/crud-command.js +264 -0
  14. package/build/dist/commands/crud-command.js.map +1 -0
  15. package/build/dist/commands/dataset-command.d.ts +45 -0
  16. package/build/dist/commands/dataset-command.d.ts.map +1 -0
  17. package/build/dist/commands/dataset-command.js +139 -0
  18. package/build/dist/commands/dataset-command.js.map +1 -0
  19. package/build/dist/commands/integration-command.d.ts +30 -0
  20. package/build/dist/commands/integration-command.d.ts.map +1 -0
  21. package/build/dist/commands/integration-command.js +80 -0
  22. package/build/dist/commands/integration-command.js.map +1 -0
  23. package/build/dist/commands/job-command.d.ts +92 -0
  24. package/build/dist/commands/job-command.d.ts.map +1 -0
  25. package/build/dist/commands/job-command.js +294 -0
  26. package/build/dist/commands/job-command.js.map +1 -0
  27. package/build/dist/commands/list-command.d.ts +69 -0
  28. package/build/dist/commands/list-command.d.ts.map +1 -0
  29. package/build/dist/commands/list-command.js +130 -0
  30. package/build/dist/commands/list-command.js.map +1 -0
  31. package/build/dist/commands/query-command.d.ts +20 -0
  32. package/build/dist/commands/query-command.d.ts.map +1 -0
  33. package/build/dist/commands/query-command.js +167 -0
  34. package/build/dist/commands/query-command.js.map +1 -0
  35. package/build/dist/grepr.d.ts +3 -0
  36. package/build/dist/grepr.d.ts.map +1 -0
  37. package/build/dist/grepr.js +155 -0
  38. package/build/dist/grepr.js.map +1 -0
  39. package/build/dist/lib/api-client-factory.d.ts +21 -0
  40. package/build/dist/lib/api-client-factory.d.ts.map +1 -0
  41. package/build/dist/lib/api-client-factory.js +23 -0
  42. package/build/dist/lib/api-client-factory.js.map +1 -0
  43. package/build/dist/lib/api-client.d.ts +2 -0
  44. package/build/dist/lib/api-client.d.ts.map +1 -0
  45. package/build/dist/lib/api-client.js +3 -0
  46. package/build/dist/lib/api-client.js.map +1 -0
  47. package/build/dist/lib/auth.d.ts +104 -0
  48. package/build/dist/lib/auth.d.ts.map +1 -0
  49. package/build/dist/lib/auth.js +312 -0
  50. package/build/dist/lib/auth.js.map +1 -0
  51. package/build/dist/lib/command-registry.d.ts +35 -0
  52. package/build/dist/lib/command-registry.d.ts.map +1 -0
  53. package/build/dist/lib/command-registry.js +33 -0
  54. package/build/dist/lib/command-registry.js.map +1 -0
  55. package/build/dist/lib/config.d.ts +40 -0
  56. package/build/dist/lib/config.d.ts.map +1 -0
  57. package/build/dist/lib/config.js +114 -0
  58. package/build/dist/lib/config.js.map +1 -0
  59. package/build/dist/lib/grepr-api-client.d.ts +216 -0
  60. package/build/dist/lib/grepr-api-client.d.ts.map +1 -0
  61. package/build/dist/lib/grepr-api-client.js +424 -0
  62. package/build/dist/lib/grepr-api-client.js.map +1 -0
  63. package/build/dist/lib/heartbeat.d.ts +17 -0
  64. package/build/dist/lib/heartbeat.d.ts.map +1 -0
  65. package/build/dist/lib/heartbeat.js +56 -0
  66. package/build/dist/lib/heartbeat.js.map +1 -0
  67. package/build/dist/lib/json-formatter.d.ts +135 -0
  68. package/build/dist/lib/json-formatter.d.ts.map +1 -0
  69. package/build/dist/lib/json-formatter.js +658 -0
  70. package/build/dist/lib/json-formatter.js.map +1 -0
  71. package/build/dist/lib/parser.d.ts +26 -0
  72. package/build/dist/lib/parser.d.ts.map +1 -0
  73. package/build/dist/lib/parser.js +95 -0
  74. package/build/dist/lib/parser.js.map +1 -0
  75. package/build/dist/lib/streaming-job-executor.d.ts +31 -0
  76. package/build/dist/lib/streaming-job-executor.d.ts.map +1 -0
  77. package/build/dist/lib/streaming-job-executor.js +281 -0
  78. package/build/dist/lib/streaming-job-executor.js.map +1 -0
  79. package/build/dist/lib/time-utils.d.ts +28 -0
  80. package/build/dist/lib/time-utils.d.ts.map +1 -0
  81. package/build/dist/lib/time-utils.js +87 -0
  82. package/build/dist/lib/time-utils.js.map +1 -0
  83. package/build/dist/openapi/openApiTypes.d.ts +10430 -0
  84. package/build/dist/openapi/openApiTypes.d.ts.map +1 -0
  85. package/build/dist/openapi/openApiTypes.js +571 -0
  86. package/build/dist/openapi/openApiTypes.js.map +1 -0
  87. package/build/dist/types.d.ts +168 -0
  88. package/build/dist/types.d.ts.map +1 -0
  89. package/build/dist/types.js +20 -0
  90. package/build/dist/types.js.map +1 -0
  91. package/package.json +68 -0
@@ -0,0 +1,658 @@
1
+ import chalk from 'chalk';
2
+ /**
3
+ * Generic formatter for any JSON objects (jobs, integrations, datasets, etc.)
4
+ * Replaces the LogEvent-specific OutputFormatter
5
+ */
6
+ export class JsonFormatter {
7
+ options;
8
+ tableData = [];
9
+ tableHeaders = [];
10
+ csvHeaders = [];
11
+ csvHeadersPrinted = false;
12
+ constructor(options) {
13
+ this.options = options;
14
+ this.reset();
15
+ }
16
+ /**
17
+ * Reset formatter state
18
+ */
19
+ reset() {
20
+ this.tableData = [];
21
+ this.tableHeaders = [];
22
+ this.csvHeaders = [];
23
+ this.csvHeadersPrinted = false;
24
+ }
25
+ /**
26
+ * Format a single object based on selected format
27
+ */
28
+ formatObject(data) {
29
+ switch (this.options.format) {
30
+ case 'table':
31
+ return this.addToTable(data);
32
+ case 'csv':
33
+ return this.addToCsv(data);
34
+ case 'pretty':
35
+ return this.formatPrettyJson(data);
36
+ case 'raw':
37
+ return JSON.stringify(data);
38
+ case 'compact':
39
+ return JSON.stringify(data, null, 0);
40
+ default:
41
+ return JSON.stringify(data, null, 2);
42
+ }
43
+ }
44
+ /**
45
+ * Format an array of objects
46
+ */
47
+ formatObjects(data) {
48
+ if (!data || data.length === 0) {
49
+ return 'No data to display';
50
+ }
51
+ this.reset();
52
+ switch (this.options.format) {
53
+ case 'table':
54
+ // Accumulate all data then render
55
+ data.forEach(obj => this.addToTable(obj));
56
+ return this.renderTable();
57
+ case 'csv':
58
+ // First pass: collect all headers from all objects
59
+ data.forEach(obj => {
60
+ const flattenedRow = this.flattenObject(obj);
61
+ const newKeys = Object.keys(flattenedRow);
62
+ if (this.csvHeaders.length === 0) {
63
+ this.csvHeaders = [...newKeys];
64
+ }
65
+ else {
66
+ const missingKeys = newKeys.filter(key => !this.csvHeaders.includes(key));
67
+ this.csvHeaders.push(...missingKeys);
68
+ }
69
+ });
70
+ // Second pass: generate CSV rows with consistent headers
71
+ const csvRows = [];
72
+ data.forEach(obj => {
73
+ const csvRow = this.addToCsv(obj);
74
+ if (csvRow) {
75
+ csvRows.push(csvRow);
76
+ }
77
+ });
78
+ return csvRows.join('\n');
79
+ case 'pretty':
80
+ return data.map(obj => this.formatPrettyJson(obj)).join('\n\n');
81
+ case 'raw':
82
+ return data.map(obj => JSON.stringify(obj)).join('\n');
83
+ case 'compact':
84
+ return data.map(obj => JSON.stringify(obj, null, 0)).join('\n');
85
+ default:
86
+ return JSON.stringify(data, null, 2);
87
+ }
88
+ }
89
+ /**
90
+ * Add data to table and return empty string (table will be rendered at the end)
91
+ */
92
+ addToTable(data) {
93
+ const flattenedRow = this.flattenObject(data);
94
+ this.tableData.push(flattenedRow);
95
+ // Update headers with any new keys
96
+ const newKeys = Object.keys(flattenedRow);
97
+ if (this.tableHeaders.length === 0) {
98
+ this.tableHeaders = [...newKeys];
99
+ }
100
+ else {
101
+ const missingKeys = newKeys.filter(key => !this.tableHeaders.includes(key));
102
+ this.tableHeaders.push(...missingKeys);
103
+ }
104
+ return ''; // Don't output anything yet, table will be rendered at the end
105
+ }
106
+ /**
107
+ * Add data to CSV and return CSV row string (with headers on first call)
108
+ */
109
+ addToCsv(data) {
110
+ const flattenedRow = this.flattenObject(data);
111
+ // Update headers with any new keys
112
+ const newKeys = Object.keys(flattenedRow);
113
+ if (this.csvHeaders.length === 0) {
114
+ this.csvHeaders = [...newKeys];
115
+ }
116
+ else {
117
+ const missingKeys = newKeys.filter(key => !this.csvHeaders.includes(key));
118
+ this.csvHeaders.push(...missingKeys);
119
+ }
120
+ let output = '';
121
+ // Print headers on first row
122
+ if (!this.csvHeadersPrinted) {
123
+ const orderedHeaders = this.orderHeaders(this.csvHeaders);
124
+ output += this.formatCsvRow(orderedHeaders) + '\n';
125
+ this.csvHeadersPrinted = true;
126
+ }
127
+ // Format and return the data row
128
+ const orderedHeaders = this.orderHeaders(this.csvHeaders);
129
+ const rowValues = orderedHeaders.map(header => flattenedRow[header] || '');
130
+ output += this.formatCsvRow(rowValues);
131
+ return output;
132
+ }
133
+ /**
134
+ * Flatten nested object into dot notation
135
+ */
136
+ flattenObject(obj, prefix = '', depth = 0) {
137
+ const maxDepth = this.options.maxDepth ?? 2;
138
+ const flattened = {};
139
+ Object.keys(obj).forEach(key => {
140
+ const value = obj[key];
141
+ const fullKey = prefix ? `${prefix}.${key}` : key;
142
+ if (value === null || value === undefined) {
143
+ flattened[fullKey] = '';
144
+ }
145
+ else if (typeof value === 'object' && !Array.isArray(value) && depth < maxDepth - 1) {
146
+ // Recursively flatten nested objects only if we haven't reached max depth
147
+ Object.assign(flattened, this.flattenObject(value, fullKey, depth + 1));
148
+ }
149
+ else {
150
+ // At max depth or not an object - format as cell value
151
+ flattened[fullKey] = this.formatCellValue(value, fullKey);
152
+ }
153
+ });
154
+ return flattened;
155
+ }
156
+ /**
157
+ * Format cell value - keep JSON as formatted JSON, primitives as strings
158
+ * Special handling for timestamp columns to show readable local time
159
+ */
160
+ formatCellValue(value, columnName = '') {
161
+ if (value === null || value === undefined) {
162
+ return '';
163
+ }
164
+ if (Array.isArray(value)) {
165
+ return JSON.stringify(value, null, 2);
166
+ }
167
+ if (typeof value === 'object') {
168
+ return JSON.stringify(value, null, 2);
169
+ }
170
+ // Check if this is a timestamp column and the value looks like a timestamp
171
+ if (this.options.showTimestamps && this.isTimestampColumn(columnName)) {
172
+ return this.formatTimestamp(value);
173
+ }
174
+ return String(value);
175
+ }
176
+ /**
177
+ * Check if a column name indicates it contains timestamps
178
+ */
179
+ isTimestampColumn(columnName) {
180
+ const timestampColumns = [
181
+ 'timestamp', 'eventTimestamp', 'receivedTimestamp',
182
+ 'createdAt', 'updatedAt', 'created_at', 'updated_at',
183
+ 'time', 'date', 'startTime', 'endTime'
184
+ ];
185
+ const lowerColumnName = columnName.toLowerCase();
186
+ // Use exact matches or specific patterns to avoid false positives like "timeout"
187
+ return timestampColumns.some(tsCol => lowerColumnName === tsCol.toLowerCase() ||
188
+ lowerColumnName === `${tsCol.toLowerCase()}stamp` ||
189
+ (lowerColumnName.endsWith('at') && lowerColumnName.length > 2) ||
190
+ (lowerColumnName.endsWith('time') && !lowerColumnName.includes('timeout')));
191
+ }
192
+ /**
193
+ * Format timestamp value to readable time string with timezone support
194
+ */
195
+ formatTimestamp(value) {
196
+ try {
197
+ const timestampMillis = this.parseTimestamp(value);
198
+ if (timestampMillis === null || isNaN(timestampMillis)) {
199
+ return String(value); // Return original if parsing fails
200
+ }
201
+ const date = new Date(timestampMillis);
202
+ // Use specified timezone or default to system locale
203
+ if (this.options.timezone && this.options.timezone !== 'system') {
204
+ return date.toLocaleString('en-US', {
205
+ timeZone: this.options.timezone,
206
+ year: 'numeric',
207
+ month: '2-digit',
208
+ day: '2-digit',
209
+ hour: '2-digit',
210
+ minute: '2-digit',
211
+ second: '2-digit',
212
+ hour12: true
213
+ });
214
+ }
215
+ else {
216
+ // Use system locale and timezone
217
+ return date.toLocaleString();
218
+ }
219
+ }
220
+ catch (error) {
221
+ return String(value); // Return original if formatting fails
222
+ }
223
+ }
224
+ /**
225
+ * Parse a timestamp value for sorting (returns milliseconds since epoch or null)
226
+ */
227
+ parseTimestamp(value) {
228
+ if (!value)
229
+ return null;
230
+ try {
231
+ const strValue = String(value);
232
+ if (/^\d+$/.test(strValue)) {
233
+ // Unix timestamp
234
+ const timestamp = parseInt(strValue);
235
+ // Handle both seconds (10 digits) and milliseconds (13 digits)
236
+ return strValue.length === 10 ? timestamp * 1000 : timestamp;
237
+ }
238
+ else {
239
+ // ISO string or other date format
240
+ const date = new Date(strValue);
241
+ return isNaN(date.getTime()) ? null : date.getTime();
242
+ }
243
+ }
244
+ catch (error) {
245
+ return null;
246
+ }
247
+ }
248
+ /**
249
+ * Order headers according to common column preferences
250
+ */
251
+ orderHeaders(headers) {
252
+ if (!headers || headers.length === 0) {
253
+ return headers;
254
+ }
255
+ const orderedHeaders = [];
256
+ // Define the desired order for common columns (works for jobs, integrations, log events)
257
+ const fixedOrder = [
258
+ 'id', 'name', 'type', 'state', 'status', 'version',
259
+ 'createdAt', 'updatedAt', 'eventTimestamp', 'receivedTimestamp',
260
+ 'severity', 'tags', 'organizationId', 'execution', 'processing'
261
+ ];
262
+ // Add fixed order columns if they exist (case-insensitive)
263
+ fixedOrder.forEach(column => {
264
+ const found = headers.find(header => header.toLowerCase() === column.toLowerCase());
265
+ if (found) {
266
+ orderedHeaders.push(found);
267
+ }
268
+ });
269
+ // Find all nested/attribute columns and sort them alphabetically
270
+ const nestedColumns = headers
271
+ .filter(header => header.includes('.'))
272
+ .filter(header => !orderedHeaders.includes(header))
273
+ .sort((a, b) => a.localeCompare(b));
274
+ orderedHeaders.push(...nestedColumns);
275
+ // Add description/message columns if they exist
276
+ const descriptionColumns = headers.filter(header => {
277
+ const lower = header.toLowerCase();
278
+ return (lower.includes('description') || lower.includes('message')) && !orderedHeaders.includes(header);
279
+ }).sort((a, b) => a.localeCompare(b));
280
+ orderedHeaders.push(...descriptionColumns);
281
+ // Add any remaining columns that weren't covered above, sorted alphabetically
282
+ const remainingColumns = headers
283
+ .filter(header => !orderedHeaders.includes(header))
284
+ .sort((a, b) => a.localeCompare(b));
285
+ orderedHeaders.push(...remainingColumns);
286
+ return orderedHeaders;
287
+ }
288
+ /**
289
+ * Render the accumulated table data
290
+ */
291
+ renderTable() {
292
+ if (!this.tableData.length) {
293
+ return 'No data to display';
294
+ }
295
+ // Sort the table data before rendering
296
+ const sortedData = this.sortData(this.tableData);
297
+ // Order the headers according to the specified column order
298
+ const orderedHeaders = this.orderHeaders(this.tableHeaders);
299
+ return this.formatTable(orderedHeaders, sortedData);
300
+ }
301
+ /**
302
+ * Sort data based on the sortBy option
303
+ */
304
+ sortData(data) {
305
+ if (!this.options.sortBy || data.length <= 1) {
306
+ return data;
307
+ }
308
+ const [sortColumn, sortOrder = 'asc'] = this.options.sortBy.split(':');
309
+ const isAscending = sortOrder.toLowerCase() === 'asc';
310
+ // Find the actual column name (case-insensitive)
311
+ const actualColumn = this.findActualColumnName(sortColumn || '');
312
+ if (!actualColumn) {
313
+ console.warn(`Warning: Sort column '${sortColumn}' not found in data`);
314
+ return data;
315
+ }
316
+ return [...data].sort((a, b) => {
317
+ const valueA = a[actualColumn];
318
+ const valueB = b[actualColumn];
319
+ // Handle null/undefined values
320
+ if (valueA == null && valueB == null)
321
+ return 0;
322
+ if (valueA == null)
323
+ return isAscending ? -1 : 1;
324
+ if (valueB == null)
325
+ return isAscending ? 1 : -1;
326
+ // Try to parse as timestamps first for timestamp columns
327
+ if (this.isTimestampColumn(actualColumn)) {
328
+ const timestampA = this.parseTimestamp(valueA);
329
+ const timestampB = this.parseTimestamp(valueB);
330
+ if (timestampA !== null && timestampB !== null) {
331
+ const result = timestampA - timestampB;
332
+ return isAscending ? result : -result;
333
+ }
334
+ }
335
+ // Try numeric comparison first
336
+ const numA = parseFloat(String(valueA));
337
+ const numB = parseFloat(String(valueB));
338
+ if (!isNaN(numA) && !isNaN(numB)) {
339
+ const result = numA - numB;
340
+ return isAscending ? result : -result;
341
+ }
342
+ // Fall back to string comparison
343
+ const strA = String(valueA).toLowerCase();
344
+ const strB = String(valueB).toLowerCase();
345
+ const result = strA.localeCompare(strB);
346
+ return isAscending ? result : -result;
347
+ });
348
+ }
349
+ /**
350
+ * Find the actual column name in the data (case-insensitive search)
351
+ */
352
+ findActualColumnName(searchColumn) {
353
+ if (!this.tableHeaders)
354
+ return null;
355
+ const lowerSearchColumn = searchColumn.toLowerCase();
356
+ // Exact match first
357
+ const exactMatch = this.tableHeaders.find(header => header === searchColumn);
358
+ if (exactMatch)
359
+ return exactMatch;
360
+ // Case-insensitive match
361
+ const caseInsensitiveMatch = this.tableHeaders.find(header => header.toLowerCase() === lowerSearchColumn);
362
+ if (caseInsensitiveMatch)
363
+ return caseInsensitiveMatch;
364
+ // Partial match for common variations
365
+ const partialMatch = this.tableHeaders.find(header => header.toLowerCase().includes(lowerSearchColumn) ||
366
+ lowerSearchColumn.includes(header.toLowerCase()));
367
+ return partialMatch || null;
368
+ }
369
+ /**
370
+ * Format a CSV row with proper escaping
371
+ */
372
+ formatCsvRow(values) {
373
+ return values.map(value => this.escapeCsvValue(String(value || ''))).join(',');
374
+ }
375
+ /**
376
+ * Escape a CSV value according to RFC 4180
377
+ */
378
+ escapeCsvValue(value) {
379
+ const stringValue = String(value);
380
+ // If the value contains comma, newline, double quote, or literal \n, wrap in quotes
381
+ if (stringValue.includes(',') || stringValue.includes('\n') || stringValue.includes('\r') || stringValue.includes('"') || stringValue.includes('\\n')) {
382
+ // Escape existing double quotes by doubling them
383
+ const escaped = stringValue.replace(/"/g, '""');
384
+ return `"${escaped}"`;
385
+ }
386
+ return stringValue;
387
+ }
388
+ /**
389
+ * Format data as a table
390
+ */
391
+ formatTable(headers, rows) {
392
+ if (!headers.length || !rows.length) {
393
+ return 'No data to display';
394
+ }
395
+ // Calculate column widths
396
+ const columnWidths = headers.map(header => {
397
+ const maxContentWidth = Math.max(header.length, ...rows.map(row => {
398
+ const cellValue = String(row[header] || '');
399
+ // For multi-line content, use the longest line length
400
+ const lines = cellValue.split('\n');
401
+ return Math.max(...lines.map(line => line.length));
402
+ }));
403
+ return Math.min(maxContentWidth, 80); // Max width of 80 chars per column
404
+ });
405
+ // Format header row
406
+ const headerRow = '| ' + headers.map((header, i) => this.wrapAndPad(header, columnWidths[i])).join(' | ') + ' |';
407
+ // Format data rows with proper wrapping
408
+ const formattedRows = [];
409
+ for (const row of rows) {
410
+ const cellLines = headers.map((header, i) => {
411
+ const cellValue = String(row[header] || '');
412
+ const colWidth = columnWidths[i];
413
+ if (colWidth === undefined) {
414
+ return [''];
415
+ }
416
+ return this.wrapText(cellValue, colWidth);
417
+ });
418
+ // Find the maximum number of lines needed for this row
419
+ const maxLines = Math.max(...cellLines.map(lines => lines.length));
420
+ // Create each line of the row
421
+ for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
422
+ const rowLine = '| ' + headers.map((_, i) => {
423
+ const lines = cellLines[i];
424
+ const lineContent = (lines && lineIndex < lines.length) ? lines[lineIndex] : '';
425
+ const colWidth = columnWidths[i];
426
+ if (colWidth === undefined) {
427
+ return '';
428
+ }
429
+ return (lineContent || '').padEnd(colWidth);
430
+ }).join(' | ') + ' |';
431
+ formattedRows.push(rowLine);
432
+ }
433
+ }
434
+ // Create separator line
435
+ const separator = '+-' + columnWidths.map(width => '-'.repeat(width)).join('-+-') + '-+';
436
+ // Combine all parts
437
+ const tableLines = [
438
+ separator,
439
+ headerRow,
440
+ separator,
441
+ ...formattedRows,
442
+ separator
443
+ ];
444
+ return tableLines.join('\n');
445
+ }
446
+ /**
447
+ * Wrap text to fit column width and pad with spaces
448
+ */
449
+ wrapAndPad(str, width) {
450
+ if (width === undefined) {
451
+ return str;
452
+ }
453
+ if (str.length > width) {
454
+ return str.substring(0, width - 3) + '...';
455
+ }
456
+ return str.padEnd(width);
457
+ }
458
+ /**
459
+ * Wrap text to multiple lines based on column width
460
+ */
461
+ wrapText(text, width) {
462
+ if (!text || width <= 0) {
463
+ return [''];
464
+ }
465
+ // Handle text that's already multi-line (like pretty-printed JSON)
466
+ const existingLines = text.split('\n');
467
+ const wrappedLines = [];
468
+ for (const line of existingLines) {
469
+ if (line.length <= width) {
470
+ wrappedLines.push(line);
471
+ }
472
+ else {
473
+ // Break long lines at word boundaries when possible
474
+ const words = line.split(' ');
475
+ let currentLine = '';
476
+ for (const word of words) {
477
+ if (word.length > width) {
478
+ // If a single word is longer than width, break it
479
+ if (currentLine) {
480
+ wrappedLines.push(currentLine.trim());
481
+ currentLine = '';
482
+ }
483
+ // Break the long word into chunks
484
+ for (let i = 0; i < word.length; i += width) {
485
+ wrappedLines.push(word.substring(i, i + width));
486
+ }
487
+ }
488
+ else if ((currentLine + ' ' + word).length <= width) {
489
+ currentLine += (currentLine ? ' ' : '') + word;
490
+ }
491
+ else {
492
+ if (currentLine) {
493
+ wrappedLines.push(currentLine.trim());
494
+ }
495
+ currentLine = word;
496
+ }
497
+ }
498
+ if (currentLine) {
499
+ wrappedLines.push(currentLine.trim());
500
+ }
501
+ }
502
+ }
503
+ return wrappedLines.length > 0 ? wrappedLines : [''];
504
+ }
505
+ /**
506
+ * Format JSON with pretty printing and syntax highlighting
507
+ */
508
+ formatPrettyJson(data) {
509
+ if (!this.options.colorize) {
510
+ return JSON.stringify(data, null, 2);
511
+ }
512
+ const json = JSON.stringify(data, null, 2);
513
+ // Simple syntax highlighting
514
+ return json
515
+ .replace(/"([^"]+)":/g, chalk.blue('"$1"') + ':') // Keys
516
+ .replace(/: "([^"]+)"/g, ': ' + chalk.green('"$1"')) // String values
517
+ .replace(/: (\d+\.?\d*)/g, ': ' + chalk.yellow('$1')) // Numbers
518
+ .replace(/: (true|false)/g, ': ' + chalk.magenta('$1')) // Booleans
519
+ .replace(/: null/g, ': ' + chalk.gray('null')); // Null values
520
+ }
521
+ /**
522
+ * Format log data based on selected format (alias for formatObject for backwards compatibility)
523
+ */
524
+ formatLogData(data) {
525
+ return this.formatObject(data);
526
+ }
527
+ /**
528
+ * Format job state message
529
+ */
530
+ formatJobState(state) {
531
+ if (!this.options.showTimestamps) {
532
+ return '';
533
+ }
534
+ const timestamp = this.options.showTimestamps ?
535
+ `${new Date().toISOString()} ` : '';
536
+ let stateColor;
537
+ let message;
538
+ switch (state) {
539
+ case 'HEARTBEAT':
540
+ stateColor = chalk.yellow;
541
+ message = 'Heartbeat received from server';
542
+ break;
543
+ case 'RUNNING':
544
+ stateColor = chalk.green;
545
+ message = 'Job is running, processing data...';
546
+ break;
547
+ case 'FINISHED':
548
+ stateColor = chalk.green.bold;
549
+ message = 'Job completed successfully';
550
+ break;
551
+ case 'FAILED':
552
+ stateColor = chalk.red.bold;
553
+ message = 'Job failed';
554
+ break;
555
+ case 'CANCELLED':
556
+ stateColor = chalk.yellow.bold;
557
+ message = 'Job was cancelled';
558
+ break;
559
+ case 'TIMED_OUT':
560
+ stateColor = chalk.red;
561
+ message = 'Job timed out';
562
+ break;
563
+ case 'SCANNED_MAX':
564
+ stateColor = chalk.yellow;
565
+ message = 'Job reached maximum scan limit';
566
+ break;
567
+ default:
568
+ stateColor = chalk.gray;
569
+ message = `Unknown job state: ${state}`;
570
+ }
571
+ return this.options.colorize ?
572
+ `${timestamp}${stateColor(`[${state}]`)} ${message}` :
573
+ `${timestamp}[${state}] ${message}`;
574
+ }
575
+ /**
576
+ * Format heartbeat status
577
+ */
578
+ formatHeartbeatStatus(action, details = '') {
579
+ if (!this.options.colorize) {
580
+ return `[HEARTBEAT] ${action} ${details}`.trim();
581
+ }
582
+ const timestamp = this.options.showTimestamps ?
583
+ `${chalk.gray(new Date().toISOString())} ` : '';
584
+ switch (action) {
585
+ case 'SENT':
586
+ return `${timestamp}${chalk.cyan('[♥]')} Heartbeat sent ${details}`;
587
+ case 'FAILED':
588
+ return `${timestamp}${chalk.red('[✗]')} Heartbeat failed ${details}`;
589
+ case 'RETRY':
590
+ return `${timestamp}${chalk.yellow('[↻]')} Retrying heartbeat ${details}`;
591
+ default:
592
+ return `${timestamp}${chalk.blue('[♥]')} ${action} ${details}`;
593
+ }
594
+ }
595
+ /**
596
+ * Format connection status
597
+ */
598
+ formatConnectionStatus(status, details = '') {
599
+ if (!this.options.colorize) {
600
+ return `[CONNECTION] ${status} ${details}`.trim();
601
+ }
602
+ const timestamp = this.options.showTimestamps ?
603
+ `${chalk.gray(new Date().toISOString())} ` : '';
604
+ switch (status) {
605
+ case 'CONNECTING':
606
+ return `${timestamp}${chalk.blue('[⚡]')} Connecting to Grepr API ${details}`;
607
+ case 'CONNECTED':
608
+ return `${timestamp}${chalk.green('[✓]')} Connected successfully ${details}`;
609
+ case 'DISCONNECTED':
610
+ return `${timestamp}${chalk.yellow('[⚡]')} Disconnected ${details}`;
611
+ case 'ERROR':
612
+ return `${timestamp}${chalk.red('[✗]')} Connection error ${details}`;
613
+ default:
614
+ return `${timestamp}${chalk.blue('[⚡]')} ${status} ${details}`;
615
+ }
616
+ }
617
+ /**
618
+ * Format error messages
619
+ */
620
+ formatError(error, context = '') {
621
+ const timestamp = this.options.showTimestamps ?
622
+ `[${new Date().toISOString()}] ` : '';
623
+ const prefix = this.options.colorize ?
624
+ chalk.red.bold('[ERROR]') : '[ERROR]';
625
+ const contextStr = context ? ` (${context})` : '';
626
+ return `${timestamp}${prefix}${contextStr} ${error.message || error}`;
627
+ }
628
+ /**
629
+ * Format summary statistics
630
+ */
631
+ formatSummary(stats) {
632
+ const lines = [];
633
+ if (this.options.colorize) {
634
+ lines.push(chalk.bold.underline('\nSummary:'));
635
+ lines.push(`${chalk.cyan('Records processed:')} ${stats.recordsProcessed || 0}`);
636
+ lines.push(`${chalk.cyan('Duration:')} ${stats.duration || 'Unknown'}`);
637
+ if (stats.heartbeatsSent > 0) {
638
+ lines.push(`${chalk.cyan('Heartbeats sent:')} ${stats.heartbeatsSent}`);
639
+ }
640
+ if (stats.errors > 0) {
641
+ lines.push(`${chalk.red('Errors:')} ${stats.errors}`);
642
+ }
643
+ }
644
+ else {
645
+ lines.push('\nSummary:');
646
+ lines.push(`Records processed: ${stats.recordsProcessed || 0}`);
647
+ lines.push(`Duration: ${stats.duration || 'Unknown'}`);
648
+ if (stats.heartbeatsSent > 0) {
649
+ lines.push(`Heartbeats sent: ${stats.heartbeatsSent}`);
650
+ }
651
+ if (stats.errors > 0) {
652
+ lines.push(`Errors: ${stats.errors}`);
653
+ }
654
+ }
655
+ return lines.join('\n');
656
+ }
657
+ }
658
+ //# sourceMappingURL=json-formatter.js.map