@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,597 @@
1
+ /**
2
+ * Access Control panel: tabbed layout for manifests, alerts,
3
+ * credentials, fraud scores, and delegations.
4
+ *
5
+ * Key bindings:
6
+ * j/k or up/down : navigate within lists
7
+ * g / Shift+G : jump to start / end of list
8
+ * Tab : cycle tabs
9
+ * Escape : close overlay
10
+ * Enter : manifests -> fetch detail (tuple entries)
11
+ * p : open permission checker (+ governance edge check)
12
+ * Shift+R : (alerts tab) resolve selected
13
+ * c : (manifests tab) create new manifest; (fraud tab) compute fraud scores
14
+ * Shift+X : (manifests tab) revoke selected manifest
15
+ * n : (delegations tab) create new delegation
16
+ * x : (delegations tab) revoke selected delegation
17
+ * o : (delegations tab) complete selected delegation
18
+ * v : (delegations tab) view delegation chain
19
+ * w : (delegations tab) view namespace config
20
+ * r : refresh current tab
21
+ */
22
+
23
+ import React, { useState, useEffect, useCallback } from "react";
24
+ import { useAccessStore } from "../../stores/access-store.js";
25
+ import type { AccessTab } from "../../stores/access-store.js";
26
+ import { useGlobalStore } from "../../stores/global-store.js";
27
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
28
+ import { listNavigationBindings } from "../../shared/hooks/use-list-navigation.js";
29
+ import { useCopy } from "../../shared/hooks/use-copy.js";
30
+ import { useConfirmStore } from "../../shared/hooks/use-confirm.js";
31
+ import { useApi } from "../../shared/hooks/use-api.js";
32
+ import { useUiStore } from "../../stores/ui-store.js";
33
+ import { useVisibleTabs, type TabDef } from "../../shared/hooks/use-visible-tabs.js";
34
+ import { LoadingIndicator } from "../../shared/components/loading-indicator.js";
35
+ import { statusColor } from "../../shared/theme.js";
36
+ import { textStyle } from "../../shared/text-style.js";
37
+ import { ManifestList } from "./manifest-list.js";
38
+ import { AlertList } from "./alert-list.js";
39
+ import { CredentialList } from "./credential-list.js";
40
+ import { FraudScoreView } from "./fraud-score-view.js";
41
+ import { DelegationList } from "./delegation-list.js";
42
+ import { PermissionChecker } from "./permission-checker.js";
43
+ import { DelegationCreator } from "./delegation-creator.js";
44
+ import { DelegationCompleter } from "./delegation-completer.js";
45
+ import { DelegationChainView } from "./delegation-chain-view.js";
46
+ import { NamespaceConfigView } from "./namespace-config-view.js";
47
+ import { ManifestCreator } from "./manifest-creator.js";
48
+ import { ConstraintList } from "./constraint-list.js";
49
+ import { ConstraintCreator } from "./constraint-creator.js";
50
+
51
+ const ALL_TABS: readonly TabDef<AccessTab>[] = [
52
+ { id: "manifests", label: "Manifests", brick: "access_manifest" },
53
+ { id: "alerts", label: "Alerts", brick: "governance" },
54
+ { id: "credentials", label: "Credentials", brick: "auth" },
55
+ { id: "fraud", label: "Fraud", brick: "governance" },
56
+ { id: "delegations", label: "Delegations", brick: "delegation" },
57
+ ];
58
+ const TAB_LABELS: Readonly<Record<AccessTab, string>> = {
59
+ manifests: "Manifests",
60
+ alerts: "Alerts",
61
+ credentials: "Credentials",
62
+ fraud: "Fraud",
63
+ delegations: "Delegations",
64
+ };
65
+
66
+ type OverlayMode =
67
+ | "none"
68
+ | "permissionChecker"
69
+ | "delegationCreator"
70
+ | "delegationCompleter"
71
+ | "delegationChainView"
72
+ | "namespaceConfigView"
73
+ | "manifestCreator"
74
+ | "constraintCreator";
75
+
76
+ export default function AccessPanel(): React.ReactNode {
77
+ const client = useApi();
78
+ const confirm = useConfirmStore((s) => s.confirm);
79
+ const overlayActive = useUiStore((s) => s.overlayActive);
80
+ const visibleTabs = useVisibleTabs(ALL_TABS);
81
+ const { copy, copied } = useCopy();
82
+ const [overlay, setOverlay] = useState<OverlayMode>("none");
83
+
84
+ // Zone for fraud score queries
85
+ const configZoneId = useGlobalStore((s) => s.config.zoneId);
86
+ const serverZoneId = useGlobalStore((s) => s.zoneId);
87
+ const effectiveZoneId = configZoneId ?? serverZoneId ?? undefined;
88
+
89
+ const manifests = useAccessStore((s) => s.manifests);
90
+ const selectedManifestIndex = useAccessStore((s) => s.selectedManifestIndex);
91
+ const manifestsLoading = useAccessStore((s) => s.manifestsLoading);
92
+ const lastPermissionCheck = useAccessStore((s) => s.lastPermissionCheck);
93
+ const permissionCheckLoading = useAccessStore((s) => s.permissionCheckLoading);
94
+ const alerts = useAccessStore((s) => s.alerts);
95
+ const alertsLoading = useAccessStore((s) => s.alertsLoading);
96
+ const selectedAlertIndex = useAccessStore((s) => s.selectedAlertIndex);
97
+ const credentials = useAccessStore((s) => s.credentials);
98
+ const credentialsLoading = useAccessStore((s) => s.credentialsLoading);
99
+ const fraudScores = useAccessStore((s) => s.fraudScores);
100
+ const fraudScoresLoading = useAccessStore((s) => s.fraudScoresLoading);
101
+ const selectedFraudIndex = useAccessStore((s) => s.selectedFraudIndex);
102
+ const delegations = useAccessStore((s) => s.delegations);
103
+ const delegationsLoading = useAccessStore((s) => s.delegationsLoading);
104
+ const selectedDelegationIndex = useAccessStore((s) => s.selectedDelegationIndex);
105
+ const governanceCheck = useAccessStore((s) => s.governanceCheck);
106
+ const governanceCheckLoading = useAccessStore((s) => s.governanceCheckLoading);
107
+ const activeTab = useAccessStore((s) => s.activeTab);
108
+ const error = useAccessStore((s) => s.error);
109
+
110
+ const fetchManifests = useAccessStore((s) => s.fetchManifests);
111
+ const fetchManifestDetail = useAccessStore((s) => s.fetchManifestDetail);
112
+ const revokeManifest = useAccessStore((s) => s.revokeManifest);
113
+ const fetchAlerts = useAccessStore((s) => s.fetchAlerts);
114
+ const resolveAlert = useAccessStore((s) => s.resolveAlert);
115
+ const fetchCredentials = useAccessStore((s) => s.fetchCredentials);
116
+ const issueCredential = useAccessStore((s) => s.issueCredential);
117
+ const collusionRings = useAccessStore((s) => s.collusionRings);
118
+ const collusionLoading = useAccessStore((s) => s.collusionLoading);
119
+ const fetchCollusionRings = useAccessStore((s) => s.fetchCollusionRings);
120
+ const suspendAgent = useAccessStore((s) => s.suspendAgent);
121
+ const constraints = useAccessStore((s) => s.constraints);
122
+ const constraintsLoading = useAccessStore((s) => s.constraintsLoading);
123
+ const selectedConstraintIndex = useAccessStore((s) => s.selectedConstraintIndex);
124
+ const fetchConstraints = useAccessStore((s) => s.fetchConstraints);
125
+ const deleteConstraint = useAccessStore((s) => s.deleteConstraint);
126
+ const setSelectedConstraintIndex = useAccessStore((s) => s.setSelectedConstraintIndex);
127
+ const revokeCredential = useAccessStore((s) => s.revokeCredential);
128
+ const fetchFraudScores = useAccessStore((s) => s.fetchFraudScores);
129
+ const computeFraudScores = useAccessStore((s) => s.computeFraudScores);
130
+ const fetchDelegations = useAccessStore((s) => s.fetchDelegations);
131
+ const revokeDelegation = useAccessStore((s) => s.revokeDelegation);
132
+ const setActiveTab = useAccessStore((s) => s.setActiveTab);
133
+ const setSelectedManifestIndex = useAccessStore((s) => s.setSelectedManifestIndex);
134
+ const setSelectedAlertIndex = useAccessStore((s) => s.setSelectedAlertIndex);
135
+ const setSelectedFraudIndex = useAccessStore((s) => s.setSelectedFraudIndex);
136
+ const setSelectedDelegationIndex = useAccessStore((s) => s.setSelectedDelegationIndex);
137
+
138
+ // Credential selection index
139
+ const [selectedCredentialIndex, setSelectedCredentialIndex] = useState(0);
140
+
141
+ // Clamp selectedCredentialIndex when credentials list shrinks (e.g. after revoke)
142
+ useEffect(() => {
143
+ if (credentials.length > 0 && selectedCredentialIndex >= credentials.length) {
144
+ setSelectedCredentialIndex(Math.max(0, credentials.length - 1));
145
+ }
146
+ }, [credentials.length, selectedCredentialIndex]);
147
+
148
+ // Fraud tab: which list is focused (scores vs constraints)
149
+ const [fraudFocus, setFraudFocus] = useState<"scores" | "constraints">("scores");
150
+
151
+ // Delegation status filter
152
+ const [delegationFilter, setDelegationFilter] = useState<string | null>(null);
153
+
154
+ // Fall back to first visible tab if the active tab becomes hidden
155
+ const visibleIds = visibleTabs.map((t) => t.id);
156
+ useEffect(() => {
157
+ if (visibleIds.length > 0 && !visibleIds.includes(activeTab)) {
158
+ setActiveTab(visibleIds[0]!);
159
+ }
160
+ }, [visibleIds.join(","), activeTab, setActiveTab]);
161
+
162
+ // Refresh current view based on active tab
163
+ const refreshCurrentView = useCallback((): void => {
164
+ if (!client) return;
165
+
166
+ if (activeTab === "manifests") {
167
+ fetchManifests(client);
168
+ } else if (activeTab === "alerts") {
169
+ fetchAlerts(effectiveZoneId, client);
170
+ } else if (activeTab === "credentials") {
171
+ const selected = manifests[selectedManifestIndex];
172
+ if (selected) {
173
+ fetchCredentials(selected.agent_id, client);
174
+ }
175
+ } else if (activeTab === "fraud") {
176
+ fetchFraudScores(effectiveZoneId, client);
177
+ if (effectiveZoneId) fetchConstraints(effectiveZoneId, client);
178
+ } else if (activeTab === "delegations") {
179
+ fetchDelegations(client, delegationFilter);
180
+ }
181
+ }, [client, activeTab, manifests, selectedManifestIndex, effectiveZoneId, delegationFilter, fetchManifests, fetchAlerts, fetchCredentials, fetchFraudScores, fetchDelegations]);
182
+
183
+ // Auto-fetch when tab changes
184
+ useEffect(() => {
185
+ refreshCurrentView();
186
+ // eslint-disable-next-line react-hooks/exhaustive-deps
187
+ }, [activeTab, client]);
188
+
189
+ // Shared list navigation (j/k/up/down/g/G) — switches per active tab
190
+ const listNav = listNavigationBindings({
191
+ getIndex: () => {
192
+ if (activeTab === "manifests") return selectedManifestIndex;
193
+ if (activeTab === "alerts") return selectedAlertIndex;
194
+ if (activeTab === "credentials") return selectedCredentialIndex;
195
+ if (activeTab === "fraud") return fraudFocus === "scores" ? selectedFraudIndex : selectedConstraintIndex;
196
+ if (activeTab === "delegations") return selectedDelegationIndex;
197
+ return 0;
198
+ },
199
+ setIndex: (i) => {
200
+ if (activeTab === "manifests") setSelectedManifestIndex(i);
201
+ else if (activeTab === "alerts") setSelectedAlertIndex(i);
202
+ else if (activeTab === "credentials") setSelectedCredentialIndex(i);
203
+ else if (activeTab === "fraud") {
204
+ if (fraudFocus === "scores") setSelectedFraudIndex(i);
205
+ else setSelectedConstraintIndex(i);
206
+ } else if (activeTab === "delegations") setSelectedDelegationIndex(i);
207
+ },
208
+ getLength: () => {
209
+ if (activeTab === "manifests") return manifests.length;
210
+ if (activeTab === "alerts") return alerts.length;
211
+ if (activeTab === "credentials") return credentials.length;
212
+ if (activeTab === "fraud") return fraudFocus === "scores" ? fraudScores.length : constraints.length;
213
+ if (activeTab === "delegations") return delegations.length;
214
+ return 0;
215
+ },
216
+ });
217
+
218
+ useKeyboard(overlayActive ? {} : {
219
+ ...listNav,
220
+ ...subTabCycleBindings(visibleTabs, activeTab, setActiveTab),
221
+ escape: () => {
222
+ if (overlay !== "none") {
223
+ setOverlay("none");
224
+ }
225
+ },
226
+ tab: () => {
227
+ if (activeTab === "fraud") {
228
+ setFraudFocus((f) => f === "scores" ? "constraints" : "scores");
229
+ return;
230
+ }
231
+ const ids = visibleTabs.map((t) => t.id);
232
+ const currentIdx = ids.indexOf(activeTab);
233
+ const nextIdx = (currentIdx + 1) % ids.length;
234
+ const nextTab = ids[nextIdx];
235
+ if (nextTab) {
236
+ setActiveTab(nextTab);
237
+ }
238
+ },
239
+ "shift+tab": () => {
240
+ const ids = visibleTabs.map((t) => t.id);
241
+ const currentIdx = ids.indexOf(activeTab);
242
+ const nextIdx = (currentIdx + 1) % ids.length;
243
+ const nextTab = ids[nextIdx];
244
+ if (nextTab) {
245
+ setActiveTab(nextTab);
246
+ }
247
+ },
248
+ return: () => {
249
+ // Manifests: fetch detail to load tuple entries
250
+ if (activeTab === "manifests" && client) {
251
+ const selected = manifests[selectedManifestIndex];
252
+ if (selected) {
253
+ fetchManifestDetail(selected.manifest_id, client);
254
+ }
255
+ }
256
+ },
257
+ r: () => refreshCurrentView(),
258
+ p: () => {
259
+ if (overlay === "none") {
260
+ setOverlay("permissionChecker");
261
+ }
262
+ },
263
+ n: () => {
264
+ if (activeTab === "delegations" && overlay === "none") {
265
+ setOverlay("delegationCreator");
266
+ } else if (activeTab === "fraud" && overlay === "none") {
267
+ setOverlay("constraintCreator");
268
+ }
269
+ },
270
+ d: async () => {
271
+ if (activeTab === "fraud" && overlay === "none" && client) {
272
+ const selected = constraints[selectedConstraintIndex];
273
+ if (selected) {
274
+ const ok = await confirm("Delete constraint?", `Delete governance constraint from ${selected.from_agent_id} to ${selected.to_agent_id} [${selected.constraint_type}].`);
275
+ if (!ok) return;
276
+ deleteConstraint(selected.id, client);
277
+ }
278
+ }
279
+ },
280
+ x: async () => {
281
+ if (activeTab === "delegations" && overlay === "none" && client) {
282
+ const selected = delegations[selectedDelegationIndex];
283
+ if (selected && selected.status === "active") {
284
+ revokeDelegation(selected.delegation_id, client);
285
+ }
286
+ } else if (activeTab === "credentials" && overlay === "none" && client) {
287
+ const selected = credentials[selectedCredentialIndex];
288
+ if (selected && selected.is_active) {
289
+ const ok = await confirm("Revoke credential?", "Revoke this credential. The holder will lose access.");
290
+ if (!ok) return;
291
+ revokeCredential(selected.credential_id, selected.subject_agent_id, client);
292
+ }
293
+ }
294
+ },
295
+ o: () => {
296
+ if (activeTab === "delegations" && overlay === "none") {
297
+ const selected = delegations[selectedDelegationIndex];
298
+ if (selected && selected.status === "active") {
299
+ setOverlay("delegationCompleter");
300
+ }
301
+ }
302
+ },
303
+ v: () => {
304
+ if (activeTab === "delegations" && overlay === "none") {
305
+ const selected = delegations[selectedDelegationIndex];
306
+ if (selected) {
307
+ setOverlay("delegationChainView");
308
+ }
309
+ }
310
+ },
311
+ w: () => {
312
+ if (activeTab === "delegations" && overlay === "none") {
313
+ const selected = delegations[selectedDelegationIndex];
314
+ if (selected) {
315
+ setOverlay("namespaceConfigView");
316
+ }
317
+ }
318
+ },
319
+ c: () => {
320
+ if (activeTab === "manifests" && overlay === "none") {
321
+ setOverlay("manifestCreator");
322
+ } else if (activeTab === "fraud" && client) {
323
+ // Compute fraud scores
324
+ computeFraudScores(effectiveZoneId, client);
325
+ }
326
+ },
327
+ "shift+x": async () => {
328
+ if (activeTab === "manifests" && overlay === "none" && client) {
329
+ const selected = manifests[selectedManifestIndex];
330
+ if (selected && selected.status === "active") {
331
+ const ok = await confirm("Revoke manifest?", "Revoke this access manifest. Active sessions may be terminated.");
332
+ if (!ok) return;
333
+ revokeManifest(selected.manifest_id, client);
334
+ }
335
+ }
336
+ },
337
+ "shift+r": () => {
338
+ if (!client || overlay !== "none") return;
339
+ if (activeTab === "alerts") {
340
+ const selected = alerts[selectedAlertIndex];
341
+ if (selected && !selected.resolved) {
342
+ resolveAlert(selected.alert_id, "tui-operator", effectiveZoneId, client);
343
+ }
344
+ }
345
+ },
346
+ f: () => {
347
+ if (activeTab === "delegations") {
348
+ const cycle: (string | null)[] = [null, "active", "revoked", "expired", "completed"];
349
+ const idx = cycle.indexOf(delegationFilter);
350
+ const next = cycle[(idx + 1) % cycle.length] ?? null;
351
+ setDelegationFilter(next);
352
+ if (client) fetchDelegations(client, next);
353
+ }
354
+ },
355
+ i: () => {
356
+ // Issue credential for the selected agent (from manifests tab's agent_id)
357
+ if (activeTab === "credentials" && client) {
358
+ const manifest = manifests[selectedManifestIndex];
359
+ if (manifest) {
360
+ issueCredential(manifest.agent_id, {}, client);
361
+ }
362
+ }
363
+ },
364
+ y: () => {
365
+ if (activeTab === "manifests") {
366
+ const selected = manifests[selectedManifestIndex];
367
+ if (selected) copy(selected.manifest_id);
368
+ } else if (activeTab === "delegations") {
369
+ const selected = delegations[selectedDelegationIndex];
370
+ if (selected) copy(selected.delegation_id);
371
+ }
372
+ },
373
+ "shift+c": () => {
374
+ // Fetch collusion rings (fraud tab)
375
+ if (activeTab === "fraud" && client) {
376
+ fetchCollusionRings(effectiveZoneId, client);
377
+ }
378
+ },
379
+ s: async () => {
380
+ // Suspend selected agent (fraud tab — selected by fraud score index)
381
+ if (activeTab === "fraud" && client) {
382
+ const selected = fraudScores[selectedFraudIndex];
383
+ if (selected) {
384
+ const ok = await confirm("Suspend agent?", "Suspend this agent. It will be unable to act until unsuspended.");
385
+ if (!ok) return;
386
+ suspendAgent(selected.agent_id, "Suspended via TUI", effectiveZoneId, client);
387
+ }
388
+ }
389
+ },
390
+ });
391
+
392
+ // Derive selected items for overlays
393
+ const selectedManifest = manifests[selectedManifestIndex];
394
+ const initialManifestId = selectedManifest?.manifest_id ?? "";
395
+ const selectedDelegation = delegations[selectedDelegationIndex];
396
+
397
+ const closeOverlay = (): void => setOverlay("none");
398
+
399
+ const OVERLAY_LABELS: Readonly<Record<OverlayMode, string>> = {
400
+ none: "",
401
+ permissionChecker: " | Permission Checker",
402
+ delegationCreator: " | New Delegation",
403
+ delegationCompleter: " | Complete Delegation",
404
+ delegationChainView: " | Delegation Chain",
405
+ namespaceConfigView: " | Namespace Editor",
406
+ manifestCreator: " | New Manifest",
407
+ constraintCreator: " | New Constraint",
408
+ };
409
+ const overlayLabel = OVERLAY_LABELS[overlay];
410
+
411
+ if (overlay !== "none") {
412
+ return (
413
+ <box height="100%" width="100%" flexDirection="column">
414
+ <box height={1} width="100%">
415
+ <text>
416
+ {visibleTabs.map((tab) => {
417
+ return tab.id === activeTab ? `[${tab.label}]` : ` ${tab.label} `;
418
+ }).join(" ")}
419
+ {overlayLabel}
420
+ </text>
421
+ </box>
422
+ <box flexGrow={1} borderStyle="single">
423
+ {overlay === "permissionChecker" && (
424
+ <PermissionChecker
425
+ initialManifestId={initialManifestId}
426
+ lastResult={lastPermissionCheck}
427
+ loading={permissionCheckLoading}
428
+ governanceCheck={governanceCheck}
429
+ governanceCheckLoading={governanceCheckLoading}
430
+ zoneId={effectiveZoneId}
431
+ onClose={closeOverlay}
432
+ />
433
+ )}
434
+ {overlay === "delegationCreator" && (
435
+ <DelegationCreator onClose={closeOverlay} />
436
+ )}
437
+ {overlay === "delegationCompleter" && (
438
+ <DelegationCompleter
439
+ delegationId={selectedDelegation?.delegation_id ?? ""}
440
+ onClose={closeOverlay}
441
+ />
442
+ )}
443
+ {overlay === "delegationChainView" && (
444
+ <DelegationChainView
445
+ delegationId={selectedDelegation?.delegation_id ?? ""}
446
+ onClose={closeOverlay}
447
+ />
448
+ )}
449
+ {overlay === "namespaceConfigView" && (
450
+ <NamespaceConfigView
451
+ delegationId={selectedDelegation?.delegation_id ?? ""}
452
+ onClose={closeOverlay}
453
+ />
454
+ )}
455
+ {overlay === "manifestCreator" && (
456
+ <ManifestCreator onClose={closeOverlay} />
457
+ )}
458
+ {overlay === "constraintCreator" && (
459
+ <ConstraintCreator
460
+ zoneId={effectiveZoneId ?? ""}
461
+ onClose={closeOverlay}
462
+ />
463
+ )}
464
+ </box>
465
+ </box>
466
+ );
467
+ }
468
+
469
+ // Tab-specific help text
470
+ const delegationFilterLabel = delegationFilter ? ` [${delegationFilter}]` : "";
471
+ const HELP: Readonly<Record<AccessTab, string>> = {
472
+ manifests: "j/k:navigate g/G:jump Enter:show entries c:new manifest Shift+X:revoke p:perm check y:copy Esc:close Tab:tab r:refresh q:quit",
473
+ alerts: "j/k:navigate g/G:jump Shift+R:resolve Esc:close Tab:tab r:refresh q:quit",
474
+ credentials: "j/k:navigate g/G:jump i:issue x:revoke Esc:close Tab:tab r:refresh q:quit",
475
+ fraud: "j/k:navigate g/G:jump c:compute Shift+C:collusion s:suspend n:new constraint d:delete Tab:focus Shift+Tab:tab Esc:close r:refresh q:quit",
476
+ delegations: `j/k:navigate g/G:jump n:new x:revoke o:complete v:chain w:namespace y:copy f:filter${delegationFilterLabel} Esc:close Tab:tab r:refresh q:quit`,
477
+ };
478
+
479
+ return (
480
+ <box height="100%" width="100%" flexDirection="column">
481
+ {/* Tab bar */}
482
+ <box height={1} width="100%">
483
+ <text>
484
+ {visibleTabs.map((tab) => {
485
+ return tab.id === activeTab ? `[${tab.label}]` : ` ${tab.label} `;
486
+ }).join(" ")}
487
+ </text>
488
+ </box>
489
+
490
+ {/* Permission evaluation result */}
491
+ {lastPermissionCheck && (
492
+ <box height={3} width="100%" borderStyle="single" borderColor={lastPermissionCheck.permission === "allow" ? statusColor.healthy : statusColor.error}>
493
+ <text style={textStyle({ fg: lastPermissionCheck.permission === "allow" ? statusColor.healthy : statusColor.error })}>
494
+ {` ${lastPermissionCheck.permission === "allow" ? "[ALLOW]" : "[DENY] "} tool=${lastPermissionCheck.tool_name} agent=${lastPermissionCheck.agent_id} manifest=${lastPermissionCheck.manifest_id}`}
495
+ </text>
496
+ </box>
497
+ )}
498
+
499
+ {/* Error display */}
500
+ {error && (
501
+ <box height={1} width="100%">
502
+ <text>{`Error: ${error}`}</text>
503
+ </box>
504
+ )}
505
+
506
+ {/* Detail content */}
507
+ <box flexGrow={1} borderStyle="single">
508
+ {activeTab === "manifests" && (
509
+ <ManifestList
510
+ manifests={manifests}
511
+ selectedIndex={selectedManifestIndex}
512
+ loading={manifestsLoading}
513
+ />
514
+ )}
515
+ {activeTab === "alerts" && (
516
+ <AlertList
517
+ alerts={alerts}
518
+ selectedIndex={selectedAlertIndex}
519
+ loading={alertsLoading}
520
+ />
521
+ )}
522
+ {activeTab === "credentials" && (
523
+ <CredentialList
524
+ credentials={credentials}
525
+ loading={credentialsLoading}
526
+ />
527
+ )}
528
+ {activeTab === "fraud" && (
529
+ <box height="100%" width="100%" flexDirection="column">
530
+ <box flexGrow={1} width="100%">
531
+ <FraudScoreView
532
+ scores={fraudScores}
533
+ selectedIndex={selectedFraudIndex}
534
+ loading={fraudScoresLoading}
535
+ />
536
+ </box>
537
+ <box flexDirection="column" width="100%">
538
+ <box height={1} width="100%">
539
+ <text>{"--- Collusion Rings ---"}</text>
540
+ </box>
541
+ {collusionLoading ? (
542
+ <box height={1} width="100%">
543
+ <text>Loading collusion rings...</text>
544
+ </box>
545
+ ) : (collusionRings as { confidence: number; members: string[]; ring_type?: string }[]).length === 0 ? (
546
+ <box height={1} width="100%">
547
+ <text style={textStyle({ dim: true })}>No collusion rings detected</text>
548
+ </box>
549
+ ) : (
550
+ (collusionRings as { confidence: number; members: string[]; ring_type?: string }[]).map((ring, i) => {
551
+ const conf = ring.confidence;
552
+ const confColor = conf > 0.7 ? statusColor.error : conf >= 0.4 ? statusColor.warning : undefined;
553
+ const confStr = conf.toFixed(3);
554
+ const members = ring.members.join(", ");
555
+ const ringType = ring.ring_type ?? "unknown";
556
+ return (
557
+ <box key={`ring-${i}`} height={1} width="100%">
558
+ <text>
559
+ {" "}
560
+ <span style={textStyle({ fg: confColor, dim: conf < 0.4 })}>{confStr}</span>
561
+ {` [${ringType}] ${members}`}
562
+ </text>
563
+ </box>
564
+ );
565
+ })
566
+ )}
567
+ </box>
568
+ <box flexDirection="column" width="100%">
569
+ <box height={1} width="100%">
570
+ <text>{"--- Governance Constraints ---"}</text>
571
+ </box>
572
+ <ConstraintList
573
+ constraints={constraints}
574
+ selectedIndex={selectedConstraintIndex}
575
+ loading={constraintsLoading}
576
+ />
577
+ </box>
578
+ </box>
579
+ )}
580
+ {activeTab === "delegations" && (
581
+ <DelegationList
582
+ delegations={delegations}
583
+ selectedIndex={selectedDelegationIndex}
584
+ loading={delegationsLoading}
585
+ />
586
+ )}
587
+ </box>
588
+
589
+ {/* Help bar */}
590
+ <box height={1} width="100%">
591
+ {copied
592
+ ? <text style={textStyle({ fg: "green" })}>Copied!</text>
593
+ : <text>{HELP[activeTab]}</text>}
594
+ </box>
595
+ </box>
596
+ );
597
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Governance alert list with severity icons and selection for resolve action.
3
+ */
4
+
5
+ import React from "react";
6
+ import type { GovernanceAlert } from "../../stores/access-store.js";
7
+ import { EmptyState } from "../../shared/components/empty-state.js";
8
+
9
+ interface AlertListProps {
10
+ readonly alerts: readonly GovernanceAlert[];
11
+ readonly selectedIndex: number;
12
+ readonly loading: boolean;
13
+ }
14
+
15
+ const SEVERITY_ICONS: Readonly<Record<GovernanceAlert["severity"], string>> = {
16
+ critical: "●",
17
+ warning: "◐",
18
+ info: "○",
19
+ };
20
+
21
+ function formatTimestamp(ts: string): string {
22
+ try {
23
+ return new Date(ts).toLocaleString();
24
+ } catch {
25
+ return ts;
26
+ }
27
+ }
28
+
29
+ export function AlertList({ alerts, selectedIndex, loading }: AlertListProps): React.ReactNode {
30
+ if (loading) {
31
+ return (
32
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
33
+ <text>Loading alerts...</text>
34
+ </box>
35
+ );
36
+ }
37
+
38
+ if (alerts.length === 0) {
39
+ return <EmptyState message="No alerts found." />;
40
+ }
41
+
42
+ return (
43
+ <scrollbox height="100%" width="100%">
44
+ {/* Header */}
45
+ <box height={1} width="100%">
46
+ <text>{" SEV TYPE AGENT DETAILS STATUS TIME"}</text>
47
+ </box>
48
+ <box height={1} width="100%">
49
+ <text>{" --- --------------- --------------- ------------------------------------- --------- ----"}</text>
50
+ </box>
51
+
52
+ {/* Rows */}
53
+ {alerts.map((alert, i) => {
54
+ const isSelected = i === selectedIndex;
55
+ const prefix = isSelected ? "> " : " ";
56
+ const icon = SEVERITY_ICONS[alert.severity] ?? "?";
57
+ const agent = alert.agent_id ?? "system";
58
+ const detailStr = typeof alert.details === "string"
59
+ ? alert.details
60
+ : JSON.stringify(alert.details ?? "");
61
+ const details = detailStr.length > 37
62
+ ? `${detailStr.slice(0, 34)}...`
63
+ : detailStr;
64
+ const status = alert.resolved ? "resolved" : "active";
65
+ const time = alert.created_at ? formatTimestamp(alert.created_at) : "-";
66
+
67
+ return (
68
+ <box key={alert.alert_id} height={1} width="100%">
69
+ <text>
70
+ {`${prefix}${icon} ${alert.alert_type.padEnd(15)} ${agent.padEnd(15)} ${details.padEnd(37)} ${status.padEnd(9)} ${time}`}
71
+ </text>
72
+ </box>
73
+ );
74
+ })}
75
+ </scrollbox>
76
+ );
77
+ }