@nexus-ai-fs/tui 0.9.18

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 (193) hide show
  1. package/README.md +30 -0
  2. package/package.json +48 -0
  3. package/src/app.tsx +349 -0
  4. package/src/index.tsx +137 -0
  5. package/src/opentui-env.d.ts +61 -0
  6. package/src/panels/access/access-panel.tsx +597 -0
  7. package/src/panels/access/alert-list.tsx +77 -0
  8. package/src/panels/access/constraint-creator.tsx +128 -0
  9. package/src/panels/access/constraint-list.tsx +72 -0
  10. package/src/panels/access/credential-list.tsx +68 -0
  11. package/src/panels/access/delegation-chain-view.tsx +110 -0
  12. package/src/panels/access/delegation-completer.tsx +120 -0
  13. package/src/panels/access/delegation-creator.tsx +237 -0
  14. package/src/panels/access/delegation-list.tsx +74 -0
  15. package/src/panels/access/fraud-score-view.tsx +94 -0
  16. package/src/panels/access/manifest-creator.tsx +167 -0
  17. package/src/panels/access/manifest-list.tsx +105 -0
  18. package/src/panels/access/namespace-config-view.tsx +525 -0
  19. package/src/panels/access/permission-checker.tsx +231 -0
  20. package/src/panels/agents/agent-status-view.tsx +196 -0
  21. package/src/panels/agents/agents-panel.tsx +493 -0
  22. package/src/panels/agents/delegation-list.tsx +154 -0
  23. package/src/panels/agents/inbox-view.tsx +96 -0
  24. package/src/panels/agents/trajectories-tab.tsx +40 -0
  25. package/src/panels/api-console/api-console-panel.tsx +189 -0
  26. package/src/panels/api-console/codegen-viewer.tsx +36 -0
  27. package/src/panels/api-console/codegen.ts +112 -0
  28. package/src/panels/api-console/endpoint-list.tsx +57 -0
  29. package/src/panels/api-console/request-builder.tsx +69 -0
  30. package/src/panels/api-console/response-viewer.tsx +54 -0
  31. package/src/panels/connectors/available-tab.tsx +357 -0
  32. package/src/panels/connectors/connector-row.tsx +121 -0
  33. package/src/panels/connectors/connectors-panel.tsx +88 -0
  34. package/src/panels/connectors/error-parser.ts +116 -0
  35. package/src/panels/connectors/mounted-tab.tsx +179 -0
  36. package/src/panels/connectors/skills-tab.tsx +235 -0
  37. package/src/panels/connectors/template-generator.ts +211 -0
  38. package/src/panels/connectors/write-tab.tsx +514 -0
  39. package/src/panels/events/audit-tab.tsx +69 -0
  40. package/src/panels/events/audit-trail.tsx +75 -0
  41. package/src/panels/events/connector-detail.tsx +49 -0
  42. package/src/panels/events/connector-list.tsx +73 -0
  43. package/src/panels/events/connectors-tab.tsx +92 -0
  44. package/src/panels/events/event-replay.tsx +80 -0
  45. package/src/panels/events/events-panel.tsx +414 -0
  46. package/src/panels/events/events-tab.tsx +212 -0
  47. package/src/panels/events/lock-list.tsx +54 -0
  48. package/src/panels/events/locks-tab.tsx +103 -0
  49. package/src/panels/events/mcl-replay.tsx +77 -0
  50. package/src/panels/events/mcl-tab.tsx +83 -0
  51. package/src/panels/events/operations-tab-wrapper.tsx +62 -0
  52. package/src/panels/events/operations-tab.tsx +41 -0
  53. package/src/panels/events/replay-tab.tsx +76 -0
  54. package/src/panels/events/secrets-audit.tsx +64 -0
  55. package/src/panels/events/secrets-tab.tsx +75 -0
  56. package/src/panels/events/subscription-list.tsx +54 -0
  57. package/src/panels/events/subscriptions-tab.tsx +82 -0
  58. package/src/panels/files/file-aspects.tsx +93 -0
  59. package/src/panels/files/file-editor.tsx +160 -0
  60. package/src/panels/files/file-explorer-keybindings.ts +468 -0
  61. package/src/panels/files/file-explorer-panel.tsx +545 -0
  62. package/src/panels/files/file-lineage.tsx +163 -0
  63. package/src/panels/files/file-list-item.tsx +28 -0
  64. package/src/panels/files/file-metadata.tsx +62 -0
  65. package/src/panels/files/file-preview.tsx +108 -0
  66. package/src/panels/files/file-schema.tsx +89 -0
  67. package/src/panels/files/file-tree-node.tsx +44 -0
  68. package/src/panels/files/file-tree.tsx +169 -0
  69. package/src/panels/files/share-links-tab.tsx +33 -0
  70. package/src/panels/files/uploads-tab.tsx +45 -0
  71. package/src/panels/payments/approval-list.tsx +83 -0
  72. package/src/panels/payments/balance-card.tsx +43 -0
  73. package/src/panels/payments/budget-card.tsx +70 -0
  74. package/src/panels/payments/payments-panel.tsx +451 -0
  75. package/src/panels/payments/policy-list.tsx +64 -0
  76. package/src/panels/payments/reservation-list.tsx +78 -0
  77. package/src/panels/payments/transaction-list.tsx +103 -0
  78. package/src/panels/payments/transfer-form.tsx +109 -0
  79. package/src/panels/search/column-search.tsx +79 -0
  80. package/src/panels/search/knowledge-view.tsx +100 -0
  81. package/src/panels/search/memory-list.tsx +197 -0
  82. package/src/panels/search/playbook-list.tsx +77 -0
  83. package/src/panels/search/rlm-answer-view.tsx +105 -0
  84. package/src/panels/search/search-panel.tsx +405 -0
  85. package/src/panels/search/search-results.tsx +116 -0
  86. package/src/panels/stack/stack-panel.tsx +474 -0
  87. package/src/panels/versions/conflicts-tab.tsx +59 -0
  88. package/src/panels/versions/entry-detail.tsx +89 -0
  89. package/src/panels/versions/transaction-actions.tsx +34 -0
  90. package/src/panels/versions/transaction-list.tsx +90 -0
  91. package/src/panels/versions/versions-panel.tsx +276 -0
  92. package/src/panels/workflows/execution-list.tsx +102 -0
  93. package/src/panels/workflows/scheduler-view.tsx +135 -0
  94. package/src/panels/workflows/workflow-list.tsx +88 -0
  95. package/src/panels/workflows/workflows-panel.tsx +295 -0
  96. package/src/panels/zones/brick-detail.tsx +136 -0
  97. package/src/panels/zones/brick-list.tsx +56 -0
  98. package/src/panels/zones/cache-tab.tsx +118 -0
  99. package/src/panels/zones/drift-view.tsx +97 -0
  100. package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
  101. package/src/panels/zones/memories-tab.tsx +37 -0
  102. package/src/panels/zones/reindex-status.tsx +84 -0
  103. package/src/panels/zones/workspaces-tab.tsx +37 -0
  104. package/src/panels/zones/zone-list.tsx +73 -0
  105. package/src/panels/zones/zones-panel.tsx +559 -0
  106. package/src/services/command-runner.ts +303 -0
  107. package/src/shared/accessibility-announcements.ts +44 -0
  108. package/src/shared/action-registry.ts +466 -0
  109. package/src/shared/brick-states.ts +91 -0
  110. package/src/shared/command-palette.ts +35 -0
  111. package/src/shared/components/announcement-bar.tsx +30 -0
  112. package/src/shared/components/app-confirm-dialog.tsx +29 -0
  113. package/src/shared/components/breadcrumb.tsx +21 -0
  114. package/src/shared/components/brick-gate.tsx +60 -0
  115. package/src/shared/components/command-output.tsx +95 -0
  116. package/src/shared/components/command-palette.tsx +97 -0
  117. package/src/shared/components/confirm-dialog.tsx +61 -0
  118. package/src/shared/components/diff-viewer.tsx +219 -0
  119. package/src/shared/components/empty-state.tsx +36 -0
  120. package/src/shared/components/error-bar.tsx +60 -0
  121. package/src/shared/components/error-boundary.tsx +53 -0
  122. package/src/shared/components/help-overlay.tsx +99 -0
  123. package/src/shared/components/identity-switcher.tsx +168 -0
  124. package/src/shared/components/loading-indicator.tsx +40 -0
  125. package/src/shared/components/pagination-bar.tsx +68 -0
  126. package/src/shared/components/pre-connection-screen.tsx +398 -0
  127. package/src/shared/components/scroll-indicator.tsx +46 -0
  128. package/src/shared/components/side-nav-utils.ts +68 -0
  129. package/src/shared/components/side-nav.tsx +287 -0
  130. package/src/shared/components/spinner.tsx +26 -0
  131. package/src/shared/components/status-bar.tsx +117 -0
  132. package/src/shared/components/styled-text.tsx +72 -0
  133. package/src/shared/components/sub-tab-bar-utils.ts +100 -0
  134. package/src/shared/components/sub-tab-bar.tsx +40 -0
  135. package/src/shared/components/tab-bar-utils.ts +36 -0
  136. package/src/shared/components/tab-bar.tsx +50 -0
  137. package/src/shared/components/text-input.tsx +73 -0
  138. package/src/shared/components/tooltip.tsx +53 -0
  139. package/src/shared/components/virtual-list.tsx +93 -0
  140. package/src/shared/components/welcome-screen.tsx +111 -0
  141. package/src/shared/hooks/use-api.ts +10 -0
  142. package/src/shared/hooks/use-brick-available.ts +42 -0
  143. package/src/shared/hooks/use-confirm.ts +66 -0
  144. package/src/shared/hooks/use-connection-state.ts +67 -0
  145. package/src/shared/hooks/use-copy.ts +31 -0
  146. package/src/shared/hooks/use-fresh-server.ts +62 -0
  147. package/src/shared/hooks/use-keyboard.ts +58 -0
  148. package/src/shared/hooks/use-list-navigation.ts +106 -0
  149. package/src/shared/hooks/use-swr.ts +117 -0
  150. package/src/shared/hooks/use-tab-fallback.ts +32 -0
  151. package/src/shared/hooks/use-text-input.ts +113 -0
  152. package/src/shared/hooks/use-visible-tabs.ts +61 -0
  153. package/src/shared/lib/circular-buffer.ts +82 -0
  154. package/src/shared/lib/clipboard.ts +14 -0
  155. package/src/shared/nav-items.ts +73 -0
  156. package/src/shared/navigation.ts +110 -0
  157. package/src/shared/status-breadcrumb.ts +74 -0
  158. package/src/shared/syntax-style.ts +3 -0
  159. package/src/shared/tab-visibility.ts +15 -0
  160. package/src/shared/text-style.ts +23 -0
  161. package/src/shared/theme.ts +179 -0
  162. package/src/shared/utils/format-size.ts +20 -0
  163. package/src/shared/utils/format-text.ts +10 -0
  164. package/src/shared/utils/format-time.ts +72 -0
  165. package/src/shared/utils/lru-cache.ts +75 -0
  166. package/src/stores/access-store-types.ts +154 -0
  167. package/src/stores/access-store.ts +674 -0
  168. package/src/stores/agents-store.ts +404 -0
  169. package/src/stores/announcement-store.ts +46 -0
  170. package/src/stores/api-console-store.ts +476 -0
  171. package/src/stores/connectors-store.ts +434 -0
  172. package/src/stores/create-api-action.ts +140 -0
  173. package/src/stores/delegation-store.ts +300 -0
  174. package/src/stores/error-store.ts +102 -0
  175. package/src/stores/events-store.ts +163 -0
  176. package/src/stores/files-store.ts +630 -0
  177. package/src/stores/first-run-store.ts +34 -0
  178. package/src/stores/global-store.ts +255 -0
  179. package/src/stores/infra-store.ts +461 -0
  180. package/src/stores/knowledge-store.ts +358 -0
  181. package/src/stores/lineage-store.ts +126 -0
  182. package/src/stores/mcp-store.ts +147 -0
  183. package/src/stores/payments-store.ts +545 -0
  184. package/src/stores/search-store-types.ts +155 -0
  185. package/src/stores/search-store.ts +656 -0
  186. package/src/stores/share-link-store.ts +151 -0
  187. package/src/stores/stack-store.ts +352 -0
  188. package/src/stores/ui-store.ts +161 -0
  189. package/src/stores/upload-store.ts +131 -0
  190. package/src/stores/versions-store.ts +355 -0
  191. package/src/stores/workflows-store.ts +402 -0
  192. package/src/stores/workspace-store.ts +185 -0
  193. package/src/stores/zones-store.ts +378 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Full-screen keybinding reference overlay.
3
+ *
4
+ * Activated with `?` key. Shows all keybindings for the current panel
5
+ * plus global bindings. Press any key to dismiss.
6
+ *
7
+ * @see Issue #3066, Phase E9
8
+ */
9
+
10
+ import React from "react";
11
+ import { useKeyboard } from "../hooks/use-keyboard.js";
12
+ import { statusColor } from "../theme.js";
13
+ import { textStyle } from "../text-style.js";
14
+ import type { PanelId } from "../../stores/global-store.js";
15
+ import {
16
+ type KeyBinding,
17
+ GLOBAL_BINDINGS,
18
+ NAV_BINDINGS,
19
+ PANEL_BINDINGS,
20
+ } from "../action-registry.js";
21
+
22
+ interface HelpOverlayProps {
23
+ readonly visible: boolean;
24
+ readonly panel: PanelId;
25
+ readonly onDismiss: () => void;
26
+ }
27
+
28
+ export function HelpOverlay({
29
+ visible,
30
+ panel,
31
+ onDismiss,
32
+ }: HelpOverlayProps): React.ReactNode {
33
+ useKeyboard(
34
+ visible
35
+ ? {
36
+ escape: onDismiss,
37
+ "?": onDismiss,
38
+ // Dismiss on any other key via onUnhandled
39
+ }
40
+ : {},
41
+ visible ? () => onDismiss() : undefined,
42
+ );
43
+
44
+ if (!visible) return null;
45
+
46
+ const panelBindings = PANEL_BINDINGS[panel] ?? [];
47
+
48
+ return (
49
+ <box
50
+ height="100%"
51
+ width="100%"
52
+ justifyContent="center"
53
+ alignItems="center"
54
+ >
55
+ <box
56
+ flexDirection="column"
57
+ borderStyle="double"
58
+ width={60}
59
+ padding={1}
60
+ >
61
+ <text style={textStyle({ bold: true })}>Keybinding Reference</text>
62
+ <text>{""}</text>
63
+
64
+ <text style={textStyle({ fg: statusColor.info, bold: true })}>{"─── Global ───"}</text>
65
+ {GLOBAL_BINDINGS.map((b) => (
66
+ <text key={b.key}>
67
+ <span style={textStyle({ fg: statusColor.info })}>{` ${b.key.padEnd(12)}`}</span>
68
+ <span>{b.action}</span>
69
+ </text>
70
+ ))}
71
+
72
+ <text>{""}</text>
73
+ <text style={textStyle({ fg: statusColor.info, bold: true })}>{"─── Navigation ───"}</text>
74
+ {NAV_BINDINGS.map((b) => (
75
+ <text key={b.key}>
76
+ <span style={textStyle({ fg: statusColor.info })}>{` ${b.key.padEnd(12)}`}</span>
77
+ <span>{b.action}</span>
78
+ </text>
79
+ ))}
80
+
81
+ {panelBindings.length > 0 && (
82
+ <>
83
+ <text>{""}</text>
84
+ <text style={textStyle({ fg: statusColor.info, bold: true })}>{`─── ${panel} ───`}</text>
85
+ {panelBindings.map((b) => (
86
+ <text key={b.key}>
87
+ <span style={textStyle({ fg: statusColor.info })}>{` ${b.key.padEnd(12)}`}</span>
88
+ <span>{b.action}</span>
89
+ </text>
90
+ ))}
91
+ </>
92
+ )}
93
+
94
+ <text>{""}</text>
95
+ <text style={textStyle({ dim: true })}>Press any key to dismiss</text>
96
+ </box>
97
+ </box>
98
+ );
99
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Overlay dialog for switching the active runtime identity.
3
+ *
4
+ * Activated via Ctrl+I from the main app. Provides three editable fields
5
+ * (Agent ID, Subject, Zone ID) pre-filled from the current config.
6
+ * Tab cycles fields, Enter confirms, Escape cancels.
7
+ *
8
+ * On confirm, calls setIdentity() then testConnection() to verify the
9
+ * new credentials against the server.
10
+ */
11
+
12
+ import React, { useState, useCallback } from "react";
13
+ import { useGlobalStore } from "../../stores/global-store.js";
14
+ import { useKeyboard } from "../hooks/use-keyboard.js";
15
+
16
+ interface IdentitySwitcherProps {
17
+ readonly visible: boolean;
18
+ readonly onClose: () => void;
19
+ }
20
+
21
+ type FieldName = "agentId" | "subject" | "zoneId";
22
+
23
+ const FIELD_ORDER: readonly FieldName[] = ["agentId", "subject", "zoneId"];
24
+
25
+ const FIELD_LABELS: Readonly<Record<FieldName, string>> = {
26
+ agentId: "Agent ID",
27
+ subject: "Subject",
28
+ zoneId: "Zone ID",
29
+ };
30
+
31
+ export function IdentitySwitcher({
32
+ visible,
33
+ onClose,
34
+ }: IdentitySwitcherProps): React.ReactNode {
35
+ const config = useGlobalStore((s) => s.config);
36
+
37
+ const [activeField, setActiveField] = useState<FieldName>("agentId");
38
+ const [fields, setFields] = useState<Readonly<Record<FieldName, string>>>({
39
+ agentId: config.agentId ?? "",
40
+ subject: config.subject ?? "",
41
+ zoneId: config.zoneId ?? "",
42
+ });
43
+
44
+ // Reset fields to current config values when the dialog opens
45
+ const resetFields = useCallback(() => {
46
+ const currentConfig = useGlobalStore.getState().config;
47
+ setFields({
48
+ agentId: currentConfig.agentId ?? "",
49
+ subject: currentConfig.subject ?? "",
50
+ zoneId: currentConfig.zoneId ?? "",
51
+ });
52
+ setActiveField("agentId");
53
+ }, []);
54
+
55
+ const handleConfirm = useCallback(() => {
56
+ const store = useGlobalStore.getState();
57
+ // Pass all fields explicitly — empty string becomes undefined to clear the header
58
+ store.setIdentity({
59
+ agentId: fields.agentId.trim() || undefined,
60
+ subject: fields.subject.trim() || undefined,
61
+ zoneId: fields.zoneId.trim() || undefined,
62
+ });
63
+ store.testConnection();
64
+ onClose();
65
+ }, [fields, onClose]);
66
+
67
+ const handleCancel = useCallback(() => {
68
+ resetFields();
69
+ onClose();
70
+ }, [resetFields, onClose]);
71
+
72
+ const handleTab = useCallback(() => {
73
+ const currentIdx = FIELD_ORDER.indexOf(activeField);
74
+ const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
75
+ const nextField = FIELD_ORDER[nextIdx];
76
+ if (nextField) {
77
+ setActiveField(nextField);
78
+ }
79
+ }, [activeField]);
80
+
81
+ const handleBackspace = useCallback(() => {
82
+ setFields((prev) => ({
83
+ ...prev,
84
+ [activeField]: prev[activeField].slice(0, -1),
85
+ }));
86
+ }, [activeField]);
87
+
88
+ const handleUnhandledKey = useCallback(
89
+ (keyName: string) => {
90
+ if (!visible) return;
91
+ // Single printable character
92
+ if (keyName.length === 1) {
93
+ setFields((prev) => ({
94
+ ...prev,
95
+ [activeField]: prev[activeField] + keyName,
96
+ }));
97
+ } else if (keyName === "space") {
98
+ setFields((prev) => ({
99
+ ...prev,
100
+ [activeField]: prev[activeField] + " ",
101
+ }));
102
+ }
103
+ },
104
+ [visible, activeField],
105
+ );
106
+
107
+ useKeyboard(
108
+ visible
109
+ ? {
110
+ return: handleConfirm,
111
+ escape: handleCancel,
112
+ tab: handleTab,
113
+ backspace: handleBackspace,
114
+ }
115
+ : {},
116
+ visible ? handleUnhandledKey : undefined,
117
+ );
118
+
119
+ if (!visible) return null;
120
+
121
+ return (
122
+ <box
123
+ height="100%"
124
+ width="100%"
125
+ justifyContent="center"
126
+ alignItems="center"
127
+ >
128
+ <box
129
+ flexDirection="column"
130
+ borderStyle="double"
131
+ width={60}
132
+ height={11}
133
+ padding={1}
134
+ >
135
+ <text>{"Switch Identity (Tab:next Enter:confirm Esc:cancel)"}</text>
136
+ <text>{""}</text>
137
+
138
+ {FIELD_ORDER.map((field) => {
139
+ const isActive = field === activeField;
140
+ const label = FIELD_LABELS[field];
141
+ const value = fields[field];
142
+ const cursor = isActive ? "\u2588" : "";
143
+ const prefix = isActive ? "\u25b8 " : " ";
144
+ return (
145
+ <box key={field} height={1} width="100%">
146
+ <text>{`${prefix}${label}: ${value}${cursor}`}</text>
147
+ </box>
148
+ );
149
+ })}
150
+
151
+ <text>{""}</text>
152
+ <text>{"Current: " + formatCurrentIdentity(config)}</text>
153
+ </box>
154
+ </box>
155
+ );
156
+ }
157
+
158
+ function formatCurrentIdentity(config: {
159
+ readonly agentId?: string;
160
+ readonly subject?: string;
161
+ readonly zoneId?: string;
162
+ }): string {
163
+ const parts: string[] = [];
164
+ if (config.agentId) parts.push(`agent:${config.agentId}`);
165
+ if (config.subject) parts.push(`sub:${config.subject}`);
166
+ if (config.zoneId) parts.push(`zone:${config.zoneId}`);
167
+ return parts.length > 0 ? parts.join(" | ") : "(none)";
168
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared loading indicator with context message.
3
+ *
4
+ * Replaces all inline "loading..." text across panels with a consistent
5
+ * component that includes the existing Spinner animation.
6
+ *
7
+ * @see Issue #3066, Phase E5
8
+ */
9
+
10
+ import React from "react";
11
+ import { Spinner } from "./spinner.js";
12
+
13
+ interface LoadingIndicatorProps {
14
+ /** Context message shown next to the spinner. Default: "Loading..." */
15
+ readonly message?: string;
16
+ /** Whether to center within parent. Default: true */
17
+ readonly centered?: boolean;
18
+ }
19
+
20
+ export function LoadingIndicator({
21
+ message = "Loading...",
22
+ centered = true,
23
+ }: LoadingIndicatorProps): React.ReactNode {
24
+ const content = <Spinner label={message} />;
25
+
26
+ if (centered) {
27
+ return (
28
+ <box
29
+ height="100%"
30
+ width="100%"
31
+ justifyContent="center"
32
+ alignItems="center"
33
+ >
34
+ {content}
35
+ </box>
36
+ );
37
+ }
38
+
39
+ return content;
40
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared pagination bar for lists with cursor-based or offset-based pagination.
3
+ *
4
+ * Shows current page position and provides prev/next hints.
5
+ *
6
+ * @see Issue #3066, Phase E4
7
+ */
8
+
9
+ import React from "react";
10
+ import { statusColor } from "../theme.js";
11
+ import { textStyle } from "../text-style.js";
12
+
13
+ interface PaginationBarProps {
14
+ /** Whether there are more items to load (next page available) */
15
+ readonly hasMore: boolean;
16
+ /** Whether there's a previous page */
17
+ readonly hasPrev: boolean;
18
+ /** Current page number (1-based) */
19
+ readonly currentPage: number;
20
+ /** Total pages if known, undefined for cursor-based pagination */
21
+ readonly totalPages?: number;
22
+ /** Key hint for next page. Default: "]" */
23
+ readonly nextKey?: string;
24
+ /** Key hint for previous page. Default: "[" */
25
+ readonly prevKey?: string;
26
+ /** Whether a page is currently loading */
27
+ readonly loading?: boolean;
28
+ }
29
+
30
+ /** Pure function for page display text — exported for testing. */
31
+ export function formatPageDisplay(currentPage: number, hasMore: boolean, totalPages?: number): string {
32
+ return totalPages
33
+ ? `Page ${currentPage} of ${totalPages}`
34
+ : `Page ${currentPage}${hasMore ? "+" : ""}`;
35
+ }
36
+
37
+ export function PaginationBar({
38
+ hasMore,
39
+ hasPrev,
40
+ currentPage,
41
+ totalPages,
42
+ nextKey = "]",
43
+ prevKey = "[",
44
+ loading = false,
45
+ }: PaginationBarProps): React.ReactNode {
46
+ const pageDisplay = formatPageDisplay(currentPage, hasMore, totalPages);
47
+
48
+ return (
49
+ <box height={1} width="100%" flexDirection="row">
50
+ <text style={textStyle({ dim: true })}>
51
+ {hasPrev && (
52
+ <span>
53
+ <span style={textStyle({ fg: statusColor.info })}>{prevKey}</span>
54
+ <span>{":prev "}</span>
55
+ </span>
56
+ )}
57
+ <span>{loading ? "Loading..." : pageDisplay}</span>
58
+ {hasMore && (
59
+ <span>
60
+ <span>{" "}</span>
61
+ <span style={textStyle({ fg: statusColor.info })}>{nextKey}</span>
62
+ <span>{":next"}</span>
63
+ </span>
64
+ )}
65
+ </text>
66
+ </box>
67
+ );
68
+ }