@hediet/linkrpc-cli 0.0.1
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.
- package/README.md +370 -0
- package/dist/chunks/cli-D_-vlAa2.js +6073 -0
- package/dist/chunks/cli-D_-vlAa2.js.map +1 -0
- package/dist/chunks/runApprovalUi-BvJZdjKm.js +715 -0
- package/dist/chunks/runApprovalUi-BvJZdjKm.js.map +1 -0
- package/dist/chunks/runComplete-BkQwzPbF.js +3949 -0
- package/dist/chunks/runComplete-BkQwzPbF.js.map +1 -0
- package/dist/chunks/runUi-D-8LMU4F.js +1143 -0
- package/dist/chunks/runUi-D-8LMU4F.js.map +1 -0
- package/dist/chunks/scroll-BTzAYh4N.js +93 -0
- package/dist/chunks/scroll-BTzAYh4N.js.map +1 -0
- package/dist/hub.d.ts +1 -0
- package/dist/hub.js +8 -0
- package/dist/hub.js.map +1 -0
- package/dist/index.d.ts +381 -0
- package/dist/index.js +2 -0
- package/dist/linkrpc.d.ts +1 -0
- package/dist/linkrpc.js +8 -0
- package/dist/linkrpc.js.map +1 -0
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.js +8 -0
- package/dist/rpc.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { a as observableValue, n as approvalDetailLines } from "./cli-D_-vlAa2.js";
|
|
2
|
+
import { n as useObservable, t as useScroll } from "./scroll-BTzAYh4N.js";
|
|
3
|
+
import { jcsCanonicalize } from "@hediet/linkrpc";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Box, Text, render, useApp, useInput, useStdout } from "ink";
|
|
6
|
+
import TextInput from "ink-text-input";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/approval-ui/ApprovalApp.tsx
|
|
9
|
+
const HEADER_HEIGHT = 2;
|
|
10
|
+
const FOOTER_HEIGHT = 2;
|
|
11
|
+
const PANE_CHROME_HEIGHT = 3;
|
|
12
|
+
const MIN_FULL_HEIGHT = 13;
|
|
13
|
+
const SPLIT_COLUMNS = 96;
|
|
14
|
+
const ApprovalApp = ({ model }) => {
|
|
15
|
+
const requests = useObservable(model.requests);
|
|
16
|
+
const selectedId = useObservable(model.selectedRequestId);
|
|
17
|
+
const connectionState = useObservable(model.connectionState);
|
|
18
|
+
const mode = useObservable(model.mode);
|
|
19
|
+
const busy = useObservable(model.busy);
|
|
20
|
+
const message = useObservable(model.message);
|
|
21
|
+
const denialReason = useObservable(model.denialReason);
|
|
22
|
+
const detailOffset = useObservable(model.detailOffset);
|
|
23
|
+
const preparedApproval = useObservable(model.preparedApproval);
|
|
24
|
+
const size = useTerminalSize();
|
|
25
|
+
const { exit } = useApp();
|
|
26
|
+
const selectedIndex = requests.findIndex((item) => item.id === selectedId);
|
|
27
|
+
const selected = selectedIndex >= 0 ? requests[selectedIndex] : void 0;
|
|
28
|
+
const rawDetails = selected === void 0 ? [] : approvalDetailLines(selected, mode === "approve" ? preparedApproval : void 0);
|
|
29
|
+
const bodyHeight = Math.max(1, size.rows - HEADER_HEIGHT - FOOTER_HEIGHT);
|
|
30
|
+
const sideBySide = size.columns >= SPLIT_COLUMNS;
|
|
31
|
+
const listPaneHeight = sideBySide ? bodyHeight : Math.max(5, Math.floor(bodyHeight * .42));
|
|
32
|
+
const detailPaneHeight = sideBySide ? bodyHeight : Math.max(1, bodyHeight - listPaneHeight);
|
|
33
|
+
const detailViewport = Math.max(1, detailPaneHeight - PANE_CHROME_HEIGHT);
|
|
34
|
+
const detailPaneWidth = sideBySide ? Math.floor(size.columns * .62) : size.columns;
|
|
35
|
+
const details = wrapDetailLines(rawDetails, Math.max(1, detailPaneWidth - 6));
|
|
36
|
+
const approvalReviewed = mode === "approve" && preparedApproval !== void 0 && details.length > 0 && detailOffset + detailViewport >= details.length;
|
|
37
|
+
useInput((input, key) => {
|
|
38
|
+
const canReviewAuthority = size.rows >= MIN_FULL_HEIGHT;
|
|
39
|
+
if (busy) return;
|
|
40
|
+
if (mode === "deny") {
|
|
41
|
+
if (key.escape) model.cancelMode();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (mode === "approve") {
|
|
45
|
+
if (key.escape) model.cancelMode();
|
|
46
|
+
else if (key.pageUp) model.scrollDetails(-detailViewport, details.length, detailViewport);
|
|
47
|
+
else if (key.pageDown) model.scrollDetails(detailViewport, details.length, detailViewport);
|
|
48
|
+
else if (key.return && canReviewAuthority && approvalReviewed) model.confirmCurrentMode();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (mode === "help") {
|
|
52
|
+
if (key.escape || input === "?" || input === "q") model.cancelMode();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (input === "q") exit();
|
|
56
|
+
else if (input === "?" || key.f1) model.showHelp();
|
|
57
|
+
else if (input === "a" && canReviewAuthority) model.beginApprove();
|
|
58
|
+
else if (input === "d" && canReviewAuthority) model.beginDeny();
|
|
59
|
+
else if (input === "r") model.refresh();
|
|
60
|
+
else if (key.upArrow || input === "k") model.selectBy(-1);
|
|
61
|
+
else if (key.downArrow || input === "j") model.selectBy(1);
|
|
62
|
+
else if (key.pageUp) model.scrollDetails(-detailViewport, details.length, detailViewport);
|
|
63
|
+
else if (key.pageDown) model.scrollDetails(detailViewport, details.length, detailViewport);
|
|
64
|
+
});
|
|
65
|
+
if (size.rows < MIN_FULL_HEIGHT) return /* @__PURE__ */ jsx(CompactApprovalApp, {
|
|
66
|
+
model,
|
|
67
|
+
selected,
|
|
68
|
+
requestCount: requests.length,
|
|
69
|
+
connectionState,
|
|
70
|
+
mode,
|
|
71
|
+
busy,
|
|
72
|
+
message,
|
|
73
|
+
rows: size.rows
|
|
74
|
+
});
|
|
75
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
76
|
+
flexDirection: "column",
|
|
77
|
+
height: size.rows,
|
|
78
|
+
width: size.columns,
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx(Header, {
|
|
81
|
+
principalId: model.principalId,
|
|
82
|
+
state: connectionState,
|
|
83
|
+
requestCount: requests.length
|
|
84
|
+
}),
|
|
85
|
+
mode === "help" ? /* @__PURE__ */ jsx(Help, { height: bodyHeight }) : /* @__PURE__ */ jsxs(Box, {
|
|
86
|
+
flexDirection: sideBySide ? "row" : "column",
|
|
87
|
+
height: bodyHeight,
|
|
88
|
+
children: [/* @__PURE__ */ jsx(RequestList, {
|
|
89
|
+
requests,
|
|
90
|
+
selectedIndex,
|
|
91
|
+
height: listPaneHeight,
|
|
92
|
+
width: sideBySide ? "38%" : "100%"
|
|
93
|
+
}), /* @__PURE__ */ jsx(RequestDetails, {
|
|
94
|
+
selected,
|
|
95
|
+
lines: details,
|
|
96
|
+
offset: detailOffset,
|
|
97
|
+
height: detailPaneHeight,
|
|
98
|
+
width: sideBySide ? "62%" : "100%"
|
|
99
|
+
})]
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ jsx(Footer, {
|
|
102
|
+
mode,
|
|
103
|
+
busy,
|
|
104
|
+
message,
|
|
105
|
+
selected,
|
|
106
|
+
denialReason,
|
|
107
|
+
approvalReviewed,
|
|
108
|
+
onReasonChange: (value) => model.setDenialReason(value),
|
|
109
|
+
onDeny: () => void model.confirmCurrentMode()
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const Header = ({ principalId, state, requestCount }) => /* @__PURE__ */ jsxs(Box, {
|
|
115
|
+
flexDirection: "column",
|
|
116
|
+
height: HEADER_HEIGHT,
|
|
117
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
118
|
+
justifyContent: "space-between",
|
|
119
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
120
|
+
bold: true,
|
|
121
|
+
children: "Hub access approvals"
|
|
122
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
123
|
+
color: state === "live" ? "green" : state === "stale" ? "yellow" : "cyan",
|
|
124
|
+
children: [state === "live" ? "● live" : state === "stale" ? "● reconnecting" : "○ connecting", /* @__PURE__ */ jsxs(Text, {
|
|
125
|
+
color: "white",
|
|
126
|
+
children: [
|
|
127
|
+
" ",
|
|
128
|
+
requestCount,
|
|
129
|
+
" pending"
|
|
130
|
+
]
|
|
131
|
+
})]
|
|
132
|
+
})]
|
|
133
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
134
|
+
dimColor: true,
|
|
135
|
+
wrap: "truncate-end",
|
|
136
|
+
children: ["approver: ", safeTerminalText(principalId)]
|
|
137
|
+
})]
|
|
138
|
+
});
|
|
139
|
+
const RequestList = ({ requests, selectedIndex, height, width }) => {
|
|
140
|
+
const viewport = Math.max(1, height - PANE_CHROME_HEIGHT);
|
|
141
|
+
const window = useScroll(requests.length, Math.max(0, selectedIndex), viewport);
|
|
142
|
+
return /* @__PURE__ */ jsx(Pane, {
|
|
143
|
+
title: "Requests",
|
|
144
|
+
height,
|
|
145
|
+
width,
|
|
146
|
+
children: requests.length === 0 ? /* @__PURE__ */ jsxs(Box, {
|
|
147
|
+
flexDirection: "column",
|
|
148
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
149
|
+
color: "green",
|
|
150
|
+
children: "No pending access requests."
|
|
151
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
152
|
+
dimColor: true,
|
|
153
|
+
children: "Watching the Hub for new requests..."
|
|
154
|
+
})]
|
|
155
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
156
|
+
/* @__PURE__ */ jsx(ScrollMarker, {
|
|
157
|
+
direction: "up",
|
|
158
|
+
count: window.above
|
|
159
|
+
}),
|
|
160
|
+
requests.slice(window.start, window.end).map((item, index) => {
|
|
161
|
+
const selected = window.start + index === selectedIndex;
|
|
162
|
+
const duration = item.request.duration ?? "unspecified";
|
|
163
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
164
|
+
color: selected ? "cyan" : void 0,
|
|
165
|
+
bold: selected,
|
|
166
|
+
inverse: selected,
|
|
167
|
+
wrap: "truncate-end",
|
|
168
|
+
children: [
|
|
169
|
+
selected ? "› " : " ",
|
|
170
|
+
safeTerminalText(item.request.consumer.name),
|
|
171
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
172
|
+
dimColor: true,
|
|
173
|
+
children: [
|
|
174
|
+
" ",
|
|
175
|
+
safeTerminalText(item.request.kind),
|
|
176
|
+
" / ",
|
|
177
|
+
safeTerminalText(duration)
|
|
178
|
+
]
|
|
179
|
+
})
|
|
180
|
+
]
|
|
181
|
+
}, item.id);
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ jsx(ScrollMarker, {
|
|
184
|
+
direction: "down",
|
|
185
|
+
count: window.below
|
|
186
|
+
})
|
|
187
|
+
] })
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const RequestDetails = ({ selected, lines, offset, height, width }) => {
|
|
191
|
+
const viewport = Math.max(1, height - PANE_CHROME_HEIGHT);
|
|
192
|
+
const safeOffset = Math.min(offset, Math.max(0, lines.length - viewport));
|
|
193
|
+
const visible = lines.slice(safeOffset, safeOffset + viewport);
|
|
194
|
+
const range = lines.length > viewport ? ` (${safeOffset + 1}-${safeOffset + visible.length} of ${lines.length})` : "";
|
|
195
|
+
return /* @__PURE__ */ jsx(Pane, {
|
|
196
|
+
title: `Request details${range}`,
|
|
197
|
+
height,
|
|
198
|
+
width,
|
|
199
|
+
children: selected === void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
200
|
+
dimColor: true,
|
|
201
|
+
children: "Select a request to inspect its exact authority."
|
|
202
|
+
}) : /* @__PURE__ */ jsx(Fragment, { children: visible.map((line, index) => /* @__PURE__ */ jsxs(Text, {
|
|
203
|
+
color: line.tone === "warning" ? "yellow" : void 0,
|
|
204
|
+
dimColor: line.tone === "muted",
|
|
205
|
+
wrap: "truncate-end",
|
|
206
|
+
children: [line.label !== void 0 && /* @__PURE__ */ jsxs(Text, {
|
|
207
|
+
bold: true,
|
|
208
|
+
children: [safeTerminalText(line.label), ": "]
|
|
209
|
+
}), safeTerminalText(line.text) || "—"]
|
|
210
|
+
}, `${safeOffset + index}:${line.label ?? ""}:${line.text}`)) })
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
const Footer = ({ mode, busy, message, selected, denialReason, approvalReviewed, onReasonChange, onDeny }) => {
|
|
214
|
+
if (busy) return /* @__PURE__ */ jsxs(Box, {
|
|
215
|
+
flexDirection: "column",
|
|
216
|
+
height: FOOTER_HEIGHT,
|
|
217
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
218
|
+
color: "cyan",
|
|
219
|
+
children: "Working... The manifest will be rechecked before the decision is applied."
|
|
220
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
221
|
+
dimColor: true,
|
|
222
|
+
children: "Please wait."
|
|
223
|
+
})]
|
|
224
|
+
});
|
|
225
|
+
if (mode === "approve") return /* @__PURE__ */ jsxs(Box, {
|
|
226
|
+
flexDirection: "column",
|
|
227
|
+
height: FOOTER_HEIGHT,
|
|
228
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
229
|
+
color: "yellow",
|
|
230
|
+
wrap: "truncate-end",
|
|
231
|
+
children: [
|
|
232
|
+
"Approve all displayed authority for",
|
|
233
|
+
" ",
|
|
234
|
+
/* @__PURE__ */ jsx(Text, {
|
|
235
|
+
bold: true,
|
|
236
|
+
children: safeTerminalText(selected?.request.consumer.name ?? "")
|
|
237
|
+
}),
|
|
238
|
+
"?"
|
|
239
|
+
]
|
|
240
|
+
}), approvalReviewed ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Text, {
|
|
242
|
+
bold: true,
|
|
243
|
+
children: "Enter"
|
|
244
|
+
}),
|
|
245
|
+
" confirm ",
|
|
246
|
+
/* @__PURE__ */ jsx(Text, {
|
|
247
|
+
bold: true,
|
|
248
|
+
children: "Esc"
|
|
249
|
+
}),
|
|
250
|
+
" cancel"
|
|
251
|
+
] }) : /* @__PURE__ */ jsxs(Text, { children: [
|
|
252
|
+
/* @__PURE__ */ jsx(Text, {
|
|
253
|
+
bold: true,
|
|
254
|
+
children: "PgDn"
|
|
255
|
+
}),
|
|
256
|
+
" review all authority before confirming ",
|
|
257
|
+
/* @__PURE__ */ jsx(Text, {
|
|
258
|
+
bold: true,
|
|
259
|
+
children: "Esc"
|
|
260
|
+
}),
|
|
261
|
+
" cancel"
|
|
262
|
+
] })]
|
|
263
|
+
});
|
|
264
|
+
if (mode === "deny") return /* @__PURE__ */ jsxs(Box, {
|
|
265
|
+
flexDirection: "column",
|
|
266
|
+
height: FOOTER_HEIGHT,
|
|
267
|
+
children: [/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
268
|
+
color: "red",
|
|
269
|
+
children: "Denial reason (optional): "
|
|
270
|
+
}), /* @__PURE__ */ jsx(TextInput, {
|
|
271
|
+
value: denialReason,
|
|
272
|
+
onChange: onReasonChange,
|
|
273
|
+
onSubmit: onDeny
|
|
274
|
+
})] }), /* @__PURE__ */ jsxs(Text, { children: [
|
|
275
|
+
/* @__PURE__ */ jsx(Text, {
|
|
276
|
+
bold: true,
|
|
277
|
+
children: "Enter"
|
|
278
|
+
}),
|
|
279
|
+
" deny ",
|
|
280
|
+
/* @__PURE__ */ jsx(Text, {
|
|
281
|
+
bold: true,
|
|
282
|
+
children: "Esc"
|
|
283
|
+
}),
|
|
284
|
+
" cancel"
|
|
285
|
+
] })]
|
|
286
|
+
});
|
|
287
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
288
|
+
flexDirection: "column",
|
|
289
|
+
height: FOOTER_HEIGHT,
|
|
290
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
291
|
+
color: messageColor(message?.kind),
|
|
292
|
+
dimColor: message === void 0,
|
|
293
|
+
wrap: "truncate-end",
|
|
294
|
+
children: safeTerminalText(message?.text ?? "Review the exact authority before approving.")
|
|
295
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
296
|
+
dimColor: true,
|
|
297
|
+
wrap: "truncate-end",
|
|
298
|
+
children: "↑/↓ or j/k select a approve d deny PgUp/PgDn details r refresh ? help q quit"
|
|
299
|
+
})]
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const Help = ({ height }) => /* @__PURE__ */ jsxs(Pane, {
|
|
303
|
+
title: "Keyboard help",
|
|
304
|
+
height,
|
|
305
|
+
width: "100%",
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
308
|
+
bold: true,
|
|
309
|
+
children: "↑/↓, j/k"
|
|
310
|
+
}), " Move through pending requests"] }),
|
|
311
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
312
|
+
bold: true,
|
|
313
|
+
children: "PgUp/PgDn"
|
|
314
|
+
}), " Scroll long request details"] }),
|
|
315
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
316
|
+
bold: true,
|
|
317
|
+
children: "a"
|
|
318
|
+
}), " Review and confirm approval"] }),
|
|
319
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
320
|
+
bold: true,
|
|
321
|
+
children: "d"
|
|
322
|
+
}), " Deny with an optional reason"] }),
|
|
323
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
324
|
+
bold: true,
|
|
325
|
+
children: "r"
|
|
326
|
+
}), " Refresh the authoritative manifest snapshot"] }),
|
|
327
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
328
|
+
bold: true,
|
|
329
|
+
children: "?, F1"
|
|
330
|
+
}), " Toggle this help"] }),
|
|
331
|
+
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
332
|
+
bold: true,
|
|
333
|
+
children: "q"
|
|
334
|
+
}), " Quit without deciding remaining requests"] }),
|
|
335
|
+
/* @__PURE__ */ jsx(Text, {
|
|
336
|
+
dimColor: true,
|
|
337
|
+
children: "Press Esc, ?, or q to return."
|
|
338
|
+
})
|
|
339
|
+
]
|
|
340
|
+
});
|
|
341
|
+
const CompactApprovalApp = ({ model, selected, requestCount, connectionState, mode, busy, message, rows }) => /* @__PURE__ */ jsxs(Box, {
|
|
342
|
+
flexDirection: "column",
|
|
343
|
+
height: Math.max(1, rows),
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
346
|
+
bold: true,
|
|
347
|
+
wrap: "truncate-end",
|
|
348
|
+
children: [
|
|
349
|
+
"Hub approvals ",
|
|
350
|
+
connectionState,
|
|
351
|
+
" ",
|
|
352
|
+
requestCount,
|
|
353
|
+
" pending"
|
|
354
|
+
]
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx(Text, {
|
|
357
|
+
wrap: "truncate-end",
|
|
358
|
+
children: selected === void 0 ? "No pending access requests." : safeTerminalText(`${selected.request.consumer.name} · ${selected.request.kind} · ${selected.request.duration ?? "unspecified"}`)
|
|
359
|
+
}),
|
|
360
|
+
rows >= 4 && /* @__PURE__ */ jsx(Text, {
|
|
361
|
+
color: messageColor(message?.kind),
|
|
362
|
+
wrap: "truncate-end",
|
|
363
|
+
children: safeTerminalText(busy ? "Resolving request..." : message?.text ?? model.principalId)
|
|
364
|
+
}),
|
|
365
|
+
rows >= 5 && /* @__PURE__ */ jsxs(Text, {
|
|
366
|
+
color: "yellow",
|
|
367
|
+
wrap: "truncate-end",
|
|
368
|
+
children: [
|
|
369
|
+
"Enlarge to at least ",
|
|
370
|
+
MIN_FULL_HEIGHT,
|
|
371
|
+
" rows to review and decide. ↑/↓ select q quit"
|
|
372
|
+
]
|
|
373
|
+
})
|
|
374
|
+
]
|
|
375
|
+
});
|
|
376
|
+
const Pane = ({ title, height, width, children }) => /* @__PURE__ */ jsxs(Box, {
|
|
377
|
+
flexDirection: "column",
|
|
378
|
+
borderStyle: "round",
|
|
379
|
+
borderColor: "gray",
|
|
380
|
+
height,
|
|
381
|
+
width,
|
|
382
|
+
paddingX: 1,
|
|
383
|
+
overflow: "hidden",
|
|
384
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
385
|
+
bold: true,
|
|
386
|
+
children: title
|
|
387
|
+
}), children]
|
|
388
|
+
});
|
|
389
|
+
const ScrollMarker = ({ direction, count }) => {
|
|
390
|
+
if (count === 0) return null;
|
|
391
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
392
|
+
dimColor: true,
|
|
393
|
+
children: [
|
|
394
|
+
direction === "up" ? "▲" : "▼",
|
|
395
|
+
" ",
|
|
396
|
+
count,
|
|
397
|
+
" more"
|
|
398
|
+
]
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
function wrapDetailLines(lines, width) {
|
|
402
|
+
return lines.flatMap((line) => {
|
|
403
|
+
return wrapTerminalText(safeTerminalText(`${line.label === void 0 ? "" : `${line.label}: `}${line.text || "—"}`), width).map((chunk) => ({
|
|
404
|
+
text: chunk,
|
|
405
|
+
...line.tone === void 0 ? {} : { tone: line.tone }
|
|
406
|
+
}));
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function wrapTerminalText(text, width) {
|
|
410
|
+
const lines = [];
|
|
411
|
+
let current = "";
|
|
412
|
+
let currentWidth = 0;
|
|
413
|
+
for (const character of text) {
|
|
414
|
+
const characterWidth = terminalCharacterWidth(character);
|
|
415
|
+
if (current.length > 0 && currentWidth + characterWidth > width) {
|
|
416
|
+
lines.push(current);
|
|
417
|
+
current = "";
|
|
418
|
+
currentWidth = 0;
|
|
419
|
+
}
|
|
420
|
+
current += character;
|
|
421
|
+
currentWidth += characterWidth;
|
|
422
|
+
}
|
|
423
|
+
if (current.length > 0 || lines.length === 0) lines.push(current);
|
|
424
|
+
return lines;
|
|
425
|
+
}
|
|
426
|
+
function terminalCharacterWidth(character) {
|
|
427
|
+
if (/^\p{Mark}$/u.test(character)) return 0;
|
|
428
|
+
return character.codePointAt(0) <= 126 ? 1 : 2;
|
|
429
|
+
}
|
|
430
|
+
function safeTerminalText(text) {
|
|
431
|
+
let result = "";
|
|
432
|
+
for (const character of text) result += /^[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]$/u.test(character) ? `\\u{${character.codePointAt(0).toString(16).padStart(4, "0")}}` : character;
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
function messageColor(kind) {
|
|
436
|
+
if (kind === "error") return "red";
|
|
437
|
+
if (kind === "success") return "green";
|
|
438
|
+
if (kind === "info") return "cyan";
|
|
439
|
+
}
|
|
440
|
+
function useTerminalSize() {
|
|
441
|
+
const { stdout } = useStdout();
|
|
442
|
+
const [size, setSize] = React.useState({
|
|
443
|
+
columns: stdout.columns ?? 100,
|
|
444
|
+
rows: stdout.rows ?? 30
|
|
445
|
+
});
|
|
446
|
+
React.useEffect(() => {
|
|
447
|
+
const onResize = () => setSize({
|
|
448
|
+
columns: stdout.columns ?? 100,
|
|
449
|
+
rows: stdout.rows ?? 30
|
|
450
|
+
});
|
|
451
|
+
stdout.on("resize", onResize);
|
|
452
|
+
return () => stdout.off("resize", onResize);
|
|
453
|
+
}, [stdout]);
|
|
454
|
+
return size;
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/approval-ui/ApprovalUiModel.ts
|
|
458
|
+
var ApprovalUiModel = class {
|
|
459
|
+
_requests;
|
|
460
|
+
requests;
|
|
461
|
+
_connectionState;
|
|
462
|
+
connectionState;
|
|
463
|
+
_selectedRequestId;
|
|
464
|
+
selectedRequestId;
|
|
465
|
+
_mode;
|
|
466
|
+
mode;
|
|
467
|
+
_busy;
|
|
468
|
+
busy;
|
|
469
|
+
_message;
|
|
470
|
+
message;
|
|
471
|
+
_denialReason;
|
|
472
|
+
denialReason;
|
|
473
|
+
_detailOffset;
|
|
474
|
+
detailOffset;
|
|
475
|
+
_preparedApproval;
|
|
476
|
+
preparedApproval;
|
|
477
|
+
_client;
|
|
478
|
+
_subscription;
|
|
479
|
+
_disposed = false;
|
|
480
|
+
constructor(client) {
|
|
481
|
+
this._client = client;
|
|
482
|
+
this._requests = observableValue(this, []);
|
|
483
|
+
this.requests = this._requests;
|
|
484
|
+
this._connectionState = observableValue(this, "connecting");
|
|
485
|
+
this.connectionState = this._connectionState;
|
|
486
|
+
this._selectedRequestId = observableValue(this, void 0);
|
|
487
|
+
this.selectedRequestId = this._selectedRequestId;
|
|
488
|
+
this._mode = observableValue(this, "browse");
|
|
489
|
+
this.mode = this._mode;
|
|
490
|
+
this._busy = observableValue(this, false);
|
|
491
|
+
this.busy = this._busy;
|
|
492
|
+
this._message = observableValue(this, void 0);
|
|
493
|
+
this.message = this._message;
|
|
494
|
+
this._denialReason = observableValue(this, "");
|
|
495
|
+
this.denialReason = this._denialReason;
|
|
496
|
+
this._detailOffset = observableValue(this, 0);
|
|
497
|
+
this.detailOffset = this._detailOffset;
|
|
498
|
+
this._preparedApproval = observableValue(this, void 0);
|
|
499
|
+
this.preparedApproval = this._preparedApproval;
|
|
500
|
+
this._subscription = client.watch((snapshot) => this._applySnapshot(snapshot));
|
|
501
|
+
}
|
|
502
|
+
get principalId() {
|
|
503
|
+
return this._client.principalId;
|
|
504
|
+
}
|
|
505
|
+
get selectedRequest() {
|
|
506
|
+
const selectedId = this._selectedRequestId.get();
|
|
507
|
+
return this._requests.get().find((item) => item.id === selectedId);
|
|
508
|
+
}
|
|
509
|
+
selectBy(delta) {
|
|
510
|
+
if (this._busy.get() || this._mode.get() !== "browse") return;
|
|
511
|
+
const requests = this._requests.get();
|
|
512
|
+
if (requests.length === 0) return;
|
|
513
|
+
const currentIndex = Math.max(0, requests.findIndex((item) => item.id === this._selectedRequestId.get()));
|
|
514
|
+
const nextIndex = Math.max(0, Math.min(requests.length - 1, currentIndex + delta));
|
|
515
|
+
if (nextIndex === currentIndex) return;
|
|
516
|
+
this._selectedRequestId.set(requests[nextIndex].id, void 0);
|
|
517
|
+
this._detailOffset.set(0, void 0);
|
|
518
|
+
}
|
|
519
|
+
showHelp() {
|
|
520
|
+
if (this._busy.get()) return;
|
|
521
|
+
this._mode.set("help", void 0);
|
|
522
|
+
}
|
|
523
|
+
async beginApprove() {
|
|
524
|
+
if (!this._canDecide()) return;
|
|
525
|
+
const selected = this.selectedRequest;
|
|
526
|
+
if (selected === void 0) return;
|
|
527
|
+
this._busy.set(true, void 0);
|
|
528
|
+
this._message.set({
|
|
529
|
+
kind: "info",
|
|
530
|
+
text: "Resolving exact authority..."
|
|
531
|
+
}, void 0);
|
|
532
|
+
try {
|
|
533
|
+
const prepared = await this._client.prepareApproval(selected.id);
|
|
534
|
+
if (this.selectedRequest?.id !== selected.id || jcsCanonicalize(this.selectedRequest.request) !== jcsCanonicalize(prepared.pending.request)) {
|
|
535
|
+
this._message.set({
|
|
536
|
+
kind: "info",
|
|
537
|
+
text: "The request changed while its authority was being resolved. Review it again."
|
|
538
|
+
}, void 0);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
this._preparedApproval.set(prepared, void 0);
|
|
542
|
+
this._detailOffset.set(0, void 0);
|
|
543
|
+
this._mode.set("approve", void 0);
|
|
544
|
+
this._message.set(void 0, void 0);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this._message.set({
|
|
547
|
+
kind: "error",
|
|
548
|
+
text: error instanceof Error ? error.message : String(error)
|
|
549
|
+
}, void 0);
|
|
550
|
+
} finally {
|
|
551
|
+
this._busy.set(false, void 0);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
beginDeny() {
|
|
555
|
+
if (!this._canDecide()) return;
|
|
556
|
+
this._denialReason.set("", void 0);
|
|
557
|
+
this._mode.set("deny", void 0);
|
|
558
|
+
}
|
|
559
|
+
setDenialReason(reason) {
|
|
560
|
+
this._denialReason.set(reason, void 0);
|
|
561
|
+
}
|
|
562
|
+
cancelMode() {
|
|
563
|
+
if (this._busy.get()) return;
|
|
564
|
+
this._mode.set("browse", void 0);
|
|
565
|
+
this._denialReason.set("", void 0);
|
|
566
|
+
this._preparedApproval.set(void 0, void 0);
|
|
567
|
+
}
|
|
568
|
+
async confirmCurrentMode() {
|
|
569
|
+
const mode = this._mode.get();
|
|
570
|
+
const selected = this.selectedRequest;
|
|
571
|
+
if (this._busy.get() || selected === void 0 || mode !== "approve" && mode !== "deny") return;
|
|
572
|
+
this._busy.set(true, void 0);
|
|
573
|
+
this._message.set({
|
|
574
|
+
kind: "info",
|
|
575
|
+
text: mode === "approve" ? "Approving request..." : "Denying request..."
|
|
576
|
+
}, void 0);
|
|
577
|
+
try {
|
|
578
|
+
let outcome;
|
|
579
|
+
if (mode === "approve") {
|
|
580
|
+
const prepared = this._preparedApproval.get();
|
|
581
|
+
if (prepared === void 0) throw new Error("approval details are no longer available; review the request again");
|
|
582
|
+
outcome = await this._client.approvePrepared(prepared);
|
|
583
|
+
} else {
|
|
584
|
+
const reason = this._denialReason.get().trim();
|
|
585
|
+
outcome = await this._client.deny(selected.id, reason.length > 0 ? reason : void 0, selected);
|
|
586
|
+
}
|
|
587
|
+
this._message.set({
|
|
588
|
+
kind: outcome === "applied" ? "success" : "info",
|
|
589
|
+
text: outcome === "applied" ? `${mode === "approve" ? "Approved" : "Denied"} ${selected.request.consumer.name}.` : `Decision accepted for ${selected.request.consumer.name}, but a matching request is still pending. The consumer may have retried.`
|
|
590
|
+
}, void 0);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
this._message.set({
|
|
593
|
+
kind: "error",
|
|
594
|
+
text: error instanceof Error ? error.message : String(error)
|
|
595
|
+
}, void 0);
|
|
596
|
+
} finally {
|
|
597
|
+
this._busy.set(false, void 0);
|
|
598
|
+
this._mode.set("browse", void 0);
|
|
599
|
+
this._denialReason.set("", void 0);
|
|
600
|
+
this._preparedApproval.set(void 0, void 0);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async refresh() {
|
|
604
|
+
if (this._busy.get()) return;
|
|
605
|
+
this._message.set({
|
|
606
|
+
kind: "info",
|
|
607
|
+
text: "Refreshing approval requests..."
|
|
608
|
+
}, void 0);
|
|
609
|
+
try {
|
|
610
|
+
await this._client.refresh();
|
|
611
|
+
if (this._connectionState.get() === "live") this._message.set({
|
|
612
|
+
kind: "info",
|
|
613
|
+
text: "Approval requests are up to date."
|
|
614
|
+
}, void 0);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
this._message.set({
|
|
617
|
+
kind: "error",
|
|
618
|
+
text: error instanceof Error ? error.message : String(error)
|
|
619
|
+
}, void 0);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
scrollDetails(delta, lineCount, viewportSize) {
|
|
623
|
+
const maxOffset = Math.max(0, lineCount - Math.max(1, viewportSize));
|
|
624
|
+
const next = Math.max(0, Math.min(maxOffset, this._detailOffset.get() + delta));
|
|
625
|
+
this._detailOffset.set(next, void 0);
|
|
626
|
+
}
|
|
627
|
+
dispose() {
|
|
628
|
+
if (this._disposed) return;
|
|
629
|
+
this._disposed = true;
|
|
630
|
+
this._subscription.dispose();
|
|
631
|
+
}
|
|
632
|
+
_canDecide() {
|
|
633
|
+
if (this._busy.get() || this.selectedRequest === void 0) return false;
|
|
634
|
+
if (this._connectionState.get() !== "live") {
|
|
635
|
+
this._message.set({
|
|
636
|
+
kind: "error",
|
|
637
|
+
text: "Cannot decide requests while the approval connection is stale."
|
|
638
|
+
}, void 0);
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
_applySnapshot(snapshot) {
|
|
644
|
+
const previous = this._requests.get();
|
|
645
|
+
const selectedId = this._selectedRequestId.get();
|
|
646
|
+
const previousSelected = previous.find((item) => item.id === selectedId);
|
|
647
|
+
const previousIndex = Math.max(0, previous.findIndex((item) => item.id === selectedId));
|
|
648
|
+
const selectedStillExists = snapshot.requests.some((item) => item.id === selectedId);
|
|
649
|
+
const nextSelected = snapshot.requests.find((item) => item.id === selectedId);
|
|
650
|
+
const selectedChanged = previousSelected !== void 0 && nextSelected !== void 0 && jcsCanonicalize(previousSelected.request) !== jcsCanonicalize(nextSelected.request);
|
|
651
|
+
this._requests.set(snapshot.requests, void 0);
|
|
652
|
+
this._connectionState.set(snapshot.state, void 0);
|
|
653
|
+
if (snapshot.requests.length === 0) {
|
|
654
|
+
this._selectedRequestId.set(void 0, void 0);
|
|
655
|
+
this._detailOffset.set(0, void 0);
|
|
656
|
+
} else if (!selectedStillExists) {
|
|
657
|
+
const nextIndex = Math.min(previousIndex, snapshot.requests.length - 1);
|
|
658
|
+
this._selectedRequestId.set(snapshot.requests[nextIndex].id, void 0);
|
|
659
|
+
this._detailOffset.set(0, void 0);
|
|
660
|
+
this._preparedApproval.set(void 0, void 0);
|
|
661
|
+
}
|
|
662
|
+
if ((!selectedStillExists && selectedId !== void 0 || selectedChanged) && this._mode.get() !== "browse") {
|
|
663
|
+
this._mode.set("browse", void 0);
|
|
664
|
+
this._denialReason.set("", void 0);
|
|
665
|
+
this._preparedApproval.set(void 0, void 0);
|
|
666
|
+
this._message.set({
|
|
667
|
+
kind: "info",
|
|
668
|
+
text: selectedChanged ? "The selected request changed. Review it again before deciding." : "The selected request is no longer pending."
|
|
669
|
+
}, void 0);
|
|
670
|
+
}
|
|
671
|
+
if (snapshot.state === "stale" && snapshot.error !== void 0) this._message.set({
|
|
672
|
+
kind: "error",
|
|
673
|
+
text: snapshot.error
|
|
674
|
+
}, void 0);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/approval-ui/runApprovalUi.tsx
|
|
679
|
+
async function runApprovalUi(options) {
|
|
680
|
+
const model = new ApprovalUiModel(options.client);
|
|
681
|
+
const instance = render(/* @__PURE__ */ jsx(ApprovalApp, { model }));
|
|
682
|
+
const onAbort = () => instance.unmount();
|
|
683
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
684
|
+
if (options.signal.aborted) onAbort();
|
|
685
|
+
try {
|
|
686
|
+
await raceAbort(model.refresh(), options.signal);
|
|
687
|
+
if (options.signal.aborted) return;
|
|
688
|
+
await instance.waitUntilExit();
|
|
689
|
+
} finally {
|
|
690
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
691
|
+
instance.unmount();
|
|
692
|
+
model.dispose();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function raceAbort(promise, signal) {
|
|
696
|
+
if (signal.aborted) return Promise.resolve(void 0);
|
|
697
|
+
return new Promise((resolve, reject) => {
|
|
698
|
+
const onAbort = () => {
|
|
699
|
+
signal.removeEventListener("abort", onAbort);
|
|
700
|
+
resolve(void 0);
|
|
701
|
+
};
|
|
702
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
703
|
+
promise.then((value) => {
|
|
704
|
+
signal.removeEventListener("abort", onAbort);
|
|
705
|
+
resolve(value);
|
|
706
|
+
}, (error) => {
|
|
707
|
+
signal.removeEventListener("abort", onAbort);
|
|
708
|
+
reject(error);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
//#endregion
|
|
713
|
+
export { runApprovalUi };
|
|
714
|
+
|
|
715
|
+
//# sourceMappingURL=runApprovalUi-BvJZdjKm.js.map
|