@sentio/cli 3.4.2-rc.1 → 3.5.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -151941,6 +151941,7 @@ function createProcessorCommand() {
151941
151941
  processorCommand.addCommand(createProcessorPauseCommand());
151942
151942
  processorCommand.addCommand(createProcessorResumeCommand());
151943
151943
  processorCommand.addCommand(createProcessorStopCommand());
151944
+ processorCommand.addCommand(createProcessorLogsCommand());
151944
151945
  return processorCommand;
151945
151946
  }
151946
151947
  function createProcessorStatusCommand() {
@@ -152011,6 +152012,17 @@ function createProcessorStopCommand() {
152011
152012
  }
152012
152013
  });
152013
152014
  }
152015
+ function createProcessorLogsCommand() {
152016
+ return withOutputOptions3(
152017
+ withSharedProjectOptions3(withAuthOptions3(new Command("logs").description("View processor logs")))
152018
+ ).showHelpAfterError().argument("[processorId]", "ID of the processor (defaults to active processor)").option("--limit <count>", "Maximum number of log entries to fetch", parseInteger2, 100).option("-f, --follow", "Poll for new log entries continuously").option("--log-type <type>", "Filter by log type (e.g. execution, system)").option("--level <level>", "Filter by log level: DEBUG, INFO, WARNING, ERROR").option("--query <query>", "Free-text filter query").action(async (processorId, options, command) => {
152019
+ try {
152020
+ await runProcessorLogs(processorId, options);
152021
+ } catch (error) {
152022
+ handleProcessorCommandError(error, command);
152023
+ }
152024
+ });
152025
+ }
152014
152026
  async function runProcessorStatus(options) {
152015
152027
  const context = createApiContext(options);
152016
152028
  const project = await resolveProjectRef(options, context, { ownerSlug: true });
@@ -152164,6 +152176,101 @@ async function runProcessorStop(processorId, options) {
152164
152176
  ...response
152165
152177
  });
152166
152178
  }
152179
+ async function resolveProcessorId(processorId, options) {
152180
+ if (processorId) return processorId;
152181
+ const context = createApiContext(options);
152182
+ const project = await resolveProjectRef(options, context, { ownerSlug: true });
152183
+ const statusResponse = await import_api5.ProcessorService.getProcessorStatusV2({
152184
+ path: { owner: project.owner, slug: project.slug },
152185
+ query: { version: "ACTIVE" },
152186
+ headers: context.headers
152187
+ });
152188
+ const data = unwrapApiResult(statusResponse);
152189
+ const processors = Array.isArray(data.processors) ? data.processors : [];
152190
+ const activeProcessor = processors.find((p7) => asString3(p7.versionState) === "ACTIVE");
152191
+ if (!activeProcessor || !activeProcessor.processorId) {
152192
+ throw new CliError(
152193
+ `No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
152194
+ );
152195
+ }
152196
+ return asString3(activeProcessor.processorId);
152197
+ }
152198
+ async function runProcessorLogs(processorId, options) {
152199
+ const resolvedProcessorId = await resolveProcessorId(processorId, options);
152200
+ const context = createApiContext(options);
152201
+ if (options.follow && !options.json && !options.yaml) {
152202
+ await followProcessorLogs(resolvedProcessorId, context, options);
152203
+ return;
152204
+ }
152205
+ const response = await postApiJson(
152206
+ `/api/v1/processors/${resolvedProcessorId}/logs`,
152207
+ context,
152208
+ buildLogsRequestBody(resolvedProcessorId, options)
152209
+ );
152210
+ printOutput3(options, response);
152211
+ }
152212
+ async function followProcessorLogs(processorId, context, options) {
152213
+ let until;
152214
+ let running = true;
152215
+ const seenIds = /* @__PURE__ */ new Set();
152216
+ process19.on("SIGINT", () => {
152217
+ running = false;
152218
+ });
152219
+ while (running) {
152220
+ try {
152221
+ const body = { ...buildLogsRequestBody(processorId, options), until };
152222
+ const response = await postApiJson(`/api/v1/processors/${processorId}/logs`, context, body);
152223
+ const entries2 = Array.isArray(response.logs) ? response.logs : [];
152224
+ for (const entry of entries2) {
152225
+ const e10 = entry;
152226
+ const id = e10.id ?? "";
152227
+ if (id && seenIds.has(id)) continue;
152228
+ if (id) seenIds.add(id);
152229
+ process19.stdout.write(formatLogEntry(e10) + "\n");
152230
+ }
152231
+ if (response.until) {
152232
+ until = response.until;
152233
+ }
152234
+ } catch {
152235
+ }
152236
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
152237
+ }
152238
+ }
152239
+ function buildLogsRequestBody(processorId, options) {
152240
+ const body = { processorId, limit: options.limit };
152241
+ if (options.logType) {
152242
+ body.logTypeFilters = [options.logType];
152243
+ }
152244
+ if (options.level || options.query) {
152245
+ const parts = [];
152246
+ if (options.level) parts.push(options.level.toUpperCase());
152247
+ if (options.query) parts.push(options.query);
152248
+ body.query = parts.join(" ");
152249
+ }
152250
+ return body;
152251
+ }
152252
+ function formatLogEntry(entry) {
152253
+ const formattedTime = entry.timestamp ? source_default.gray(entry.timestamp.replace("T", " ").replace("Z", "")) : "";
152254
+ const level = (entry.level ?? "INFO").toUpperCase();
152255
+ const logType = entry.logType ? source_default.gray(`[${entry.logType}]`) : "";
152256
+ const coloredLevel = colorSeverity(level);
152257
+ const message = entry.message ?? "";
152258
+ const chain4 = entry.chainId ? source_default.gray(`(chain=${entry.chainId})`) : "";
152259
+ return [formattedTime, coloredLevel, logType, message, chain4].filter(Boolean).join(" ");
152260
+ }
152261
+ function colorSeverity(severity) {
152262
+ switch (severity) {
152263
+ case "ERROR":
152264
+ return source_default.red(`[${severity}]`);
152265
+ case "WARNING":
152266
+ case "WARN":
152267
+ return source_default.yellow(`[${severity}]`);
152268
+ case "DEBUG":
152269
+ return source_default.gray(`[${severity}]`);
152270
+ default:
152271
+ return source_default.cyan(`[${severity}]`);
152272
+ }
152273
+ }
152167
152274
  function withAuthOptions3(command) {
152168
152275
  return command.option("--host <host>", "Override Sentio host").option("--api-key <key>", "Use an explicit API key instead of saved credentials").option("--token <token>", "Use an explicit bearer token instead of saved credentials");
152169
152276
  }
@@ -152207,7 +152314,8 @@ function formatOutput2(data) {
152207
152314
  lines.push(`${group.versionState} (${group.processors.length})`);
152208
152315
  for (const processor of group.processors) {
152209
152316
  const version2 = asNumber(processor.version);
152210
- const statusState = asString3(processor.processorStatus?.state) ?? "UNKNOWN";
152317
+ const processorStatus = processor.processorStatus;
152318
+ const statusState = asString3(processorStatus?.state) ?? "UNKNOWN";
152211
152319
  const uploadedAt = asString3(processor.uploadedAt);
152212
152320
  lines.push(`- v${version2 ?? "?"} status=${statusState}${uploadedAt ? ` uploaded=${uploadedAt}` : ""}`);
152213
152321
  if (asString3(processor.processorId)) {
@@ -152217,9 +152325,17 @@ function formatOutput2(data) {
152217
152325
  for (const stateEntry of states.slice(0, 5)) {
152218
152326
  const state = stateEntry;
152219
152327
  const chainId = asString3(state.chainId) ?? "?";
152220
- const chainState = asString3(state.status?.state) ?? "UNKNOWN";
152328
+ const stateStatus = state.status;
152329
+ const chainState = asString3(stateStatus?.state) ?? "UNKNOWN";
152221
152330
  const block = asString3(state.processedBlockNumber) ?? "?";
152222
152331
  lines.push(` chain ${chainId}: ${chainState} block=${block}`);
152332
+ const errorRecord = stateStatus?.errorRecord;
152333
+ const chainError = asString3(errorRecord?.message);
152334
+ if (chainError) {
152335
+ const createdAt = asString3(errorRecord?.createdAt);
152336
+ const prefix = createdAt ? `[${createdAt}] ` : "";
152337
+ lines.push(` error: ${prefix}${chainError}`);
152338
+ }
152223
152339
  }
152224
152340
  if (states.length > 5) {
152225
152341
  lines.push(` ... ${states.length - 5} more chains`);
@@ -152248,6 +152364,14 @@ function formatOutput2(data) {
152248
152364
  const objectData = data;
152249
152365
  return `Processor ${asString3(objectData.processorId)} successfully ${asString3(objectData.action)}.`;
152250
152366
  }
152367
+ if (data && typeof data === "object" && "logs" in data) {
152368
+ const logsData = data;
152369
+ const entries2 = Array.isArray(logsData.logs) ? logsData.logs : [];
152370
+ if (entries2.length === 0) {
152371
+ return "No logs found.";
152372
+ }
152373
+ return entries2.map((entry) => formatLogEntry(entry)).join("\n");
152374
+ }
152251
152375
  return JSON.stringify(data, null, 2);
152252
152376
  }
152253
152377
  function normalizeVersionSelector(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/cli",
3
- "version": "3.4.2-rc.1",
3
+ "version": "3.5.0-rc.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,5 +1,6 @@
1
1
  import { ProcessorExtService, ProcessorService } from '@sentio/api'
2
2
  import { Command, InvalidArgumentError } from '@commander-js/extra-typings'
3
+ import chalk from 'chalk'
3
4
  import process from 'process'
4
5
  import yaml from 'yaml'
5
6
  import {
@@ -34,6 +35,14 @@ interface ProcessorSourceOptions extends ProcessorOptions {
34
35
  path?: string
35
36
  }
36
37
 
38
+ interface ProcessorLogsOptions extends ProcessorOptions {
39
+ limit?: number
40
+ follow?: boolean
41
+ logType?: string
42
+ level?: string
43
+ query?: string
44
+ }
45
+
37
46
  export function createProcessorCommand() {
38
47
  const processorCommand = new Command('processor').description('Manage Sentio processor versions')
39
48
  processorCommand.addCommand(createProcessorStatusCommand())
@@ -42,6 +51,7 @@ export function createProcessorCommand() {
42
51
  processorCommand.addCommand(createProcessorPauseCommand())
43
52
  processorCommand.addCommand(createProcessorResumeCommand())
44
53
  processorCommand.addCommand(createProcessorStopCommand())
54
+ processorCommand.addCommand(createProcessorLogsCommand())
45
55
  return processorCommand
46
56
  }
47
57
 
@@ -142,6 +152,26 @@ function createProcessorStopCommand() {
142
152
  })
143
153
  }
144
154
 
155
+ function createProcessorLogsCommand() {
156
+ return withOutputOptions(
157
+ withSharedProjectOptions(withAuthOptions(new Command('logs').description('View processor logs')))
158
+ )
159
+ .showHelpAfterError()
160
+ .argument('[processorId]', 'ID of the processor (defaults to active processor)')
161
+ .option('--limit <count>', 'Maximum number of log entries to fetch', parseInteger, 100)
162
+ .option('-f, --follow', 'Poll for new log entries continuously')
163
+ .option('--log-type <type>', 'Filter by log type (e.g. execution, system)')
164
+ .option('--level <level>', 'Filter by log level: DEBUG, INFO, WARNING, ERROR')
165
+ .option('--query <query>', 'Free-text filter query')
166
+ .action(async (processorId, options, command) => {
167
+ try {
168
+ await runProcessorLogs(processorId, options)
169
+ } catch (error) {
170
+ handleProcessorCommandError(error, command)
171
+ }
172
+ })
173
+ }
174
+
145
175
  async function runProcessorStatus(options: ProcessorStatusOptions) {
146
176
  const context = createApiContext(options)
147
177
  const project = await resolveProjectRef(options, context, { ownerSlug: true })
@@ -319,6 +349,134 @@ async function runProcessorStop(processorId: string | undefined, options: Proces
319
349
  })
320
350
  }
321
351
 
352
+ async function resolveProcessorId(processorId: string | undefined, options: ProcessorOptions): Promise<string> {
353
+ if (processorId) return processorId
354
+ const context = createApiContext(options)
355
+ const project = await resolveProjectRef(options, context, { ownerSlug: true })
356
+ const statusResponse = await ProcessorService.getProcessorStatusV2({
357
+ path: { owner: project.owner, slug: project.slug },
358
+ query: { version: 'ACTIVE' },
359
+ headers: context.headers
360
+ })
361
+ const data = unwrapApiResult(statusResponse)
362
+ const processors = Array.isArray(data.processors) ? data.processors : []
363
+ const activeProcessor = processors.find((p) => asString(p.versionState) === 'ACTIVE')
364
+ if (!activeProcessor || !activeProcessor.processorId) {
365
+ throw new CliError(
366
+ `No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
367
+ )
368
+ }
369
+ return asString(activeProcessor.processorId)!
370
+ }
371
+
372
+ async function runProcessorLogs(processorId: string | undefined, options: ProcessorLogsOptions) {
373
+ const resolvedProcessorId = await resolveProcessorId(processorId, options)
374
+ const context = createApiContext(options)
375
+
376
+ if (options.follow && !options.json && !options.yaml) {
377
+ await followProcessorLogs(resolvedProcessorId, context, options)
378
+ return
379
+ }
380
+
381
+ const response = await postApiJson<ProcessorLogsResponse>(
382
+ `/api/v1/processors/${resolvedProcessorId}/logs`,
383
+ context,
384
+ buildLogsRequestBody(resolvedProcessorId, options)
385
+ )
386
+ printOutput(options, response)
387
+ }
388
+
389
+ async function followProcessorLogs(
390
+ processorId: string,
391
+ context: ReturnType<typeof createApiContext>,
392
+ options: ProcessorLogsOptions
393
+ ) {
394
+ let until: unknown[] | undefined
395
+ let running = true
396
+ const seenIds = new Set<string>()
397
+
398
+ process.on('SIGINT', () => {
399
+ running = false
400
+ })
401
+
402
+ while (running) {
403
+ try {
404
+ const body = { ...buildLogsRequestBody(processorId, options), until }
405
+ const response = await postApiJson<ProcessorLogsResponse>(`/api/v1/processors/${processorId}/logs`, context, body)
406
+ const entries = Array.isArray(response.logs) ? response.logs : []
407
+ for (const entry of entries) {
408
+ const e = entry as ProcessorLog
409
+ const id = e.id ?? ''
410
+ if (id && seenIds.has(id)) continue
411
+ if (id) seenIds.add(id)
412
+ process.stdout.write(formatLogEntry(e) + '\n')
413
+ }
414
+ if (response.until) {
415
+ until = response.until
416
+ }
417
+ } catch {
418
+ // Ignore transient fetch errors during follow mode
419
+ }
420
+ await new Promise((resolve) => setTimeout(resolve, 2000))
421
+ }
422
+ }
423
+
424
+ function buildLogsRequestBody(processorId: string, options: ProcessorLogsOptions): Record<string, unknown> {
425
+ const body: Record<string, unknown> = { processorId, limit: options.limit }
426
+ if (options.logType) {
427
+ body.logTypeFilters = [options.logType]
428
+ }
429
+ if (options.level || options.query) {
430
+ const parts: string[] = []
431
+ if (options.level) parts.push(options.level.toUpperCase())
432
+ if (options.query) parts.push(options.query)
433
+ body.query = parts.join(' ')
434
+ }
435
+ return body
436
+ }
437
+
438
+ interface ProcessorLog {
439
+ id?: string
440
+ message?: string
441
+ timestamp?: string
442
+ attributes?: Record<string, unknown>
443
+ logType?: string
444
+ level?: string
445
+ highlightedMessage?: string
446
+ chainId?: string
447
+ }
448
+
449
+ interface ProcessorLogsResponse {
450
+ logs?: ProcessorLog[]
451
+ until?: unknown[]
452
+ total?: string
453
+ }
454
+
455
+ function formatLogEntry(entry: ProcessorLog): string {
456
+ const formattedTime = entry.timestamp ? chalk.gray(entry.timestamp.replace('T', ' ').replace('Z', '')) : ''
457
+ const level = (entry.level ?? 'INFO').toUpperCase()
458
+ const logType = entry.logType ? chalk.gray(`[${entry.logType}]`) : ''
459
+ const coloredLevel = colorSeverity(level)
460
+ const message = entry.message ?? ''
461
+ const chain = entry.chainId ? chalk.gray(`(chain=${entry.chainId})`) : ''
462
+
463
+ return [formattedTime, coloredLevel, logType, message, chain].filter(Boolean).join(' ')
464
+ }
465
+
466
+ function colorSeverity(severity: string): string {
467
+ switch (severity) {
468
+ case 'ERROR':
469
+ return chalk.red(`[${severity}]`)
470
+ case 'WARNING':
471
+ case 'WARN':
472
+ return chalk.yellow(`[${severity}]`)
473
+ case 'DEBUG':
474
+ return chalk.gray(`[${severity}]`)
475
+ default:
476
+ return chalk.cyan(`[${severity}]`)
477
+ }
478
+ }
479
+
322
480
  function withAuthOptions<T extends Command<any, any, any>>(command: T) {
323
481
  return command
324
482
  .option('--host <host>', 'Override Sentio host')
@@ -380,8 +538,8 @@ function formatOutput(data: unknown) {
380
538
  lines.push(`${group.versionState} (${group.processors.length})`)
381
539
  for (const processor of group.processors) {
382
540
  const version = asNumber(processor.version)
383
- const statusState =
384
- asString((processor.processorStatus as Record<string, unknown> | undefined)?.state) ?? 'UNKNOWN'
541
+ const processorStatus = processor.processorStatus as Record<string, unknown> | undefined
542
+ const statusState = asString(processorStatus?.state) ?? 'UNKNOWN'
385
543
  const uploadedAt = asString(processor.uploadedAt)
386
544
  lines.push(`- v${version ?? '?'} status=${statusState}${uploadedAt ? ` uploaded=${uploadedAt}` : ''}`)
387
545
  if (asString(processor.processorId)) {
@@ -391,9 +549,17 @@ function formatOutput(data: unknown) {
391
549
  for (const stateEntry of states.slice(0, 5)) {
392
550
  const state = stateEntry as Record<string, unknown>
393
551
  const chainId = asString(state.chainId) ?? '?'
394
- const chainState = asString((state.status as Record<string, unknown> | undefined)?.state) ?? 'UNKNOWN'
552
+ const stateStatus = state.status as Record<string, unknown> | undefined
553
+ const chainState = asString(stateStatus?.state) ?? 'UNKNOWN'
395
554
  const block = asString(state.processedBlockNumber) ?? '?'
396
555
  lines.push(` chain ${chainId}: ${chainState} block=${block}`)
556
+ const errorRecord = stateStatus?.errorRecord as Record<string, unknown> | undefined
557
+ const chainError = asString(errorRecord?.message)
558
+ if (chainError) {
559
+ const createdAt = asString(errorRecord?.createdAt)
560
+ const prefix = createdAt ? `[${createdAt}] ` : ''
561
+ lines.push(` error: ${prefix}${chainError}`)
562
+ }
397
563
  }
398
564
  if (states.length > 5) {
399
565
  lines.push(` ... ${states.length - 5} more chains`)
@@ -432,6 +598,15 @@ function formatOutput(data: unknown) {
432
598
  return `Processor ${asString(objectData.processorId)} successfully ${asString(objectData.action)}.`
433
599
  }
434
600
 
601
+ if (data && typeof data === 'object' && 'logs' in (data as Record<string, unknown>)) {
602
+ const logsData = data as ProcessorLogsResponse
603
+ const entries = Array.isArray(logsData.logs) ? logsData.logs : []
604
+ if (entries.length === 0) {
605
+ return 'No logs found.'
606
+ }
607
+ return entries.map((entry) => formatLogEntry(entry)).join('\n')
608
+ }
609
+
435
610
  return JSON.stringify(data, null, 2)
436
611
  }
437
612