@docyrus/docyrus 0.0.3 → 0.0.5
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 +9 -1
- package/main.js +23892 -19
- package/main.js.map +4 -4
- package/package.json +9 -2
- package/tui.mjs +1073 -0
- package/tui.mjs.map +7 -0
package/tui.mjs
ADDED
|
@@ -0,0 +1,1073 @@
|
|
|
1
|
+
// src/tui/opentuiMain.tsx
|
|
2
|
+
import { createCliRenderer } from "@opentui/core";
|
|
3
|
+
import { createRoot } from "@opentui/react";
|
|
4
|
+
|
|
5
|
+
// src/tui/opentui/DocyrusOpenTuiApp.tsx
|
|
6
|
+
import { useKeyboard as useKeyboard2, useRenderer } from "@opentui/react";
|
|
7
|
+
import { useCallback as useCallback2, useEffect, useMemo, useState as useState2 } from "react";
|
|
8
|
+
|
|
9
|
+
// src/services/tuiProcessExecutor.ts
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
// src/errors.ts
|
|
13
|
+
var ApplicationError = class extends Error {
|
|
14
|
+
constructor(message, data = {}, httpStatusCode = 500) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.data = data;
|
|
17
|
+
this.httpStatusCode = httpStatusCode;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var UserInputError = class extends ApplicationError {
|
|
21
|
+
constructor(message, data = {}) {
|
|
22
|
+
super(message, data, 400);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/services/tuiProcessExecutor.ts
|
|
27
|
+
function normalizeCommandArg(arg) {
|
|
28
|
+
return arg.trim();
|
|
29
|
+
}
|
|
30
|
+
function sanitizeTuiCommandArgs(args) {
|
|
31
|
+
const normalized = args.map(normalizeCommandArg).filter((value) => value.length > 0);
|
|
32
|
+
const withoutBinary = normalized[0] === "docyrus" ? normalized.slice(1) : normalized;
|
|
33
|
+
const withoutJson = withoutBinary.filter((value) => value !== "--json");
|
|
34
|
+
const firstArg = withoutJson[0];
|
|
35
|
+
if (firstArg === "tui" || firstArg === "interactive") {
|
|
36
|
+
throw new UserInputError("Already in TUI session.");
|
|
37
|
+
}
|
|
38
|
+
return withoutJson;
|
|
39
|
+
}
|
|
40
|
+
function parseJsonOutput(rawOutput) {
|
|
41
|
+
const trimmedOutput = rawOutput.trim();
|
|
42
|
+
if (!trimmedOutput) {
|
|
43
|
+
throw new UserInputError("Command produced no output.");
|
|
44
|
+
}
|
|
45
|
+
const lines = trimmedOutput.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
46
|
+
const lastLine = lines.at(-1);
|
|
47
|
+
if (lastLine) {
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
data: JSON.parse(lastLine),
|
|
51
|
+
messages: lines.slice(0, -1)
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return {
|
|
58
|
+
data: JSON.parse(trimmedOutput),
|
|
59
|
+
messages: []
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new UserInputError("Invalid command output format. Expected JSON output.", {
|
|
63
|
+
cause: error,
|
|
64
|
+
rawOutput: trimmedOutput
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function toErrorMessage(error) {
|
|
69
|
+
if (error instanceof Error && error.message.trim().length > 0) {
|
|
70
|
+
return error.message;
|
|
71
|
+
}
|
|
72
|
+
return "Failed to execute command.";
|
|
73
|
+
}
|
|
74
|
+
function createTuiProcessExecutor(options) {
|
|
75
|
+
const spawnCommand = options.spawnSyncFn ?? spawnSync;
|
|
76
|
+
const now = options.now ?? Date.now;
|
|
77
|
+
const runCliCommand = async (args) => {
|
|
78
|
+
const startedAt = now();
|
|
79
|
+
let commandArgs = [];
|
|
80
|
+
let commandLabel = "";
|
|
81
|
+
let rawOutput = "";
|
|
82
|
+
let messageLines = [];
|
|
83
|
+
try {
|
|
84
|
+
commandArgs = sanitizeTuiCommandArgs(args);
|
|
85
|
+
commandLabel = commandArgs.join(" ").trim();
|
|
86
|
+
const spawnResult = spawnCommand(
|
|
87
|
+
options.executionConfig.executable,
|
|
88
|
+
[
|
|
89
|
+
options.executionConfig.scriptPath,
|
|
90
|
+
...commandArgs,
|
|
91
|
+
"--json"
|
|
92
|
+
],
|
|
93
|
+
{
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
cwd: options.executionConfig.cwd,
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
...options.executionConfig.env || {}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
const stdout = spawnResult.stdout?.toString() || "";
|
|
103
|
+
const stderr = spawnResult.stderr?.toString() || "";
|
|
104
|
+
rawOutput = stdout.trim();
|
|
105
|
+
if (spawnResult.error) {
|
|
106
|
+
throw spawnResult.error;
|
|
107
|
+
}
|
|
108
|
+
if (spawnResult.status !== 0) {
|
|
109
|
+
const details = [rawOutput, stderr.trim()].filter((line) => line.length > 0).join("\n");
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
command: commandLabel,
|
|
113
|
+
rawOutput: details,
|
|
114
|
+
error: {
|
|
115
|
+
message: stderr.trim() || `Command exited with code ${spawnResult.status ?? "unknown"}.`,
|
|
116
|
+
details: details || void 0
|
|
117
|
+
},
|
|
118
|
+
durationMs: now() - startedAt
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const parsed = parseJsonOutput(stdout);
|
|
122
|
+
messageLines = parsed.messages;
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
command: commandLabel,
|
|
126
|
+
rawOutput,
|
|
127
|
+
data: parsed.data,
|
|
128
|
+
durationMs: now() - startedAt,
|
|
129
|
+
messages: messageLines.length > 0 ? messageLines : void 0
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
command: commandLabel,
|
|
135
|
+
rawOutput,
|
|
136
|
+
error: {
|
|
137
|
+
message: toErrorMessage(error),
|
|
138
|
+
details: rawOutput || void 0
|
|
139
|
+
},
|
|
140
|
+
durationMs: now() - startedAt,
|
|
141
|
+
messages: messageLines.length > 0 ? messageLines : void 0
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
runCliCommand
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/tui/opentui/commandInput.ts
|
|
151
|
+
import { useKeyboard } from "@opentui/react";
|
|
152
|
+
import { useCallback, useState } from "react";
|
|
153
|
+
function parseCommandLine(command) {
|
|
154
|
+
const args = [];
|
|
155
|
+
let current = "";
|
|
156
|
+
let quote = null;
|
|
157
|
+
let escaped = false;
|
|
158
|
+
for (const char of command) {
|
|
159
|
+
if (escaped) {
|
|
160
|
+
current += char;
|
|
161
|
+
escaped = false;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (char === "\\") {
|
|
165
|
+
escaped = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (quote) {
|
|
169
|
+
if (char === quote) {
|
|
170
|
+
quote = null;
|
|
171
|
+
} else {
|
|
172
|
+
current += char;
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (char === '"' || char === "'") {
|
|
177
|
+
quote = char;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (/\s/.test(char)) {
|
|
181
|
+
if (current.length > 0) {
|
|
182
|
+
args.push(current);
|
|
183
|
+
current = "";
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
current += char;
|
|
188
|
+
}
|
|
189
|
+
if (quote) {
|
|
190
|
+
throw new Error("Invalid command syntax: unmatched quote.");
|
|
191
|
+
}
|
|
192
|
+
if (escaped) {
|
|
193
|
+
current += "\\";
|
|
194
|
+
}
|
|
195
|
+
if (current.length > 0) {
|
|
196
|
+
args.push(current);
|
|
197
|
+
}
|
|
198
|
+
return args;
|
|
199
|
+
}
|
|
200
|
+
function preventDefault(event) {
|
|
201
|
+
if (typeof event.preventDefault === "function") {
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function resolveSectionJumpKey(keyName, shift) {
|
|
206
|
+
if (keyName === "s") {
|
|
207
|
+
return "shortcuts";
|
|
208
|
+
}
|
|
209
|
+
if (keyName === "h") {
|
|
210
|
+
return "history";
|
|
211
|
+
}
|
|
212
|
+
if (keyName === "m") {
|
|
213
|
+
return "messages";
|
|
214
|
+
}
|
|
215
|
+
if (keyName === "?" || keyName === "/" && shift) {
|
|
216
|
+
return "help";
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
function useCommandInput(options) {
|
|
221
|
+
const [history, setHistory] = useState([]);
|
|
222
|
+
const [historyCursor, setHistoryCursor] = useState(null);
|
|
223
|
+
const [draftInput, setDraftInput] = useState("");
|
|
224
|
+
const submitInput = useCallback((value) => {
|
|
225
|
+
if (options.busy) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const trimmed = (value ?? options.input).trim();
|
|
229
|
+
if (trimmed.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
setHistory((previous) => {
|
|
233
|
+
if (previous[0] === trimmed) {
|
|
234
|
+
return previous;
|
|
235
|
+
}
|
|
236
|
+
return [trimmed, ...previous];
|
|
237
|
+
});
|
|
238
|
+
setHistoryCursor(null);
|
|
239
|
+
setDraftInput("");
|
|
240
|
+
options.setInput("");
|
|
241
|
+
options.onSubmit(trimmed);
|
|
242
|
+
}, [options]);
|
|
243
|
+
const recallHistory = useCallback((direction) => {
|
|
244
|
+
if (history.length === 0) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (direction === "up") {
|
|
248
|
+
if (historyCursor === null) {
|
|
249
|
+
setDraftInput(options.input);
|
|
250
|
+
setHistoryCursor(0);
|
|
251
|
+
options.setInput(history[0] || "");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const nextCursor2 = Math.min(historyCursor + 1, history.length - 1);
|
|
255
|
+
setHistoryCursor(nextCursor2);
|
|
256
|
+
options.setInput(history[nextCursor2] || "");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (historyCursor === null) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const nextCursor = historyCursor - 1;
|
|
263
|
+
if (nextCursor < 0) {
|
|
264
|
+
setHistoryCursor(null);
|
|
265
|
+
options.setInput(draftInput);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
setHistoryCursor(nextCursor);
|
|
269
|
+
options.setInput(history[nextCursor] || "");
|
|
270
|
+
}, [draftInput, history, historyCursor, options]);
|
|
271
|
+
useKeyboard((keyEvent) => {
|
|
272
|
+
if (options.isModalOpen) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const keyName = keyEvent.name;
|
|
276
|
+
if (keyEvent.ctrl && keyName === "l") {
|
|
277
|
+
preventDefault(keyEvent);
|
|
278
|
+
options.onClear();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (keyName === "tab") {
|
|
282
|
+
preventDefault(keyEvent);
|
|
283
|
+
if (keyEvent.shift) {
|
|
284
|
+
options.onPreviousSection();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
options.onNextSection();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (keyName === "[") {
|
|
291
|
+
preventDefault(keyEvent);
|
|
292
|
+
options.onPreviousSection();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (keyName === "]") {
|
|
296
|
+
preventDefault(keyEvent);
|
|
297
|
+
options.onNextSection();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (options.isInputFocused && keyName === "up") {
|
|
301
|
+
preventDefault(keyEvent);
|
|
302
|
+
recallHistory("up");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (options.isInputFocused && keyName === "down") {
|
|
306
|
+
preventDefault(keyEvent);
|
|
307
|
+
recallHistory("down");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (options.input.length === 0) {
|
|
311
|
+
const jumpSection = resolveSectionJumpKey(keyName, Boolean(keyEvent.shift));
|
|
312
|
+
if (jumpSection) {
|
|
313
|
+
preventDefault(keyEvent);
|
|
314
|
+
options.onJumpSection(jumpSection);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (options.busy) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (options.input.length === 0 && /^[1-6]$/.test(keyName)) {
|
|
322
|
+
preventDefault(keyEvent);
|
|
323
|
+
options.onShortcut(Number(keyName));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
submitInput
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/tui/opentui/renderResult.tsx
|
|
332
|
+
import { RGBA, SyntaxStyle } from "@opentui/core";
|
|
333
|
+
|
|
334
|
+
// src/tui/opentui/resultDocument.ts
|
|
335
|
+
function toErrorPayload(entry) {
|
|
336
|
+
return {
|
|
337
|
+
message: entry.result.error?.message || "Command failed.",
|
|
338
|
+
details: entry.result.error?.details
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function buildPayload(entry) {
|
|
342
|
+
if (entry.result.ok) {
|
|
343
|
+
return entry.result.data ?? null;
|
|
344
|
+
}
|
|
345
|
+
return toErrorPayload(entry);
|
|
346
|
+
}
|
|
347
|
+
function buildResultDocument(entry) {
|
|
348
|
+
return {
|
|
349
|
+
command: entry.command || "(root)",
|
|
350
|
+
ok: entry.result.ok,
|
|
351
|
+
durationMs: entry.result.durationMs,
|
|
352
|
+
timestamp: new Date(entry.createdAt).toISOString(),
|
|
353
|
+
payload: buildPayload(entry),
|
|
354
|
+
messages: entry.result.messages,
|
|
355
|
+
rawOutput: entry.result.rawOutput || void 0
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function serializeResultDocument(document) {
|
|
359
|
+
try {
|
|
360
|
+
const result = JSON.stringify(document, null, 2);
|
|
361
|
+
if (!result) {
|
|
362
|
+
return "null";
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
return JSON.stringify({
|
|
368
|
+
command: document.command,
|
|
369
|
+
ok: false,
|
|
370
|
+
durationMs: document.durationMs,
|
|
371
|
+
timestamp: document.timestamp,
|
|
372
|
+
payload: {
|
|
373
|
+
message: "Serialization failed",
|
|
374
|
+
details: message
|
|
375
|
+
}
|
|
376
|
+
}, null, 2);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/tui/opentui/renderResult.tsx
|
|
381
|
+
import { jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
382
|
+
var jsonSyntaxStyle = null;
|
|
383
|
+
function getJsonSyntaxStyle() {
|
|
384
|
+
if (jsonSyntaxStyle) {
|
|
385
|
+
return jsonSyntaxStyle;
|
|
386
|
+
}
|
|
387
|
+
jsonSyntaxStyle = SyntaxStyle.fromStyles({
|
|
388
|
+
default: {
|
|
389
|
+
fg: RGBA.fromHex("#d4d4d4")
|
|
390
|
+
},
|
|
391
|
+
property: {
|
|
392
|
+
fg: RGBA.fromHex("#9cdcfe")
|
|
393
|
+
},
|
|
394
|
+
string: {
|
|
395
|
+
fg: RGBA.fromHex("#ce9178")
|
|
396
|
+
},
|
|
397
|
+
number: {
|
|
398
|
+
fg: RGBA.fromHex("#b5cea8")
|
|
399
|
+
},
|
|
400
|
+
keyword: {
|
|
401
|
+
fg: RGBA.fromHex("#569cd6")
|
|
402
|
+
},
|
|
403
|
+
constant: {
|
|
404
|
+
fg: RGBA.fromHex("#4fc1ff")
|
|
405
|
+
},
|
|
406
|
+
punctuation: {
|
|
407
|
+
fg: RGBA.fromHex("#c586c0")
|
|
408
|
+
},
|
|
409
|
+
operator: {
|
|
410
|
+
fg: RGBA.fromHex("#d4d4d4")
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return jsonSyntaxStyle;
|
|
414
|
+
}
|
|
415
|
+
function renderResult(params) {
|
|
416
|
+
const {
|
|
417
|
+
entry,
|
|
418
|
+
isFocused
|
|
419
|
+
} = params;
|
|
420
|
+
if (!entry) {
|
|
421
|
+
return /* @__PURE__ */ jsx("text", { fg: "yellow", children: "No command executed yet." });
|
|
422
|
+
}
|
|
423
|
+
const document = buildResultDocument(entry);
|
|
424
|
+
const json = serializeResultDocument(document);
|
|
425
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", width: "100%", height: "100%", children: [
|
|
426
|
+
/* @__PURE__ */ jsx("box", { paddingBottom: 1, children: /* @__PURE__ */ jsxs("text", { fg: document.ok ? "green" : "red", children: [
|
|
427
|
+
document.ok ? "OK" : "ERR",
|
|
428
|
+
" ",
|
|
429
|
+
document.command,
|
|
430
|
+
" [",
|
|
431
|
+
document.durationMs,
|
|
432
|
+
"ms]"
|
|
433
|
+
] }) }),
|
|
434
|
+
/* @__PURE__ */ jsx("box", { flexGrow: 1, width: "100%", height: "100%", children: /* @__PURE__ */ jsx("scrollbox", { focused: isFocused, style: { height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
435
|
+
"code",
|
|
436
|
+
{
|
|
437
|
+
width: "100%",
|
|
438
|
+
content: json,
|
|
439
|
+
filetype: "json",
|
|
440
|
+
syntaxStyle: getJsonSyntaxStyle(),
|
|
441
|
+
streaming: false,
|
|
442
|
+
conceal: false
|
|
443
|
+
}
|
|
444
|
+
) }) })
|
|
445
|
+
] });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/tui/opentui/DocyrusOpenTuiApp.tsx
|
|
449
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
|
|
450
|
+
var SHORTCUTS = [
|
|
451
|
+
{
|
|
452
|
+
id: 1,
|
|
453
|
+
run: "env list",
|
|
454
|
+
label: "env list"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: 2,
|
|
458
|
+
run: "auth who",
|
|
459
|
+
label: "auth who"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: 3,
|
|
463
|
+
run: "apps list",
|
|
464
|
+
label: "apps list"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: 4,
|
|
468
|
+
run: "discover namespaces",
|
|
469
|
+
label: "discover namespaces"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: 5,
|
|
473
|
+
template: "studio list-data-sources --appSlug ",
|
|
474
|
+
label: "insert: studio list-data-sources --appSlug "
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: 6,
|
|
478
|
+
template: "ds list <appSlug> <dataSourceSlug>",
|
|
479
|
+
label: "insert: ds list <appSlug> <dataSourceSlug>"
|
|
480
|
+
}
|
|
481
|
+
];
|
|
482
|
+
var SECTION_ORDER = ["shortcuts", "history", "messages", "help"];
|
|
483
|
+
var FOCUS_ORDER = ["input", "left", "result"];
|
|
484
|
+
var ESC_ARM_TIMEOUT_MS = 1400;
|
|
485
|
+
function isRecord(value) {
|
|
486
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
487
|
+
}
|
|
488
|
+
function extractMetadata(payload) {
|
|
489
|
+
if (!isRecord(payload)) {
|
|
490
|
+
return {};
|
|
491
|
+
}
|
|
492
|
+
const environmentValue = payload.environment;
|
|
493
|
+
const contextValue = payload.context;
|
|
494
|
+
const environment = isRecord(environmentValue) && typeof environmentValue.id === "string" && typeof environmentValue.name === "string" ? { id: environmentValue.id, name: environmentValue.name } : void 0;
|
|
495
|
+
if (contextValue === null) {
|
|
496
|
+
return {
|
|
497
|
+
environment,
|
|
498
|
+
context: null
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const context = isRecord(contextValue) && typeof contextValue.email === "string" && typeof contextValue.tenantDisplay === "string" ? {
|
|
502
|
+
email: contextValue.email,
|
|
503
|
+
tenantDisplay: contextValue.tenantDisplay
|
|
504
|
+
} : void 0;
|
|
505
|
+
return {
|
|
506
|
+
environment,
|
|
507
|
+
context
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function toFailedResult(error, command) {
|
|
511
|
+
const message = error instanceof Error ? error.message : "Invalid command syntax.";
|
|
512
|
+
return {
|
|
513
|
+
ok: false,
|
|
514
|
+
command,
|
|
515
|
+
rawOutput: "",
|
|
516
|
+
error: {
|
|
517
|
+
message
|
|
518
|
+
},
|
|
519
|
+
durationMs: 0
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function formatHistoryLabel(entry) {
|
|
523
|
+
const status = entry.result.ok ? "OK" : "ERR";
|
|
524
|
+
const command = entry.command || "(root)";
|
|
525
|
+
return `${status} ${command} [${entry.result.durationMs}ms]`;
|
|
526
|
+
}
|
|
527
|
+
function preventDefault2(keyEvent) {
|
|
528
|
+
if (typeof keyEvent.preventDefault === "function") {
|
|
529
|
+
keyEvent.preventDefault();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function isEscapeKey(keyName) {
|
|
533
|
+
return keyName === "escape" || keyName === "esc";
|
|
534
|
+
}
|
|
535
|
+
function isEnterKey(keyName) {
|
|
536
|
+
return keyName === "enter" || keyName === "return";
|
|
537
|
+
}
|
|
538
|
+
function DocyrusOpenTuiApp(props) {
|
|
539
|
+
const renderer = useRenderer();
|
|
540
|
+
const [input, setInput] = useState2("");
|
|
541
|
+
const [entries, setEntries] = useState2([]);
|
|
542
|
+
const [activeEntryId, setActiveEntryId] = useState2(null);
|
|
543
|
+
const [systemMessages, setSystemMessages] = useState2([]);
|
|
544
|
+
const [activeSection, setActiveSection] = useState2("shortcuts");
|
|
545
|
+
const [focusedPanel, setFocusedPanel] = useState2("left");
|
|
546
|
+
const [environment, setEnvironment] = useState2();
|
|
547
|
+
const [context, setContext] = useState2(void 0);
|
|
548
|
+
const [isRunning, setIsRunning] = useState2(false);
|
|
549
|
+
const [spinnerFrame, setSpinnerFrame] = useState2(0);
|
|
550
|
+
const [selectedShortcutIndex, setSelectedShortcutIndex] = useState2(0);
|
|
551
|
+
const [selectedHistoryIndex, setSelectedHistoryIndex] = useState2(0);
|
|
552
|
+
const [isExitConfirmOpen, setIsExitConfirmOpen] = useState2(false);
|
|
553
|
+
const [isEscArmed, setIsEscArmed] = useState2(false);
|
|
554
|
+
const executor = useMemo(() => {
|
|
555
|
+
return createTuiProcessExecutor({
|
|
556
|
+
executionConfig: props.executionConfig
|
|
557
|
+
});
|
|
558
|
+
}, [props.executionConfig]);
|
|
559
|
+
const applyCommandResult = useCallback2((result, command) => {
|
|
560
|
+
const entry = {
|
|
561
|
+
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
562
|
+
command,
|
|
563
|
+
result,
|
|
564
|
+
createdAt: Date.now()
|
|
565
|
+
};
|
|
566
|
+
setEntries((previous) => [entry, ...previous]);
|
|
567
|
+
setActiveEntryId(entry.id);
|
|
568
|
+
setSelectedHistoryIndex(0);
|
|
569
|
+
if (result.messages && result.messages.length > 0) {
|
|
570
|
+
setSystemMessages((previous) => [...previous, ...result.messages || []]);
|
|
571
|
+
}
|
|
572
|
+
if (result.ok && result.data !== void 0) {
|
|
573
|
+
const metadata = extractMetadata(result.data);
|
|
574
|
+
if (metadata.environment) {
|
|
575
|
+
setEnvironment(metadata.environment);
|
|
576
|
+
}
|
|
577
|
+
if (metadata.context !== void 0) {
|
|
578
|
+
setContext(metadata.context);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}, []);
|
|
582
|
+
const executeArgs = useCallback2(async (args, commandText) => {
|
|
583
|
+
setIsRunning(true);
|
|
584
|
+
try {
|
|
585
|
+
const result = await executor.runCliCommand(args);
|
|
586
|
+
applyCommandResult(result, commandText);
|
|
587
|
+
} finally {
|
|
588
|
+
setIsRunning(false);
|
|
589
|
+
}
|
|
590
|
+
}, [applyCommandResult, executor]);
|
|
591
|
+
const executeLine = useCallback2((line) => {
|
|
592
|
+
let args;
|
|
593
|
+
try {
|
|
594
|
+
args = parseCommandLine(line);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
applyCommandResult(toFailedResult(error, line.trim()), line.trim());
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (args.length === 0) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
void executeArgs(args, line.trim());
|
|
603
|
+
}, [applyCommandResult, executeArgs]);
|
|
604
|
+
const runHistoryEntry = useCallback2(async (entry) => {
|
|
605
|
+
if (entry.command === "(root)") {
|
|
606
|
+
await executeArgs([], "(root)");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
const args = parseCommandLine(entry.command);
|
|
611
|
+
await executeArgs(args, entry.command);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
applyCommandResult(toFailedResult(error, entry.command), entry.command);
|
|
614
|
+
}
|
|
615
|
+
}, [applyCommandResult, executeArgs]);
|
|
616
|
+
const applyShortcut = useCallback2((shortcutId) => {
|
|
617
|
+
const action = SHORTCUTS.find((shortcut) => shortcut.id === shortcutId);
|
|
618
|
+
if (!action || isRunning) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (action.template) {
|
|
622
|
+
setInput(action.template);
|
|
623
|
+
setFocusedPanel("input");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (action.run) {
|
|
627
|
+
void executeArgs(parseCommandLine(action.run), action.run);
|
|
628
|
+
}
|
|
629
|
+
}, [executeArgs, isRunning]);
|
|
630
|
+
const setSectionByIndex = useCallback2((index) => {
|
|
631
|
+
const safeIndex = index < 0 ? SECTION_ORDER.length - 1 : index >= SECTION_ORDER.length ? 0 : index;
|
|
632
|
+
const section = SECTION_ORDER[safeIndex];
|
|
633
|
+
if (section) {
|
|
634
|
+
setActiveSection(section);
|
|
635
|
+
}
|
|
636
|
+
}, []);
|
|
637
|
+
const nextSection = useCallback2(() => {
|
|
638
|
+
const currentIndex = SECTION_ORDER.indexOf(activeSection);
|
|
639
|
+
setSectionByIndex(currentIndex + 1);
|
|
640
|
+
}, [activeSection, setSectionByIndex]);
|
|
641
|
+
const previousSection = useCallback2(() => {
|
|
642
|
+
const currentIndex = SECTION_ORDER.indexOf(activeSection);
|
|
643
|
+
setSectionByIndex(currentIndex - 1);
|
|
644
|
+
}, [activeSection, setSectionByIndex]);
|
|
645
|
+
const setPanelByIndex = useCallback2((index) => {
|
|
646
|
+
const safeIndex = index < 0 ? FOCUS_ORDER.length - 1 : index >= FOCUS_ORDER.length ? 0 : index;
|
|
647
|
+
const panel = FOCUS_ORDER[safeIndex];
|
|
648
|
+
if (panel) {
|
|
649
|
+
setFocusedPanel(panel);
|
|
650
|
+
}
|
|
651
|
+
}, []);
|
|
652
|
+
const nextPanel = useCallback2(() => {
|
|
653
|
+
const currentIndex = FOCUS_ORDER.indexOf(focusedPanel);
|
|
654
|
+
setPanelByIndex(currentIndex + 1);
|
|
655
|
+
}, [focusedPanel, setPanelByIndex]);
|
|
656
|
+
const previousPanel = useCallback2(() => {
|
|
657
|
+
const currentIndex = FOCUS_ORDER.indexOf(focusedPanel);
|
|
658
|
+
setPanelByIndex(currentIndex - 1);
|
|
659
|
+
}, [focusedPanel, setPanelByIndex]);
|
|
660
|
+
const openExitConfirmation = useCallback2(() => {
|
|
661
|
+
setIsEscArmed(false);
|
|
662
|
+
setIsExitConfirmOpen(true);
|
|
663
|
+
}, []);
|
|
664
|
+
const closeExitConfirmation = useCallback2(() => {
|
|
665
|
+
setIsExitConfirmOpen(false);
|
|
666
|
+
setIsEscArmed(false);
|
|
667
|
+
}, []);
|
|
668
|
+
const armEscapeExit = useCallback2(() => {
|
|
669
|
+
setIsEscArmed(true);
|
|
670
|
+
setSystemMessages((previous) => {
|
|
671
|
+
const lastMessage = previous[previous.length - 1];
|
|
672
|
+
const message = "Press Esc again to open exit confirmation.";
|
|
673
|
+
if (lastMessage === message) {
|
|
674
|
+
return previous;
|
|
675
|
+
}
|
|
676
|
+
return [...previous, message];
|
|
677
|
+
});
|
|
678
|
+
}, []);
|
|
679
|
+
const { submitInput } = useCommandInput({
|
|
680
|
+
busy: isRunning,
|
|
681
|
+
input,
|
|
682
|
+
isInputFocused: focusedPanel === "input",
|
|
683
|
+
isModalOpen: isExitConfirmOpen,
|
|
684
|
+
setInput,
|
|
685
|
+
onSubmit: executeLine,
|
|
686
|
+
onClear: () => {
|
|
687
|
+
setEntries([]);
|
|
688
|
+
setActiveEntryId(null);
|
|
689
|
+
setSystemMessages([]);
|
|
690
|
+
setSelectedHistoryIndex(0);
|
|
691
|
+
},
|
|
692
|
+
onShortcut: applyShortcut,
|
|
693
|
+
onNextSection: nextSection,
|
|
694
|
+
onPreviousSection: previousSection,
|
|
695
|
+
onJumpSection: (section) => {
|
|
696
|
+
setActiveSection(section);
|
|
697
|
+
setFocusedPanel("left");
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
useKeyboard2((keyEvent) => {
|
|
701
|
+
const keyName = keyEvent.name;
|
|
702
|
+
if (isExitConfirmOpen) {
|
|
703
|
+
if (isEscapeKey(keyName)) {
|
|
704
|
+
preventDefault2(keyEvent);
|
|
705
|
+
closeExitConfirmation();
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (isEnterKey(keyName)) {
|
|
709
|
+
preventDefault2(keyEvent);
|
|
710
|
+
renderer.destroy();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (keyEvent.ctrl && keyName === "c") {
|
|
714
|
+
renderer.destroy();
|
|
715
|
+
}
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (keyEvent.ctrl && keyName === "c") {
|
|
719
|
+
renderer.destroy();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (isEscapeKey(keyName)) {
|
|
723
|
+
preventDefault2(keyEvent);
|
|
724
|
+
if (focusedPanel !== "input") {
|
|
725
|
+
setFocusedPanel("input");
|
|
726
|
+
armEscapeExit();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!isEscArmed) {
|
|
730
|
+
armEscapeExit();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
openExitConfirmation();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (keyName === "left" || keyName === "right") {
|
|
737
|
+
if (focusedPanel === "input" && input.length > 0) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
preventDefault2(keyEvent);
|
|
741
|
+
setIsEscArmed(false);
|
|
742
|
+
if (keyName === "left") {
|
|
743
|
+
previousPanel();
|
|
744
|
+
} else {
|
|
745
|
+
nextPanel();
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (keyName === "r" && activeSection === "history" && focusedPanel === "left" && input.length === 0 && !isRunning) {
|
|
750
|
+
const entry = entries[selectedHistoryIndex];
|
|
751
|
+
if (entry) {
|
|
752
|
+
void runHistoryEntry(entry);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
useEffect(() => {
|
|
757
|
+
void executeArgs([], "(root)");
|
|
758
|
+
}, [executeArgs]);
|
|
759
|
+
useEffect(() => {
|
|
760
|
+
if (!isEscArmed) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const timer = setTimeout(() => {
|
|
764
|
+
setIsEscArmed(false);
|
|
765
|
+
}, ESC_ARM_TIMEOUT_MS);
|
|
766
|
+
return () => {
|
|
767
|
+
clearTimeout(timer);
|
|
768
|
+
};
|
|
769
|
+
}, [isEscArmed]);
|
|
770
|
+
useEffect(() => {
|
|
771
|
+
if (!isRunning) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const timer = setInterval(() => {
|
|
775
|
+
setSpinnerFrame((previous) => (previous + 1) % 4);
|
|
776
|
+
}, 120);
|
|
777
|
+
return () => {
|
|
778
|
+
clearInterval(timer);
|
|
779
|
+
};
|
|
780
|
+
}, [isRunning]);
|
|
781
|
+
useEffect(() => {
|
|
782
|
+
if (selectedHistoryIndex < entries.length) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const nextIndex = Math.max(entries.length - 1, 0);
|
|
786
|
+
setSelectedHistoryIndex(nextIndex);
|
|
787
|
+
}, [entries.length, selectedHistoryIndex]);
|
|
788
|
+
const spinner = useMemo(() => {
|
|
789
|
+
return ["|", "/", "-", "\\"][spinnerFrame] || "|";
|
|
790
|
+
}, [spinnerFrame]);
|
|
791
|
+
const activeEntry = useMemo(() => {
|
|
792
|
+
if (activeEntryId) {
|
|
793
|
+
const found = entries.find((entry) => entry.id === activeEntryId);
|
|
794
|
+
if (found) {
|
|
795
|
+
return found;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return entries[0] || null;
|
|
799
|
+
}, [activeEntryId, entries]);
|
|
800
|
+
const sectionOptions = useMemo(() => {
|
|
801
|
+
const ordered = [
|
|
802
|
+
activeSection,
|
|
803
|
+
...SECTION_ORDER.filter((section) => section !== activeSection)
|
|
804
|
+
];
|
|
805
|
+
return ordered.map((section) => ({
|
|
806
|
+
name: section === "shortcuts" ? "Shortcuts" : section === "history" ? "History" : section === "messages" ? "Messages" : "Help",
|
|
807
|
+
description: section === "shortcuts" ? "Quick actions" : section === "history" ? "Past runs" : section === "messages" ? "System output" : "Key map",
|
|
808
|
+
value: section
|
|
809
|
+
}));
|
|
810
|
+
}, [activeSection]);
|
|
811
|
+
const shortcutOptions = useMemo(() => {
|
|
812
|
+
return SHORTCUTS.map((shortcut) => ({
|
|
813
|
+
name: `${shortcut.id}. ${shortcut.label}`,
|
|
814
|
+
description: shortcut.run || shortcut.template || "",
|
|
815
|
+
value: shortcut.id
|
|
816
|
+
}));
|
|
817
|
+
}, []);
|
|
818
|
+
const historyOptions = useMemo(() => {
|
|
819
|
+
return entries.map((entry) => ({
|
|
820
|
+
name: formatHistoryLabel(entry),
|
|
821
|
+
description: new Date(entry.createdAt).toLocaleTimeString(),
|
|
822
|
+
value: entry.id
|
|
823
|
+
}));
|
|
824
|
+
}, [entries]);
|
|
825
|
+
const renderLeftSection = () => {
|
|
826
|
+
if (activeSection === "shortcuts") {
|
|
827
|
+
return /* @__PURE__ */ jsx2(
|
|
828
|
+
"select",
|
|
829
|
+
{
|
|
830
|
+
focused: focusedPanel === "left" && !isExitConfirmOpen,
|
|
831
|
+
options: shortcutOptions,
|
|
832
|
+
selectedIndex: selectedShortcutIndex,
|
|
833
|
+
onChange: (index) => {
|
|
834
|
+
setSelectedShortcutIndex(index);
|
|
835
|
+
},
|
|
836
|
+
onSelect: (index, option) => {
|
|
837
|
+
setSelectedShortcutIndex(index);
|
|
838
|
+
const shortcutId = Number(option?.value || 0);
|
|
839
|
+
if (Number.isFinite(shortcutId) && shortcutId > 0) {
|
|
840
|
+
applyShortcut(shortcutId);
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
showScrollIndicator: true,
|
|
844
|
+
style: {
|
|
845
|
+
flexGrow: 1
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (activeSection === "history") {
|
|
851
|
+
if (entries.length === 0) {
|
|
852
|
+
return /* @__PURE__ */ jsx2("text", { fg: "gray", children: "No command history yet." });
|
|
853
|
+
}
|
|
854
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", flexGrow: 1, children: [
|
|
855
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "Press `r` to re-run selected command." }),
|
|
856
|
+
/* @__PURE__ */ jsx2(
|
|
857
|
+
"select",
|
|
858
|
+
{
|
|
859
|
+
focused: focusedPanel === "left" && !isExitConfirmOpen,
|
|
860
|
+
options: historyOptions,
|
|
861
|
+
selectedIndex: selectedHistoryIndex,
|
|
862
|
+
onChange: (index, option) => {
|
|
863
|
+
setSelectedHistoryIndex(index);
|
|
864
|
+
if (typeof option?.value === "string") {
|
|
865
|
+
setActiveEntryId(option.value);
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
onSelect: (index, option) => {
|
|
869
|
+
setSelectedHistoryIndex(index);
|
|
870
|
+
if (typeof option?.value === "string") {
|
|
871
|
+
setActiveEntryId(option.value);
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
showScrollIndicator: true,
|
|
875
|
+
style: {
|
|
876
|
+
flexGrow: 1
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] });
|
|
881
|
+
}
|
|
882
|
+
if (activeSection === "messages") {
|
|
883
|
+
return /* @__PURE__ */ jsxs2("scrollbox", { focused: focusedPanel === "left" && !isExitConfirmOpen, style: { height: "100%" }, children: [
|
|
884
|
+
systemMessages.length === 0 && /* @__PURE__ */ jsx2("text", { fg: "gray", children: "No messages yet." }),
|
|
885
|
+
systemMessages.map((line, index) => /* @__PURE__ */ jsx2("text", { children: line }, `message-${index}`))
|
|
886
|
+
] });
|
|
887
|
+
}
|
|
888
|
+
return /* @__PURE__ */ jsxs2("scrollbox", { focused: focusedPanel === "left" && !isExitConfirmOpen, style: { height: "100%" }, children: [
|
|
889
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "Key Bindings" }),
|
|
890
|
+
/* @__PURE__ */ jsx2("text", { children: "Left/Right: switch focused panel" }),
|
|
891
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in left lists: selection" }),
|
|
892
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in result: scroll" }),
|
|
893
|
+
/* @__PURE__ */ jsx2("text", { children: "Enter: run command" }),
|
|
894
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in input: command history" }),
|
|
895
|
+
/* @__PURE__ */ jsx2("text", { children: "Tab / Shift+Tab: next/prev left section" }),
|
|
896
|
+
/* @__PURE__ */ jsx2("text", { children: "[ / ]: next/prev left section" }),
|
|
897
|
+
/* @__PURE__ */ jsx2("text", { children: "s/h/m/?: jump to section" }),
|
|
898
|
+
/* @__PURE__ */ jsx2("text", { children: "1..6: run shortcut" }),
|
|
899
|
+
/* @__PURE__ */ jsx2("text", { children: "Ctrl+L: clear command and message history" }),
|
|
900
|
+
/* @__PURE__ */ jsx2("text", { children: "Esc (other panel): focus command input" }),
|
|
901
|
+
/* @__PURE__ */ jsx2("text", { children: "Esc then Esc: open exit confirmation" }),
|
|
902
|
+
/* @__PURE__ */ jsx2("text", { children: "Enter (confirm): exit, Esc (confirm): cancel" }),
|
|
903
|
+
/* @__PURE__ */ jsx2("text", { children: "Ctrl+C: force quit" })
|
|
904
|
+
] });
|
|
905
|
+
};
|
|
906
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", width: "100%", height: "100%", padding: 1, children: [
|
|
907
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, paddingX: 1, children: [
|
|
908
|
+
/* @__PURE__ */ jsx2("ascii-font", { text: "DOCYRUS", font: "tiny", color: "#7aa2f7" }),
|
|
909
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "column", alignItems: "flex-end", justifyContent: "center", children: [
|
|
910
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
911
|
+
"env: ",
|
|
912
|
+
environment ? `${environment.name} (${environment.id})` : "unknown"
|
|
913
|
+
] }),
|
|
914
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
915
|
+
"status: ",
|
|
916
|
+
isRunning ? `${spinner} running` : "idle"
|
|
917
|
+
] }),
|
|
918
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
919
|
+
"account: ",
|
|
920
|
+
context ? context.email : "not-authenticated"
|
|
921
|
+
] }),
|
|
922
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
923
|
+
"tenant: ",
|
|
924
|
+
context ? context.tenantDisplay : "-"
|
|
925
|
+
] })
|
|
926
|
+
] })
|
|
927
|
+
] }),
|
|
928
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "row", flexGrow: 1, children: [
|
|
929
|
+
/* @__PURE__ */ jsxs2(
|
|
930
|
+
"box",
|
|
931
|
+
{
|
|
932
|
+
width: 46,
|
|
933
|
+
marginRight: 1,
|
|
934
|
+
border: true,
|
|
935
|
+
borderStyle: "rounded",
|
|
936
|
+
borderColor: focusedPanel === "left" ? "#7aa2f7" : "#3b4261",
|
|
937
|
+
padding: 1,
|
|
938
|
+
flexDirection: "column",
|
|
939
|
+
children: [
|
|
940
|
+
/* @__PURE__ */ jsx2(
|
|
941
|
+
"tab-select",
|
|
942
|
+
{
|
|
943
|
+
options: sectionOptions,
|
|
944
|
+
onChange: (_index, option) => {
|
|
945
|
+
if (typeof option?.value === "string") {
|
|
946
|
+
setActiveSection(option.value);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
),
|
|
951
|
+
/* @__PURE__ */ jsx2("box", { marginTop: 1, flexGrow: 1, border: true, borderStyle: "single", borderColor: "#414868", padding: 1, children: renderLeftSection() })
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
/* @__PURE__ */ jsx2(
|
|
956
|
+
"box",
|
|
957
|
+
{
|
|
958
|
+
border: true,
|
|
959
|
+
borderStyle: "double",
|
|
960
|
+
borderColor: focusedPanel === "result" ? "#9ece6a" : "#3b4261",
|
|
961
|
+
padding: 1,
|
|
962
|
+
flexGrow: 1,
|
|
963
|
+
children: renderResult({
|
|
964
|
+
entry: activeEntry,
|
|
965
|
+
isFocused: focusedPanel === "result" && !isExitConfirmOpen
|
|
966
|
+
})
|
|
967
|
+
}
|
|
968
|
+
)
|
|
969
|
+
] }),
|
|
970
|
+
/* @__PURE__ */ jsxs2(
|
|
971
|
+
"box",
|
|
972
|
+
{
|
|
973
|
+
border: true,
|
|
974
|
+
borderStyle: "rounded",
|
|
975
|
+
borderColor: focusedPanel === "input" ? "#e0af68" : "#3b4261",
|
|
976
|
+
padding: 1,
|
|
977
|
+
marginTop: 1,
|
|
978
|
+
children: [
|
|
979
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "> " }),
|
|
980
|
+
/* @__PURE__ */ jsx2(
|
|
981
|
+
"input",
|
|
982
|
+
{
|
|
983
|
+
style: { flexGrow: 1 },
|
|
984
|
+
value: input,
|
|
985
|
+
placeholder: "Type a command...",
|
|
986
|
+
onChange: (value) => {
|
|
987
|
+
setInput(value);
|
|
988
|
+
},
|
|
989
|
+
onSubmit: (value) => {
|
|
990
|
+
submitInput(typeof value === "string" ? value : void 0);
|
|
991
|
+
},
|
|
992
|
+
focused: !isRunning && focusedPanel === "input" && !isExitConfirmOpen
|
|
993
|
+
}
|
|
994
|
+
)
|
|
995
|
+
]
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
isExitConfirmOpen && /* @__PURE__ */ jsx2(
|
|
999
|
+
"box",
|
|
1000
|
+
{
|
|
1001
|
+
style: {
|
|
1002
|
+
position: "absolute",
|
|
1003
|
+
top: 0,
|
|
1004
|
+
left: 0,
|
|
1005
|
+
width: "100%",
|
|
1006
|
+
height: "100%"
|
|
1007
|
+
},
|
|
1008
|
+
alignItems: "center",
|
|
1009
|
+
justifyContent: "center",
|
|
1010
|
+
children: /* @__PURE__ */ jsxs2(
|
|
1011
|
+
"box",
|
|
1012
|
+
{
|
|
1013
|
+
width: 58,
|
|
1014
|
+
padding: 1,
|
|
1015
|
+
border: true,
|
|
1016
|
+
borderStyle: "double",
|
|
1017
|
+
borderColor: "#f7768e",
|
|
1018
|
+
flexDirection: "column",
|
|
1019
|
+
style: {
|
|
1020
|
+
backgroundColor: "#1f2335"
|
|
1021
|
+
},
|
|
1022
|
+
children: [
|
|
1023
|
+
/* @__PURE__ */ jsx2("text", { fg: "#f7768e", children: "Exit Docyrus TUI?" }),
|
|
1024
|
+
/* @__PURE__ */ jsx2("text", { children: "Press Enter to exit." }),
|
|
1025
|
+
/* @__PURE__ */ jsx2("text", { children: "Press Esc to stay in this session." })
|
|
1026
|
+
]
|
|
1027
|
+
}
|
|
1028
|
+
)
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
] });
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/tui/opentuiMain.tsx
|
|
1035
|
+
import { jsx as jsx3 } from "@opentui/react/jsx-runtime";
|
|
1036
|
+
function readRequiredEnv(name) {
|
|
1037
|
+
const value = process.env[name];
|
|
1038
|
+
if (!value || value.trim().length === 0) {
|
|
1039
|
+
throw new Error(`Missing required launch variable: ${name}`);
|
|
1040
|
+
}
|
|
1041
|
+
return value;
|
|
1042
|
+
}
|
|
1043
|
+
function readLaunchConfig() {
|
|
1044
|
+
return {
|
|
1045
|
+
version: process.env.DOCYRUS_TUI_VERSION || "dev",
|
|
1046
|
+
executionConfig: {
|
|
1047
|
+
executable: readRequiredEnv("DOCYRUS_TUI_EXECUTABLE"),
|
|
1048
|
+
scriptPath: readRequiredEnv("DOCYRUS_TUI_SCRIPT")
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
async function startTui() {
|
|
1053
|
+
const launchConfig = readLaunchConfig();
|
|
1054
|
+
const renderer = await createCliRenderer({
|
|
1055
|
+
exitOnCtrlC: false
|
|
1056
|
+
});
|
|
1057
|
+
const root = createRoot(renderer);
|
|
1058
|
+
root.render(
|
|
1059
|
+
/* @__PURE__ */ jsx3(
|
|
1060
|
+
DocyrusOpenTuiApp,
|
|
1061
|
+
{
|
|
1062
|
+
executionConfig: launchConfig.executionConfig,
|
|
1063
|
+
version: launchConfig.version
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
void startTui().catch((error) => {
|
|
1069
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1070
|
+
console.error(`Failed to start Docyrus OpenTUI: ${message}`);
|
|
1071
|
+
process.exitCode = 1;
|
|
1072
|
+
});
|
|
1073
|
+
//# sourceMappingURL=tui.mjs.map
|