@sentio/cli 3.4.2-rc.1 → 3.5.0-rc.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.
@@ -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
 
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ import { createAlertCommand } from './commands/alert.js'
18
18
  import { createPriceCommand } from './commands/price.js'
19
19
  import { createSimulationCommand } from './commands/simulation.js'
20
20
  import { createEndpointCommand } from './commands/endpoint.js'
21
+ import { createDashboardCommand } from './commands/dashboard.js'
21
22
  import { enableApiDebug } from './api.js'
22
23
  import { printVersions } from './utils.js'
23
24
 
@@ -50,5 +51,6 @@ program.addCommand(createAlertCommand())
50
51
  program.addCommand(createPriceCommand())
51
52
  program.addCommand(createSimulationCommand())
52
53
  program.addCommand(createEndpointCommand())
54
+ program.addCommand(createDashboardCommand())
53
55
 
54
56
  program.parse()