@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,237 @@
1
+ /**
2
+ * Delegation creator form: create a new delegation with namespace scope.
3
+ *
4
+ * Tab cycles between fields.
5
+ * Enter submits via the delegation store's createDelegation().
6
+ * Escape cancels and returns to normal mode.
7
+ */
8
+
9
+ import React, { useState, useCallback } from "react";
10
+ import { useDelegationStore } from "../../stores/delegation-store.js";
11
+ import type { DelegationCreateResponse } from "../../stores/delegation-store.js";
12
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
13
+ import { useApi } from "../../shared/hooks/use-api.js";
14
+
15
+ type ActiveField =
16
+ | "workerId"
17
+ | "workerName"
18
+ | "namespaceMode"
19
+ | "scopePrefix"
20
+ | "intent"
21
+ | "canSubDelegate"
22
+ | "ttlSeconds"
23
+ | "removeGrants"
24
+ | "addGrants"
25
+ | "readonlyPaths"
26
+ | "scopeOps"
27
+ | "minTrustScore";
28
+
29
+ const FIELD_ORDER: readonly ActiveField[] = [
30
+ "workerId",
31
+ "workerName",
32
+ "namespaceMode",
33
+ "scopePrefix",
34
+ "intent",
35
+ "canSubDelegate",
36
+ "ttlSeconds",
37
+ "removeGrants",
38
+ "addGrants",
39
+ "readonlyPaths",
40
+ "scopeOps",
41
+ "minTrustScore",
42
+ ];
43
+
44
+ interface DelegationCreatorProps {
45
+ readonly onClose: () => void;
46
+ }
47
+
48
+ export function DelegationCreator({ onClose }: DelegationCreatorProps): React.ReactNode {
49
+ const client = useApi();
50
+ const createDelegation = useDelegationStore((s) => s.createDelegation);
51
+ const delegationsLoading = useDelegationStore((s) => s.delegationsLoading);
52
+ const lastResult = useDelegationStore((s) => s.lastDelegationCreate);
53
+ const error = useDelegationStore((s) => s.error);
54
+
55
+ const [workerId, setWorkerId] = useState("");
56
+ const [workerName, setWorkerName] = useState("");
57
+ const [namespaceMode, setNamespaceMode] = useState("clean");
58
+ const [scopePrefix, setScopePrefix] = useState("");
59
+ const [intent, setIntent] = useState("");
60
+ const [canSubDelegate, setCanSubDelegate] = useState("no");
61
+ const [ttlSeconds, setTtlSeconds] = useState("");
62
+ const [removeGrants, setRemoveGrants] = useState("");
63
+ const [addGrants, setAddGrants] = useState("");
64
+ const [readonlyPaths, setReadonlyPaths] = useState("");
65
+ const [scopeOps, setScopeOps] = useState("");
66
+ const [minTrustScore, setMinTrustScore] = useState("");
67
+ const [activeField, setActiveField] = useState<ActiveField>("workerId");
68
+
69
+ const setters: Readonly<Record<ActiveField, (fn: (b: string) => string) => void>> = {
70
+ workerId: (fn) => setWorkerId((b) => fn(b)),
71
+ workerName: (fn) => setWorkerName((b) => fn(b)),
72
+ namespaceMode: (fn) => setNamespaceMode((b) => fn(b)),
73
+ scopePrefix: (fn) => setScopePrefix((b) => fn(b)),
74
+ intent: (fn) => setIntent((b) => fn(b)),
75
+ canSubDelegate: (fn) => setCanSubDelegate((b) => fn(b)),
76
+ ttlSeconds: (fn) => setTtlSeconds((b) => fn(b)),
77
+ removeGrants: (fn) => setRemoveGrants((b) => fn(b)),
78
+ addGrants: (fn) => setAddGrants((b) => fn(b)),
79
+ readonlyPaths: (fn) => setReadonlyPaths((b) => fn(b)),
80
+ scopeOps: (fn) => setScopeOps((b) => fn(b)),
81
+ minTrustScore: (fn) => setMinTrustScore((b) => fn(b)),
82
+ };
83
+
84
+ const handleSubmit = useCallback(() => {
85
+ if (!client || !workerId.trim() || !workerName.trim() || !intent.trim()) return;
86
+ const ttl = ttlSeconds.trim() ? parseInt(ttlSeconds.trim(), 10) : undefined;
87
+ createDelegation(
88
+ {
89
+ worker_id: workerId.trim(),
90
+ worker_name: workerName.trim(),
91
+ namespace_mode: namespaceMode.trim() || "clean",
92
+ scope_prefix: scopePrefix.trim() || undefined,
93
+ intent: intent.trim(),
94
+ can_sub_delegate: canSubDelegate.toLowerCase() === "yes",
95
+ ttl_seconds: Number.isFinite(ttl) ? ttl : undefined,
96
+ remove_grants: removeGrants.trim() ? removeGrants.split(",").map(s => s.trim()) : undefined,
97
+ add_grants: addGrants.trim() ? addGrants.split(",").map(s => s.trim()) : undefined,
98
+ readonly_paths: readonlyPaths.trim() ? readonlyPaths.split(",").map(s => s.trim()) : undefined,
99
+ scope: scopeOps.trim() ? {
100
+ allowed_operations: scopeOps.split(",").map(s => s.trim()),
101
+ } : undefined,
102
+ min_trust_score: minTrustScore.trim() ? parseFloat(minTrustScore.trim()) : undefined,
103
+ },
104
+ client,
105
+ );
106
+ }, [client, workerId, workerName, namespaceMode, scopePrefix, intent, canSubDelegate, ttlSeconds, removeGrants, addGrants, readonlyPaths, scopeOps, minTrustScore, createDelegation]);
107
+
108
+ const handleUnhandledKey = useCallback(
109
+ (keyName: string) => {
110
+ const setter = setters[activeField];
111
+ if (keyName.length === 1) {
112
+ setter((b) => b + keyName);
113
+ } else if (keyName === "space") {
114
+ setter((b) => b + " ");
115
+ }
116
+ },
117
+ [activeField],
118
+ );
119
+
120
+ useKeyboard(
121
+ {
122
+ return: handleSubmit,
123
+ escape: onClose,
124
+ backspace: () => {
125
+ setters[activeField]((b) => b.slice(0, -1));
126
+ },
127
+ tab: () => {
128
+ const currentIdx = FIELD_ORDER.indexOf(activeField);
129
+ const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
130
+ const next = FIELD_ORDER[nextIdx];
131
+ if (next) {
132
+ setActiveField(next);
133
+ }
134
+ },
135
+ },
136
+ handleUnhandledKey,
137
+ );
138
+
139
+ const cursor = "\u2588";
140
+
141
+ const fields: readonly { readonly key: ActiveField; readonly label: string; readonly value: string; readonly hint?: string }[] = [
142
+ { key: "workerId", label: "Worker ID ", value: workerId },
143
+ { key: "workerName", label: "Worker Name ", value: workerName },
144
+ { key: "namespaceMode", label: "Namespace Mode ", value: namespaceMode, hint: "copy|clean|shared" },
145
+ { key: "scopePrefix", label: "Scope Prefix ", value: scopePrefix, hint: "e.g. files/reports/" },
146
+ { key: "intent", label: "Intent ", value: intent },
147
+ { key: "canSubDelegate", label: "Sub-delegate? ", value: canSubDelegate, hint: "yes|no" },
148
+ { key: "ttlSeconds", label: "TTL (seconds) ", value: ttlSeconds, hint: "1-86400, blank=none" },
149
+ { key: "removeGrants", label: "Remove Grants ", value: removeGrants, hint: "comma-separated paths" },
150
+ { key: "addGrants", label: "Add Grants ", value: addGrants, hint: "comma-separated paths" },
151
+ { key: "readonlyPaths", label: "Readonly Paths ", value: readonlyPaths, hint: "comma-separated paths" },
152
+ { key: "scopeOps", label: "Scope Ops ", value: scopeOps, hint: "comma-separated operations" },
153
+ { key: "minTrustScore", label: "Min Trust Score", value: minTrustScore, hint: "0.0-1.0, blank=none" },
154
+ ];
155
+
156
+ const showResult = lastResult && !delegationsLoading;
157
+
158
+ return (
159
+ <box height="100%" width="100%" flexDirection="column">
160
+ {/* Form fields */}
161
+ {fields.map((f) => (
162
+ <box key={f.key} height={1} width="100%">
163
+ <text>
164
+ {activeField === f.key
165
+ ? `> ${f.label}: ${f.value}${cursor}${f.hint ? ` (${f.hint})` : ""}`
166
+ : ` ${f.label}: ${f.value}${f.hint && !f.value ? ` (${f.hint})` : ""}`}
167
+ </text>
168
+ </box>
169
+ ))}
170
+
171
+ {delegationsLoading && (
172
+ <box height={1} width="100%">
173
+ <text>Creating delegation...</text>
174
+ </box>
175
+ )}
176
+
177
+ {error && !delegationsLoading && (
178
+ <box height={1} width="100%">
179
+ <text>{`Error: ${error}`}</text>
180
+ </box>
181
+ )}
182
+
183
+ {showResult && (
184
+ <DelegationCreateResult result={lastResult} />
185
+ )}
186
+
187
+ <box height={1} width="100%">
188
+ <text>
189
+ {"Tab:next field Enter:create Escape:cancel Backspace:delete"}
190
+ </text>
191
+ </box>
192
+ </box>
193
+ );
194
+ }
195
+
196
+ function DelegationCreateResult({
197
+ result,
198
+ }: {
199
+ readonly result: DelegationCreateResponse;
200
+ }): React.ReactNode {
201
+ return (
202
+ <box flexDirection="column" width="100%">
203
+ <box height={1} width="100%">
204
+ <text>{"--- Delegation Created ---"}</text>
205
+ </box>
206
+ <box height={1} width="100%">
207
+ <text>{` ID: ${result.delegation_id}`}</text>
208
+ </box>
209
+ <box height={1} width="100%">
210
+ <text>{` Worker: ${result.worker_agent_id}`}</text>
211
+ </box>
212
+ <box height={1} width="100%">
213
+ <text>{` Mode: ${result.delegation_mode}`}</text>
214
+ </box>
215
+ <box height={1} width="100%">
216
+ <text>{` Key: ${result.api_key}`}</text>
217
+ </box>
218
+ {result.expires_at && (
219
+ <box height={1} width="100%">
220
+ <text>{` Expiry: ${result.expires_at}`}</text>
221
+ </box>
222
+ )}
223
+ {result.mount_table.length > 0 && (
224
+ <>
225
+ <box height={1} width="100%">
226
+ <text>{" Mount table:"}</text>
227
+ </box>
228
+ {result.mount_table.map((path, i) => (
229
+ <box key={`mt-${i}`} height={1} width="100%">
230
+ <text>{` ${path}`}</text>
231
+ </box>
232
+ ))}
233
+ </>
234
+ )}
235
+ </box>
236
+ );
237
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Delegation list: shows delegations with scope_prefix as namespace view,
3
+ * agent hierarchy, mode, status, and lease expiry.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { DelegationItem } from "../../stores/access-store.js";
8
+ import { EmptyState } from "../../shared/components/empty-state.js";
9
+
10
+ interface DelegationListProps {
11
+ readonly delegations: readonly DelegationItem[];
12
+ readonly selectedIndex: number;
13
+ readonly loading: boolean;
14
+ }
15
+
16
+ function shortId(id: string): string {
17
+ if (id.length <= 14) return id;
18
+ return `${id.slice(0, 11)}..`;
19
+ }
20
+
21
+ function formatExpiry(ts: string | null): string {
22
+ if (!ts) return "-";
23
+ try {
24
+ return new Date(ts).toLocaleString();
25
+ } catch {
26
+ return ts;
27
+ }
28
+ }
29
+
30
+ export function DelegationList({
31
+ delegations,
32
+ selectedIndex,
33
+ loading,
34
+ }: DelegationListProps): React.ReactNode {
35
+ if (loading) {
36
+ return (
37
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
38
+ <text>Loading delegations...</text>
39
+ </box>
40
+ );
41
+ }
42
+
43
+ if (delegations.length === 0) {
44
+ return <EmptyState message="No delegations yet." hint="Press n to create one." />;
45
+ }
46
+
47
+ return (
48
+ <scrollbox height="100%" width="100%">
49
+ {/* Header */}
50
+ <box height={1} width="100%">
51
+ <text>{" AGENT PARENT SCOPE PREFIX MODE STATUS DEPTH SUB-DEL LEASE EXPIRES"}</text>
52
+ </box>
53
+ <box height={1} width="100%">
54
+ <text>{" ------------- ------------- ------------------- --------- --------- ----- ------- -----------------"}</text>
55
+ </box>
56
+
57
+ {/* Rows */}
58
+ {delegations.map((d, i) => {
59
+ const isSelected = i === selectedIndex;
60
+ const prefix = isSelected ? "> " : " ";
61
+ const scope = d.scope_prefix ?? "*";
62
+ const subDel = d.can_sub_delegate ? "yes" : "no";
63
+
64
+ return (
65
+ <box key={d.delegation_id} height={1} width="100%">
66
+ <text>
67
+ {`${prefix}${shortId(d.agent_id).padEnd(13)} ${shortId(d.parent_agent_id).padEnd(13)} ${scope.padEnd(19)} ${d.delegation_mode.padEnd(9)} ${d.status.padEnd(9)} ${String(d.depth).padEnd(5)} ${subDel.padEnd(7)} ${formatExpiry(d.lease_expires_at)}`}
68
+ </text>
69
+ </box>
70
+ );
71
+ })}
72
+ </scrollbox>
73
+ );
74
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Fraud score list: displays per-agent fraud scores with component breakdown.
3
+ * Data from GET /api/v2/governance/fraud-scores.
4
+ */
5
+
6
+ import React from "react";
7
+ import type { FraudScore } from "../../stores/access-store.js";
8
+
9
+ interface FraudScoreViewProps {
10
+ readonly scores: readonly FraudScore[];
11
+ readonly selectedIndex: number;
12
+ readonly loading: boolean;
13
+ }
14
+
15
+ function shortId(id: string): string {
16
+ if (id.length <= 16) return id;
17
+ return `${id.slice(0, 12)}..`;
18
+ }
19
+
20
+ function formatTimestamp(ts: string): string {
21
+ try {
22
+ return new Date(ts).toLocaleString();
23
+ } catch {
24
+ return ts;
25
+ }
26
+ }
27
+
28
+ export function FraudScoreView({
29
+ scores,
30
+ selectedIndex,
31
+ loading,
32
+ }: FraudScoreViewProps): React.ReactNode {
33
+ if (loading) {
34
+ return (
35
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
36
+ <text>Loading fraud scores...</text>
37
+ </box>
38
+ );
39
+ }
40
+
41
+ if (scores.length === 0) {
42
+ return (
43
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
44
+ <text>No fraud scores. Press 'c' to compute scores for this zone.</text>
45
+ </box>
46
+ );
47
+ }
48
+
49
+ const selected = scores[selectedIndex];
50
+
51
+ return (
52
+ <box height="100%" width="100%" flexDirection="column">
53
+ {/* Header */}
54
+ <box height={1} width="100%">
55
+ <text>{`Fraud Scores: ${scores.length} agents`}</text>
56
+ </box>
57
+ <box height={1} width="100%">
58
+ <text>{" AGENT ZONE SCORE COMPUTED"}</text>
59
+ </box>
60
+ <box height={1} width="100%">
61
+ <text>{" --------------- --------------- ------ ------------------"}</text>
62
+ </box>
63
+
64
+ {/* Rows */}
65
+ <scrollbox flexGrow={1} width="100%">
66
+ {scores.map((s, i) => {
67
+ const isSelected = i === selectedIndex;
68
+ const prefix = isSelected ? "> " : " ";
69
+ const scoreStr = s.score.toFixed(3).padEnd(6);
70
+
71
+ return (
72
+ <box key={`${s.agent_id}-${s.zone_id}`} height={1} width="100%">
73
+ <text>
74
+ {`${prefix}${shortId(s.agent_id).padEnd(15)} ${shortId(s.zone_id).padEnd(15)} ${scoreStr} ${formatTimestamp(s.computed_at)}`}
75
+ </text>
76
+ </box>
77
+ );
78
+ })}
79
+ </scrollbox>
80
+
81
+ {/* Selected score component breakdown */}
82
+ {selected && Object.keys(selected.components).length > 0 && (
83
+ <box height={4} width="100%" flexDirection="column">
84
+ <text>{`Components for ${shortId(selected.agent_id)}:`}</text>
85
+ <text>
86
+ {Object.entries(selected.components)
87
+ .map(([k, v]) => ` ${k}=${typeof v === "number" ? v.toFixed(3) : v}`)
88
+ .join(" ")}
89
+ </text>
90
+ </box>
91
+ )}
92
+ </box>
93
+ );
94
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Manifest creator form: create a new access manifest with a single entry.
3
+ *
4
+ * Tab cycles between fields.
5
+ * Enter submits via the store's createManifest().
6
+ * Escape cancels and returns to normal mode.
7
+ */
8
+
9
+ import React, { useState, useCallback } from "react";
10
+ import { useAccessStore } from "../../stores/access-store.js";
11
+ import { useKeyboard } from "../../shared/hooks/use-keyboard.js";
12
+ import { useApi } from "../../shared/hooks/use-api.js";
13
+
14
+ type ActiveField =
15
+ | "agentId"
16
+ | "name"
17
+ | "toolPattern"
18
+ | "permission"
19
+ | "maxCallsPerMinute"
20
+ | "validFrom"
21
+ | "validUntil";
22
+
23
+ const FIELD_ORDER: readonly ActiveField[] = [
24
+ "agentId",
25
+ "name",
26
+ "toolPattern",
27
+ "permission",
28
+ "maxCallsPerMinute",
29
+ "validFrom",
30
+ "validUntil",
31
+ ];
32
+
33
+ interface ManifestCreatorProps {
34
+ readonly onClose: () => void;
35
+ }
36
+
37
+ export function ManifestCreator({ onClose }: ManifestCreatorProps): React.ReactNode {
38
+ const client = useApi();
39
+ const createManifest = useAccessStore((s) => s.createManifest);
40
+ const manifestsLoading = useAccessStore((s) => s.manifestsLoading);
41
+ const error = useAccessStore((s) => s.error);
42
+
43
+ const [agentId, setAgentId] = useState("");
44
+ const [name, setName] = useState("");
45
+ const [toolPattern, setToolPattern] = useState("");
46
+ const [permission, setPermission] = useState("allow");
47
+ const [maxCallsPerMinute, setMaxCallsPerMinute] = useState("");
48
+ const [validFrom, setValidFrom] = useState("");
49
+ const [validUntil, setValidUntil] = useState("");
50
+ const [activeField, setActiveField] = useState<ActiveField>("agentId");
51
+ const [submitted, setSubmitted] = useState(false);
52
+
53
+ const setters: Readonly<Record<ActiveField, (fn: (b: string) => string) => void>> = {
54
+ agentId: (fn) => setAgentId((b) => fn(b)),
55
+ name: (fn) => setName((b) => fn(b)),
56
+ toolPattern: (fn) => setToolPattern((b) => fn(b)),
57
+ permission: (fn) => setPermission((b) => fn(b)),
58
+ maxCallsPerMinute: (fn) => setMaxCallsPerMinute((b) => fn(b)),
59
+ validFrom: (fn) => setValidFrom((b) => fn(b)),
60
+ validUntil: (fn) => setValidUntil((b) => fn(b)),
61
+ };
62
+
63
+ const handleSubmit = useCallback(() => {
64
+ if (!client || !agentId.trim() || !name.trim() || !toolPattern.trim()) return;
65
+ const maxCalls = maxCallsPerMinute.trim() ? parseInt(maxCallsPerMinute.trim(), 10) : undefined;
66
+ const entry: { tool_pattern: string; permission: string; max_calls_per_minute?: number } = {
67
+ tool_pattern: toolPattern.trim(),
68
+ permission: permission.trim() || "allow",
69
+ };
70
+ if (Number.isFinite(maxCalls)) {
71
+ entry.max_calls_per_minute = maxCalls;
72
+ }
73
+ createManifest(
74
+ {
75
+ agent_id: agentId.trim(),
76
+ name: name.trim(),
77
+ entries: [entry],
78
+ valid_from: validFrom.trim() || undefined,
79
+ valid_until: validUntil.trim() || undefined,
80
+ },
81
+ client,
82
+ );
83
+ setSubmitted(true);
84
+ }, [client, agentId, name, toolPattern, permission, maxCallsPerMinute, validFrom, validUntil, createManifest]);
85
+
86
+ const handleUnhandledKey = useCallback(
87
+ (keyName: string) => {
88
+ const setter = setters[activeField];
89
+ if (keyName.length === 1) {
90
+ setter((b) => b + keyName);
91
+ } else if (keyName === "space") {
92
+ setter((b) => b + " ");
93
+ }
94
+ },
95
+ [activeField],
96
+ );
97
+
98
+ useKeyboard(
99
+ {
100
+ return: handleSubmit,
101
+ escape: onClose,
102
+ backspace: () => {
103
+ setters[activeField]((b) => b.slice(0, -1));
104
+ },
105
+ tab: () => {
106
+ const currentIdx = FIELD_ORDER.indexOf(activeField);
107
+ const nextIdx = (currentIdx + 1) % FIELD_ORDER.length;
108
+ const next = FIELD_ORDER[nextIdx];
109
+ if (next) {
110
+ setActiveField(next);
111
+ }
112
+ },
113
+ },
114
+ handleUnhandledKey,
115
+ );
116
+
117
+ const cursor = "\u2588";
118
+
119
+ const fields: readonly { readonly key: ActiveField; readonly label: string; readonly value: string; readonly hint?: string }[] = [
120
+ { key: "agentId", label: "Agent ID ", value: agentId },
121
+ { key: "name", label: "Manifest Name ", value: name },
122
+ { key: "toolPattern", label: "Tool Pattern ", value: toolPattern, hint: "e.g. tool:* or tool:read" },
123
+ { key: "permission", label: "Permission ", value: permission, hint: "allow|deny" },
124
+ { key: "maxCallsPerMinute", label: "Max Calls/Min ", value: maxCallsPerMinute, hint: "blank=unlimited" },
125
+ { key: "validFrom", label: "Valid From ", value: validFrom, hint: "ISO 8601, blank=now" },
126
+ { key: "validUntil", label: "Valid Until ", value: validUntil, hint: "ISO 8601, blank=none" },
127
+ ];
128
+
129
+ return (
130
+ <box height="100%" width="100%" flexDirection="column">
131
+ {/* Form fields */}
132
+ {fields.map((f) => (
133
+ <box key={f.key} height={1} width="100%">
134
+ <text>
135
+ {activeField === f.key
136
+ ? `> ${f.label}: ${f.value}${cursor}${f.hint ? ` (${f.hint})` : ""}`
137
+ : ` ${f.label}: ${f.value}${f.hint && !f.value ? ` (${f.hint})` : ""}`}
138
+ </text>
139
+ </box>
140
+ ))}
141
+
142
+ {manifestsLoading && (
143
+ <box height={1} width="100%">
144
+ <text>Creating manifest...</text>
145
+ </box>
146
+ )}
147
+
148
+ {error && !manifestsLoading && (
149
+ <box height={1} width="100%">
150
+ <text>{`Error: ${error}`}</text>
151
+ </box>
152
+ )}
153
+
154
+ {submitted && !manifestsLoading && !error && (
155
+ <box height={1} width="100%">
156
+ <text>Manifest created successfully. Press Escape to close.</text>
157
+ </box>
158
+ )}
159
+
160
+ <box height={1} width="100%">
161
+ <text>
162
+ {"Tab:next field Enter:create Escape:cancel Backspace:delete"}
163
+ </text>
164
+ </box>
165
+ </box>
166
+ );
167
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Access manifest list: shows manifests with name, agent, zone, status, entries count, validity.
3
+ * When entries are loaded (via fetchManifestDetail), shows the tuple entries inline
4
+ * for the selected manifest — this serves as the tuple browser.
5
+ */
6
+
7
+ import React from "react";
8
+ import type { AccessManifest } from "../../stores/access-store.js";
9
+ import { EmptyState } from "../../shared/components/empty-state.js";
10
+
11
+ interface ManifestListProps {
12
+ readonly manifests: readonly AccessManifest[];
13
+ readonly selectedIndex: number;
14
+ readonly loading: boolean;
15
+ }
16
+
17
+ function shortId(id: string): string {
18
+ if (id.length <= 16) return id;
19
+ return `${id.slice(0, 12)}..`;
20
+ }
21
+
22
+ function formatTimestamp(ts: string): string {
23
+ try {
24
+ return new Date(ts).toLocaleString();
25
+ } catch {
26
+ return ts;
27
+ }
28
+ }
29
+
30
+ export function ManifestList({
31
+ manifests,
32
+ selectedIndex,
33
+ loading,
34
+ }: ManifestListProps): React.ReactNode {
35
+ if (loading) {
36
+ return (
37
+ <box height="100%" width="100%" justifyContent="center" alignItems="center">
38
+ <text>Loading manifests...</text>
39
+ </box>
40
+ );
41
+ }
42
+
43
+ if (manifests.length === 0) {
44
+ return <EmptyState message="No manifests found." hint="Press c to create a manifest." />;
45
+ }
46
+
47
+ const selected = manifests[selectedIndex];
48
+ const entries = selected?.entries;
49
+
50
+ return (
51
+ <box height="100%" width="100%" flexDirection="column">
52
+ <scrollbox flexGrow={1} width="100%">
53
+ {/* Header */}
54
+ <box height={1} width="100%">
55
+ <text>{" NAME AGENT ZONE STATUS ENTRIES VALID FROM VALID UNTIL"}</text>
56
+ </box>
57
+ <box height={1} width="100%">
58
+ <text>{" --------------- --------------- --------------- --------- ------- ----------------- -----------------"}</text>
59
+ </box>
60
+
61
+ {/* Rows */}
62
+ {manifests.map((m, i) => {
63
+ const isSelected = i === selectedIndex;
64
+ const prefix = isSelected ? "> " : " ";
65
+ const entriesCount = String(m.entries?.length ?? "-");
66
+
67
+ return (
68
+ <box key={m.manifest_id} height={1} width="100%">
69
+ <text>
70
+ {`${prefix}${shortId(m.name).padEnd(15)} ${shortId(m.agent_id).padEnd(15)} ${shortId(m.zone_id).padEnd(15)} ${m.status.padEnd(9)} ${entriesCount.padEnd(7)} ${formatTimestamp(m.valid_from).padEnd(17)} ${formatTimestamp(m.valid_until)}`}
71
+ </text>
72
+ </box>
73
+ );
74
+ })}
75
+ </scrollbox>
76
+
77
+ {/* Tuple entries for selected manifest (shown when loaded via Enter) */}
78
+ {entries && entries.length > 0 && (
79
+ <box height={Math.min(entries.length + 2, 8)} width="100%" flexDirection="column" borderStyle="single">
80
+ <box height={1} width="100%">
81
+ <text>{`Entries (tuples) for ${selected.name}:`}</text>
82
+ </box>
83
+ {entries.map((e, i) => {
84
+ const rateStr = e.max_calls_per_minute
85
+ ? ` rate=${e.max_calls_per_minute}/min`
86
+ : "";
87
+ return (
88
+ <box key={`${e.tool_pattern}-${i}`} height={1} width="100%">
89
+ <text>
90
+ {` ${e.tool_pattern.padEnd(30)} ${e.permission.padEnd(6)}${rateStr}`}
91
+ </text>
92
+ </box>
93
+ );
94
+ })}
95
+ </box>
96
+ )}
97
+
98
+ {entries !== undefined && entries.length === 0 && (
99
+ <box height={1} width="100%">
100
+ <text>{`No entries (tuples) for ${selected?.name ?? "manifest"}`}</text>
101
+ </box>
102
+ )}
103
+ </box>
104
+ );
105
+ }