@navios/commander-tui 1.0.0 → 1.2.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 (45) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +27 -0
  3. package/dist/tsconfig.tsbuildinfo +1 -1
  4. package/lib/index.cjs +338 -22
  5. package/lib/index.cjs.map +1 -1
  6. package/lib/index.d.cts +161 -118
  7. package/lib/index.d.cts.map +1 -1
  8. package/lib/index.d.mts +176 -133
  9. package/lib/index.d.mts.map +1 -1
  10. package/lib/index.mjs +333 -24
  11. package/lib/index.mjs.map +1 -1
  12. package/lib/{screen_manager_bridge-DN2J6_k1.mjs → screen_manager_bridge-Bk1i53h5.mjs} +1 -1
  13. package/lib/{screen_manager_bridge-Dfg4QUrl.mjs → screen_manager_bridge-BwPVr0bX.mjs} +17 -3
  14. package/lib/screen_manager_bridge-BwPVr0bX.mjs.map +1 -0
  15. package/lib/{screen_manager_bridge-BpDgVu3e.cjs → screen_manager_bridge-CWxLO0CK.cjs} +17 -3
  16. package/lib/screen_manager_bridge-CWxLO0CK.cjs.map +1 -0
  17. package/lib/{screen_manager_bridge-CkV7637i.cjs → screen_manager_bridge-DXc57iXW.cjs} +1 -1
  18. package/package.json +1 -1
  19. package/src/__tests__/components/__snapshots__/filter_bar.spec.tsx.snap +2604 -0
  20. package/src/__tests__/components/__snapshots__/loading_message.spec.tsx.snap +1621 -0
  21. package/src/__tests__/components/__snapshots__/log_message.spec.tsx.snap +3757 -0
  22. package/src/__tests__/components/__snapshots__/progress_message.spec.tsx.snap +2042 -0
  23. package/src/__tests__/components/__snapshots__/prompt_renderer.spec.tsx.snap +3652 -0
  24. package/src/__tests__/components/__snapshots__/sidebar.spec.tsx.snap +3426 -0
  25. package/src/__tests__/components/sidebar.spec.tsx +17 -0
  26. package/src/__tests__/services/logger.spec.ts +5 -3
  27. package/src/factories/index.ts +1 -0
  28. package/src/factories/isomorphic-logger.factory.ts +23 -0
  29. package/src/index.ts +5 -0
  30. package/src/interfaces/index.ts +1 -0
  31. package/src/interfaces/isomorphic-logger.ts +9 -0
  32. package/src/overrides/console.logger.override.ts +8 -1
  33. package/src/schemas/screen-options.ts +2 -0
  34. package/src/services/logger.ts +1 -1
  35. package/src/services/screen.ts +3 -0
  36. package/src/services/screen_manager.tsx +16 -12
  37. package/src/themes/dark.ts +4 -0
  38. package/src/themes/high-contrast.ts +4 -0
  39. package/src/themes/light.ts +4 -0
  40. package/src/tokens/logger.ts +9 -0
  41. package/src/types/screen.types.ts +2 -2
  42. package/src/types/theme.types.ts +1 -0
  43. package/src/utils/format.ts +3 -2
  44. package/lib/screen_manager_bridge-BpDgVu3e.cjs.map +0 -1
  45. package/lib/screen_manager_bridge-Dfg4QUrl.mjs.map +0 -1
@@ -200,6 +200,23 @@ describe('Sidebar', () => {
200
200
 
201
201
  expect(component).toMatchSnapshot()
202
202
  })
203
+
204
+ it('should render static status', () => {
205
+ const screens = [createMockScreen({ id: '1', name: 'Static', status: 'static' })]
206
+
207
+ const component = wrapWithContext(
208
+ <Sidebar
209
+ screens={screens}
210
+ selectedIndex={0}
211
+ activeScreenId="1"
212
+ focused={true}
213
+ width={30}
214
+ title="Screens"
215
+ />,
216
+ )
217
+
218
+ expect(component).toMatchSnapshot()
219
+ })
203
220
  })
204
221
 
205
222
  describe('badge counts', () => {
@@ -1,12 +1,14 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
1
  import { TestContainer } from '@navios/core/testing'
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+
4
+ import type { LogLevel } from '@navios/core'
3
5
 
4
6
  import { ScreenLoggerInstance } from '../../services/logger.ts'
5
7
  import { Screen } from '../../tokens/index.ts'
6
8
  import { createMockScreenInstance } from '../utils/factories.ts'
7
9
 
10
+ import type { ScreenInstance } from '../../index.js'
8
11
  import type { MockScreenInstance } from '../utils/factories.ts'
9
- import type { LogLevel } from '@navios/core'
10
12
 
11
13
  describe('ScreenLoggerInstance', () => {
12
14
  let container: TestContainer
@@ -17,7 +19,7 @@ describe('ScreenLoggerInstance', () => {
17
19
  beforeEach(() => {
18
20
  container = new TestContainer()
19
21
  mockScreen = createMockScreenInstance()
20
- container.bind(Screen).toValue(mockScreen)
22
+ container.bind(Screen).toValue(mockScreen as unknown as ScreenInstance)
21
23
  })
22
24
 
23
25
  afterEach(async () => {
@@ -1 +1,2 @@
1
+ export * from './isomorphic-logger.factory.js'
1
2
  export * from './screen.factory.ts'
@@ -0,0 +1,23 @@
1
+ import { Factory, inject, Logger, type FactoryContext } from '@navios/core'
2
+
3
+ import { ScreenManager } from '../services/screen_manager.js'
4
+ import { IsomorphicLogger, ScreenLogger } from '../tokens/logger.js'
5
+
6
+ import type { IsomorphicLoggerInterface } from '../interfaces/isomorphic-logger.js'
7
+ import type { LoggerOptions } from '../schemas/index.js'
8
+
9
+ @Factory({
10
+ token: IsomorphicLogger,
11
+ })
12
+ export class IsomorphicLoggerFactory {
13
+ private readonly screenManager = inject(ScreenManager)
14
+
15
+ async create(ctx: FactoryContext, options: LoggerOptions): Promise<IsomorphicLoggerInterface> {
16
+ if (this.screenManager.isTuiBound()) {
17
+ return ctx.inject(ScreenLogger, options)
18
+ }
19
+ return ctx.inject(Logger, {
20
+ context: options.context,
21
+ })
22
+ }
23
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,11 @@
3
3
  // ============================================
4
4
  export * from './types/index.ts'
5
5
 
6
+ // ============================================
7
+ // Interfaces
8
+ // ============================================
9
+ export * from './interfaces/index.ts'
10
+
6
11
  // ============================================
7
12
  // Components
8
13
  // ============================================
@@ -0,0 +1 @@
1
+ export * from './isomorphic-logger.ts'
@@ -0,0 +1,9 @@
1
+ import type { LoggerInstance } from '@navios/core'
2
+
3
+ import type { ScreenLoggerInstance } from '../services/logger.ts'
4
+
5
+ type PublicLoggerMethods = Pick<LoggerInstance, keyof LoggerInstance>
6
+ type PublicScreenLoggerMethods = Pick<ScreenLoggerInstance, keyof ScreenLoggerInstance>
7
+ type ScreenOwnMethods = Partial<Omit<PublicScreenLoggerMethods, keyof PublicLoggerMethods>>
8
+
9
+ export interface IsomorphicLoggerInterface extends PublicLoggerMethods, ScreenOwnMethods {}
@@ -3,11 +3,13 @@ import {
3
3
  Injectable,
4
4
  LoggerOutput,
5
5
  type ClassTypeWithInstance,
6
+ type LoggerOptions,
6
7
  type LoggerService,
7
8
  type LogLevel,
8
9
  } from '@navios/core'
9
10
 
10
11
  import { ScreenLogger } from '../tokens/index.ts'
12
+ import { ALL_LOG_LEVELS } from '../types/index.ts'
11
13
 
12
14
  export function overrideConsoleLogger(
13
15
  hidden: boolean = false,
@@ -22,12 +24,17 @@ export function overrideConsoleLogger(
22
24
  class ConsoleLoggerOverride implements LoggerService {
23
25
  protected readonly logger = inject(ScreenLogger, {
24
26
  screen: {
25
- name: 'default',
27
+ name: 'internal',
26
28
  icon: '💻',
27
29
  hidden,
30
+ static: true,
28
31
  },
29
32
  })
30
33
 
34
+ setup(options: LoggerOptions & { logLevels: LogLevel[] }): void {
35
+ this.logger.setLogLevels(options.logLevels ?? ALL_LOG_LEVELS)
36
+ }
37
+
31
38
  log(message: string): void {
32
39
  this.logger.log(message)
33
40
  }
@@ -9,6 +9,8 @@ export const ScreenOptionsSchema = z.object({
9
9
  badgeCount: z.number().optional(),
10
10
  /** Whether the screen is hidden */
11
11
  hidden: z.boolean().optional().default(false),
12
+ /** Whether the screen is static (ignored in auto-close calculations) */
13
+ static: z.boolean().optional().default(false),
12
14
  })
13
15
 
14
16
  export type ScreenOptions = z.infer<typeof ScreenOptionsSchema>
@@ -68,7 +68,7 @@ export class ScreenLoggerInstance implements LoggerService {
68
68
  }
69
69
 
70
70
  trace(msg: string | object, label?: string): this {
71
- const trace = captureTrace()
71
+ const trace = captureTrace(1)
72
72
  return this.write('verbose', msg, label, trace, 'trace')
73
73
  }
74
74
 
@@ -43,6 +43,9 @@ export class ScreenInstance {
43
43
  this.icon = options.icon
44
44
  this.badgeCount = options.badgeCount ?? 0
45
45
  this.hidden = options.hidden ?? false
46
+ if (options.static) {
47
+ this.status = 'static'
48
+ }
46
49
  }
47
50
 
48
51
  incrementVersion(): void {
@@ -101,7 +101,7 @@ export class ScreenManager {
101
101
  this.renderer = await createCliRenderer({
102
102
  exitOnCtrlC: options?.exitOnCtrlC ?? true,
103
103
  useAlternateScreen: true,
104
- useMouse: options?.useMouse ?? false,
104
+ useMouse: options?.useMouse ?? true,
105
105
  })
106
106
 
107
107
  this.root = createRoot(this.renderer)
@@ -244,33 +244,34 @@ export class ScreenManager {
244
244
  }
245
245
  }
246
246
 
247
- // Check for auto-close
248
- this.checkAutoClose()
249
-
250
247
  this.notifyChange()
251
248
  }
252
249
 
253
250
  /**
254
- * Check if all screens are successful and start auto-close timer if enabled
251
+ * Check if all screens are successful (or only static) and start auto-close timer if enabled.
252
+ * Static screens are ignored in this calculation.
253
+ * If there are only static screens, the timer will trigger after the delay with no new activity.
255
254
  */
256
255
  private checkAutoClose(): void {
257
256
  const autoClose = this.bindOptions.autoClose
258
257
  if (!autoClose || !this.isBound) return
259
258
 
260
- // Clear any existing timer
259
+ // Clear any existing timer (will be restarted if conditions are met)
261
260
  if (this.autoCloseTimer) {
262
261
  clearTimeout(this.autoCloseTimer)
263
262
  this.autoCloseTimer = null
264
263
  }
265
264
 
266
- // Check if all screens are successful
267
- const screens = this.getScreens()
268
- if (screens.length === 0) return
265
+ // Get non-static screens
266
+ const nonStaticScreens = this.getScreens().filter((s) => s.getStatus() !== 'static')
269
267
 
270
- const allSuccessful = screens.every((s) => s.getStatus() === 'success')
271
- if (!allSuccessful) return
268
+ // If there are non-static screens, check if all are successful
269
+ if (nonStaticScreens.length > 0) {
270
+ const allSuccessful = nonStaticScreens.every((s) => s.getStatus() === 'success')
271
+ if (!allSuccessful) return
272
+ }
272
273
 
273
- // Start auto-close timer
274
+ // Start auto-close timer (either all non-static screens succeeded, or only static screens exist)
274
275
  const delay = typeof autoClose === 'number' ? autoClose : 5000
275
276
  this.autoCloseTimer = setTimeout(() => {
276
277
  this.unbind()
@@ -376,6 +377,9 @@ export class ScreenManager {
376
377
  }
377
378
 
378
379
  private notifyChange(): void {
380
+ // Check auto-close on every change (resets timer if activity occurs)
381
+ this.checkAutoClose()
382
+
379
383
  // Notify listeners - React components will forceUpdate and re-render
380
384
  this.changeListeners.forEach((listener) => listener())
381
385
  }
@@ -69,6 +69,10 @@ export const darkTheme: Theme = {
69
69
  icon: '✗',
70
70
  color: '#EF4444', // Red-500
71
71
  },
72
+ static: {
73
+ icon: '●',
74
+ color: '#3B82F6', // Blue-500
75
+ },
72
76
  },
73
77
 
74
78
  separator: {
@@ -74,6 +74,10 @@ export const highContrastTheme: Theme = {
74
74
  icon: '✗',
75
75
  color: '#EF4444', // Red-500
76
76
  },
77
+ static: {
78
+ icon: '●',
79
+ color: '#00FFFF', // Cyan for high visibility
80
+ },
77
81
  },
78
82
 
79
83
  separator: {
@@ -68,6 +68,10 @@ export const lightTheme: Theme = {
68
68
  icon: '✗',
69
69
  color: '#DC2626', // Red-600
70
70
  },
71
+ static: {
72
+ icon: '●',
73
+ color: '#2563EB', // Blue-600
74
+ },
71
75
  },
72
76
 
73
77
  separator: {
@@ -2,9 +2,18 @@ import { InjectionToken } from '@navios/core'
2
2
 
3
3
  import { LoggerOptionsSchema } from '../schemas/index.ts'
4
4
 
5
+ import type { IsomorphicLoggerInterface } from '../interfaces/isomorphic-logger.ts'
5
6
  import type { ScreenLoggerInstance } from '../services/index.ts'
6
7
 
7
8
  export const ScreenLogger = InjectionToken.create<ScreenLoggerInstance, typeof LoggerOptionsSchema>(
8
9
  'ScreenLoggerInstance',
9
10
  LoggerOptionsSchema,
10
11
  )
12
+ /**
13
+ * Isomorphic logger token for shared logger between TUI and Server.
14
+ * This token is used to inject the logger instance into the application.
15
+ */
16
+ export const IsomorphicLogger = InjectionToken.create<
17
+ IsomorphicLoggerInterface,
18
+ typeof LoggerOptionsSchema
19
+ >('IsomorphicLogger', LoggerOptionsSchema)
@@ -108,7 +108,7 @@ export interface LoggerContextValue {
108
108
  // Screen Service Types
109
109
  // ============================================
110
110
 
111
- export type ScreenStatus = 'waiting' | 'pending' | 'success' | 'fail'
111
+ export type ScreenStatus = 'waiting' | 'pending' | 'success' | 'fail' | 'static'
112
112
 
113
113
  export interface BindOptions {
114
114
  exitOnCtrlC?: boolean
@@ -119,6 +119,6 @@ export interface BindOptions {
119
119
  autoClose?: boolean | number
120
120
  /** Theme to use for the TUI (theme object or preset name) */
121
121
  theme?: Theme | ThemePreset
122
- /** Enable mouse support (default: false) */
122
+ /** Enable mouse support (default: true) */
123
123
  useMouse?: boolean
124
124
  }
@@ -52,6 +52,7 @@ export interface StatusIndicatorsTheme {
52
52
  pending: StatusIndicator
53
53
  success: StatusIndicator
54
54
  fail: StatusIndicator
55
+ static: StatusIndicator
55
56
  }
56
57
 
57
58
  /**
@@ -43,10 +43,11 @@ export function formatObject(obj: unknown, depth: number = 2, currentDepth: numb
43
43
  /**
44
44
  * Capture a stack trace, filtering out internal frames
45
45
  */
46
- export function captureTrace(): string {
46
+ export function captureTrace(offset: number = 0): string {
47
47
  const err = new Error()
48
48
  const stack = err.stack ?? ''
49
49
  const lines = stack.split('\n')
50
+ const begin = lines.findIndex((line) => line.includes('captureTrace'))
50
51
  // Skip the first 4 lines: Error, captureTrace, trace method, and the log method
51
- return lines.slice(4).join('\n')
52
+ return lines.slice(begin !== -1 ? begin + 1 + offset : 4).join('\n')
52
53
  }