@docyrus/docyrus 0.0.4 → 0.0.7
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 +2 -1
- package/main.js +114 -742
- package/main.js.map +4 -4
- package/package.json +5 -4
- package/tui.mjs +1503 -0
- package/tui.mjs.map +7 -0
package/tui.mjs
ADDED
|
@@ -0,0 +1,1503 @@
|
|
|
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, useRef, 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 useCommandInput(options) {
|
|
206
|
+
const [history, setHistory] = useState([]);
|
|
207
|
+
const [historyCursor, setHistoryCursor] = useState(null);
|
|
208
|
+
const [draftInput, setDraftInput] = useState("");
|
|
209
|
+
const submitInput = useCallback((value) => {
|
|
210
|
+
if (options.busy) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const trimmed = (value ?? options.input).trim();
|
|
214
|
+
if (trimmed.length === 0) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
setHistory((previous) => {
|
|
218
|
+
if (previous[0] === trimmed) {
|
|
219
|
+
return previous;
|
|
220
|
+
}
|
|
221
|
+
return [trimmed, ...previous];
|
|
222
|
+
});
|
|
223
|
+
setHistoryCursor(null);
|
|
224
|
+
setDraftInput("");
|
|
225
|
+
options.setInput("");
|
|
226
|
+
options.onSubmit(trimmed);
|
|
227
|
+
}, [options]);
|
|
228
|
+
const recallHistory = useCallback((direction) => {
|
|
229
|
+
if (history.length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (direction === "up") {
|
|
233
|
+
if (historyCursor === null) {
|
|
234
|
+
setDraftInput(options.input);
|
|
235
|
+
setHistoryCursor(0);
|
|
236
|
+
options.setInput(history[0] || "");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const nextCursor2 = Math.min(historyCursor + 1, history.length - 1);
|
|
240
|
+
setHistoryCursor(nextCursor2);
|
|
241
|
+
options.setInput(history[nextCursor2] || "");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (historyCursor === null) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const nextCursor = historyCursor - 1;
|
|
248
|
+
if (nextCursor < 0) {
|
|
249
|
+
setHistoryCursor(null);
|
|
250
|
+
options.setInput(draftInput);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
setHistoryCursor(nextCursor);
|
|
254
|
+
options.setInput(history[nextCursor] || "");
|
|
255
|
+
}, [draftInput, history, historyCursor, options]);
|
|
256
|
+
useKeyboard((keyEvent) => {
|
|
257
|
+
if (options.isModalOpen) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const keyName = keyEvent.name;
|
|
261
|
+
const hotkeysEnabled = options.hotkeysEnabled !== false;
|
|
262
|
+
if (keyEvent.ctrl && keyName === "l") {
|
|
263
|
+
preventDefault(keyEvent);
|
|
264
|
+
options.onClear();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (hotkeysEnabled && keyName === "tab") {
|
|
268
|
+
preventDefault(keyEvent);
|
|
269
|
+
if (keyEvent.shift) {
|
|
270
|
+
options.onPreviousSection();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
options.onNextSection();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (hotkeysEnabled && keyName === "[") {
|
|
277
|
+
preventDefault(keyEvent);
|
|
278
|
+
options.onPreviousSection();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (hotkeysEnabled && keyName === "]") {
|
|
282
|
+
preventDefault(keyEvent);
|
|
283
|
+
options.onNextSection();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (options.isInputFocused && keyName === "up") {
|
|
287
|
+
preventDefault(keyEvent);
|
|
288
|
+
recallHistory("up");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (options.isInputFocused && keyName === "down") {
|
|
292
|
+
preventDefault(keyEvent);
|
|
293
|
+
recallHistory("down");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (options.busy) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (hotkeysEnabled && options.input.length === 0 && /^[1-6]$/.test(keyName)) {
|
|
300
|
+
preventDefault(keyEvent);
|
|
301
|
+
options.onShortcut(Number(keyName));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
submitInput
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/tui/opentui/dataSourceCatalog.ts
|
|
310
|
+
function isRecord(value) {
|
|
311
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
312
|
+
}
|
|
313
|
+
function toNonEmptyString(value) {
|
|
314
|
+
if (typeof value !== "string") {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
const trimmed = value.trim();
|
|
318
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
319
|
+
}
|
|
320
|
+
function toStringValue(value, fallback) {
|
|
321
|
+
return toNonEmptyString(value) || fallback;
|
|
322
|
+
}
|
|
323
|
+
function getArrayCandidates(payload) {
|
|
324
|
+
if (Array.isArray(payload)) {
|
|
325
|
+
return payload;
|
|
326
|
+
}
|
|
327
|
+
if (!isRecord(payload)) {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
const directData = payload.data;
|
|
331
|
+
if (Array.isArray(directData)) {
|
|
332
|
+
return directData;
|
|
333
|
+
}
|
|
334
|
+
if (isRecord(directData) && Array.isArray(directData.data)) {
|
|
335
|
+
return directData.data;
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(payload.items)) {
|
|
338
|
+
return payload.items;
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(payload.dataSources)) {
|
|
341
|
+
return payload.dataSources;
|
|
342
|
+
}
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
function compareByName(a, b) {
|
|
346
|
+
return a.localeCompare(b, void 0, { sensitivity: "base" });
|
|
347
|
+
}
|
|
348
|
+
function getDataSourceDisplayName(dataSource) {
|
|
349
|
+
return dataSource.title || dataSource.name || dataSource.slug;
|
|
350
|
+
}
|
|
351
|
+
function extractAppsFromPayload(payload) {
|
|
352
|
+
const result = [];
|
|
353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
354
|
+
for (const candidate of getArrayCandidates(payload)) {
|
|
355
|
+
if (!isRecord(candidate)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const id = toNonEmptyString(candidate.id);
|
|
359
|
+
const slug = toNonEmptyString(candidate.slug);
|
|
360
|
+
if (!id || !slug) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const name = toStringValue(candidate.name, slug);
|
|
364
|
+
const dedupeKey = `${id}::${slug}`;
|
|
365
|
+
if (seen.has(dedupeKey)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
seen.add(dedupeKey);
|
|
369
|
+
result.push({ id, slug, name });
|
|
370
|
+
}
|
|
371
|
+
result.sort((a, b) => compareByName(a.name, b.name));
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
function extractDataSourcesFromPayload(payload) {
|
|
375
|
+
const result = [];
|
|
376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
377
|
+
for (const candidate of getArrayCandidates(payload)) {
|
|
378
|
+
if (!isRecord(candidate)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const id = toNonEmptyString(candidate.id);
|
|
382
|
+
const slug = toNonEmptyString(candidate.slug);
|
|
383
|
+
if (!id || !slug) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const title = toNonEmptyString(candidate.title);
|
|
387
|
+
const name = toStringValue(candidate.name, slug);
|
|
388
|
+
const appSlug = toNonEmptyString(candidate.app_slug);
|
|
389
|
+
const tenantAppId = toNonEmptyString(candidate.tenant_app_id);
|
|
390
|
+
const dedupeKey = `${id}::${slug}`;
|
|
391
|
+
if (seen.has(dedupeKey)) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
seen.add(dedupeKey);
|
|
395
|
+
result.push({
|
|
396
|
+
id,
|
|
397
|
+
name,
|
|
398
|
+
title,
|
|
399
|
+
slug,
|
|
400
|
+
app_slug: appSlug,
|
|
401
|
+
tenant_app_id: tenantAppId
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
result.sort((a, b) => compareByName(getDataSourceDisplayName(a), getDataSourceDisplayName(b)));
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
function groupDataSourcesByApp(params) {
|
|
408
|
+
const { apps, dataSources } = params;
|
|
409
|
+
const appById = /* @__PURE__ */ new Map();
|
|
410
|
+
const appBySlug = /* @__PURE__ */ new Map();
|
|
411
|
+
const groups = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const app of apps) {
|
|
413
|
+
appById.set(app.id, app);
|
|
414
|
+
appBySlug.set(app.slug, app);
|
|
415
|
+
}
|
|
416
|
+
for (const dataSource of dataSources) {
|
|
417
|
+
const byId = dataSource.tenant_app_id ? appById.get(dataSource.tenant_app_id) : void 0;
|
|
418
|
+
const bySlug = dataSource.app_slug ? appBySlug.get(dataSource.app_slug) : void 0;
|
|
419
|
+
const matchedApp = byId || bySlug;
|
|
420
|
+
const appKey = matchedApp ? matchedApp.id : dataSource.app_slug ? `slug:${dataSource.app_slug}` : "unknown";
|
|
421
|
+
const group = groups.get(appKey) || {
|
|
422
|
+
appKey,
|
|
423
|
+
appId: matchedApp?.id || dataSource.tenant_app_id,
|
|
424
|
+
appName: matchedApp?.name || dataSource.app_slug || "Unknown App",
|
|
425
|
+
appSlug: matchedApp?.slug || dataSource.app_slug,
|
|
426
|
+
dataSources: []
|
|
427
|
+
};
|
|
428
|
+
group.dataSources.push(dataSource);
|
|
429
|
+
groups.set(appKey, group);
|
|
430
|
+
}
|
|
431
|
+
const list = Array.from(groups.values());
|
|
432
|
+
for (const group of list) {
|
|
433
|
+
group.dataSources.sort((a, b) => compareByName(getDataSourceDisplayName(a), getDataSourceDisplayName(b)));
|
|
434
|
+
}
|
|
435
|
+
list.sort((a, b) => compareByName(a.appName, b.appName));
|
|
436
|
+
return list;
|
|
437
|
+
}
|
|
438
|
+
function filterDataSources(groups, query) {
|
|
439
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
440
|
+
if (normalizedQuery.length === 0) {
|
|
441
|
+
return groups;
|
|
442
|
+
}
|
|
443
|
+
return groups.map((group) => {
|
|
444
|
+
const filteredDataSources = group.dataSources.filter((dataSource) => {
|
|
445
|
+
const haystack = [
|
|
446
|
+
dataSource.title,
|
|
447
|
+
dataSource.name,
|
|
448
|
+
dataSource.slug
|
|
449
|
+
].filter((value) => Boolean(value)).join(" ").toLowerCase();
|
|
450
|
+
return haystack.includes(normalizedQuery);
|
|
451
|
+
});
|
|
452
|
+
return {
|
|
453
|
+
...group,
|
|
454
|
+
dataSources: filteredDataSources
|
|
455
|
+
};
|
|
456
|
+
}).filter((group) => group.dataSources.length > 0);
|
|
457
|
+
}
|
|
458
|
+
function flattenTreeRows(params) {
|
|
459
|
+
const rows = [];
|
|
460
|
+
const alwaysExpanded = params.query.trim().length > 0;
|
|
461
|
+
for (const group of params.groups) {
|
|
462
|
+
const expanded = alwaysExpanded || params.expandedAppKeys.has(group.appKey);
|
|
463
|
+
rows.push({
|
|
464
|
+
id: `app:${group.appKey}`,
|
|
465
|
+
kind: "app",
|
|
466
|
+
appKey: group.appKey,
|
|
467
|
+
appName: group.appName,
|
|
468
|
+
appSlug: group.appSlug,
|
|
469
|
+
expanded
|
|
470
|
+
});
|
|
471
|
+
if (!expanded) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
for (const dataSource of group.dataSources) {
|
|
475
|
+
const dataSourceName = getDataSourceDisplayName(dataSource);
|
|
476
|
+
rows.push({
|
|
477
|
+
id: `ds:${group.appKey}:${dataSource.id}`,
|
|
478
|
+
kind: "datasource",
|
|
479
|
+
appKey: group.appKey,
|
|
480
|
+
appName: group.appName,
|
|
481
|
+
appSlug: group.appSlug || dataSource.app_slug,
|
|
482
|
+
dataSourceId: dataSource.id,
|
|
483
|
+
dataSourceName,
|
|
484
|
+
dataSourceSlug: dataSource.slug
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return rows;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/tui/opentui/renderResult.tsx
|
|
492
|
+
import { RGBA, SyntaxStyle } from "@opentui/core";
|
|
493
|
+
|
|
494
|
+
// src/tui/opentui/resultDocument.ts
|
|
495
|
+
function toErrorPayload(entry) {
|
|
496
|
+
return {
|
|
497
|
+
message: entry.result.error?.message || "Command failed.",
|
|
498
|
+
details: entry.result.error?.details
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function buildPayload(entry) {
|
|
502
|
+
if (entry.result.ok) {
|
|
503
|
+
return entry.result.data ?? null;
|
|
504
|
+
}
|
|
505
|
+
return toErrorPayload(entry);
|
|
506
|
+
}
|
|
507
|
+
function buildResultDocument(entry) {
|
|
508
|
+
return {
|
|
509
|
+
command: entry.command || "(root)",
|
|
510
|
+
ok: entry.result.ok,
|
|
511
|
+
durationMs: entry.result.durationMs,
|
|
512
|
+
timestamp: new Date(entry.createdAt).toISOString(),
|
|
513
|
+
payload: buildPayload(entry),
|
|
514
|
+
messages: entry.result.messages,
|
|
515
|
+
rawOutput: entry.result.rawOutput || void 0
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function serializeResultDocument(document) {
|
|
519
|
+
try {
|
|
520
|
+
const result = JSON.stringify(document, null, 2);
|
|
521
|
+
if (!result) {
|
|
522
|
+
return "null";
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
527
|
+
return JSON.stringify({
|
|
528
|
+
command: document.command,
|
|
529
|
+
ok: false,
|
|
530
|
+
durationMs: document.durationMs,
|
|
531
|
+
timestamp: document.timestamp,
|
|
532
|
+
payload: {
|
|
533
|
+
message: "Serialization failed",
|
|
534
|
+
details: message
|
|
535
|
+
}
|
|
536
|
+
}, null, 2);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/tui/opentui/renderResult.tsx
|
|
541
|
+
import { jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
542
|
+
var jsonSyntaxStyle = null;
|
|
543
|
+
function getJsonSyntaxStyle() {
|
|
544
|
+
if (jsonSyntaxStyle) {
|
|
545
|
+
return jsonSyntaxStyle;
|
|
546
|
+
}
|
|
547
|
+
jsonSyntaxStyle = SyntaxStyle.fromStyles({
|
|
548
|
+
default: {
|
|
549
|
+
fg: RGBA.fromHex("#d4d4d4")
|
|
550
|
+
},
|
|
551
|
+
property: {
|
|
552
|
+
fg: RGBA.fromHex("#9cdcfe")
|
|
553
|
+
},
|
|
554
|
+
string: {
|
|
555
|
+
fg: RGBA.fromHex("#ce9178")
|
|
556
|
+
},
|
|
557
|
+
number: {
|
|
558
|
+
fg: RGBA.fromHex("#b5cea8")
|
|
559
|
+
},
|
|
560
|
+
keyword: {
|
|
561
|
+
fg: RGBA.fromHex("#569cd6")
|
|
562
|
+
},
|
|
563
|
+
constant: {
|
|
564
|
+
fg: RGBA.fromHex("#4fc1ff")
|
|
565
|
+
},
|
|
566
|
+
punctuation: {
|
|
567
|
+
fg: RGBA.fromHex("#c586c0")
|
|
568
|
+
},
|
|
569
|
+
operator: {
|
|
570
|
+
fg: RGBA.fromHex("#d4d4d4")
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
return jsonSyntaxStyle;
|
|
574
|
+
}
|
|
575
|
+
function renderResult(params) {
|
|
576
|
+
const {
|
|
577
|
+
entry,
|
|
578
|
+
isFocused
|
|
579
|
+
} = params;
|
|
580
|
+
if (!entry) {
|
|
581
|
+
return /* @__PURE__ */ jsx("text", { fg: "yellow", children: "No command executed yet." });
|
|
582
|
+
}
|
|
583
|
+
const document = buildResultDocument(entry);
|
|
584
|
+
const json = serializeResultDocument(document);
|
|
585
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", width: "100%", height: "100%", children: [
|
|
586
|
+
/* @__PURE__ */ jsx("box", { paddingBottom: 1, children: /* @__PURE__ */ jsxs("text", { fg: document.ok ? "green" : "red", children: [
|
|
587
|
+
document.ok ? "OK" : "ERR",
|
|
588
|
+
" ",
|
|
589
|
+
document.command,
|
|
590
|
+
" [",
|
|
591
|
+
document.durationMs,
|
|
592
|
+
"ms]"
|
|
593
|
+
] }) }),
|
|
594
|
+
/* @__PURE__ */ jsx("box", { flexGrow: 1, width: "100%", height: "100%", children: /* @__PURE__ */ jsx("scrollbox", { focused: isFocused, style: { height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
595
|
+
"code",
|
|
596
|
+
{
|
|
597
|
+
width: "100%",
|
|
598
|
+
content: json,
|
|
599
|
+
filetype: "json",
|
|
600
|
+
syntaxStyle: getJsonSyntaxStyle(),
|
|
601
|
+
streaming: false,
|
|
602
|
+
conceal: false
|
|
603
|
+
}
|
|
604
|
+
) }) })
|
|
605
|
+
] });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/tui/opentui/DocyrusOpenTuiApp.tsx
|
|
609
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
|
|
610
|
+
var SHORTCUTS = [
|
|
611
|
+
{
|
|
612
|
+
id: 1,
|
|
613
|
+
run: "env list",
|
|
614
|
+
label: "env list"
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: 2,
|
|
618
|
+
run: "auth who",
|
|
619
|
+
label: "auth who"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: 3,
|
|
623
|
+
run: "apps list",
|
|
624
|
+
label: "apps list"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: 4,
|
|
628
|
+
run: "discover namespaces",
|
|
629
|
+
label: "discover namespaces"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
id: 5,
|
|
633
|
+
template: "studio list-data-sources --appSlug ",
|
|
634
|
+
label: "insert: studio list-data-sources --appSlug "
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: 6,
|
|
638
|
+
template: "ds list <appSlug> <dataSourceSlug>",
|
|
639
|
+
label: "insert: ds list <appSlug> <dataSourceSlug>"
|
|
640
|
+
}
|
|
641
|
+
];
|
|
642
|
+
var SECTION_ORDER = ["shortcuts", "datasources", "history", "messages", "help"];
|
|
643
|
+
var FOCUS_ORDER = ["input", "left", "result"];
|
|
644
|
+
var ESC_ARM_TIMEOUT_MS = 1400;
|
|
645
|
+
function isRecord2(value) {
|
|
646
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
647
|
+
}
|
|
648
|
+
function extractMetadata(payload) {
|
|
649
|
+
if (!isRecord2(payload)) {
|
|
650
|
+
return {};
|
|
651
|
+
}
|
|
652
|
+
const environmentValue = payload.environment;
|
|
653
|
+
const contextValue = payload.context;
|
|
654
|
+
const environment = isRecord2(environmentValue) && typeof environmentValue.id === "string" && typeof environmentValue.name === "string" ? { id: environmentValue.id, name: environmentValue.name } : void 0;
|
|
655
|
+
if (contextValue === null) {
|
|
656
|
+
return {
|
|
657
|
+
environment,
|
|
658
|
+
context: null
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const context = isRecord2(contextValue) && typeof contextValue.email === "string" && typeof contextValue.tenantDisplay === "string" ? {
|
|
662
|
+
email: contextValue.email,
|
|
663
|
+
tenantDisplay: contextValue.tenantDisplay
|
|
664
|
+
} : void 0;
|
|
665
|
+
return {
|
|
666
|
+
environment,
|
|
667
|
+
context
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function toFailedResult(error, command) {
|
|
671
|
+
const message = error instanceof Error ? error.message : "Invalid command syntax.";
|
|
672
|
+
return {
|
|
673
|
+
ok: false,
|
|
674
|
+
command,
|
|
675
|
+
rawOutput: "",
|
|
676
|
+
error: {
|
|
677
|
+
message
|
|
678
|
+
},
|
|
679
|
+
durationMs: 0
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function formatHistoryLabel(entry) {
|
|
683
|
+
const status = entry.result.ok ? "OK" : "ERR";
|
|
684
|
+
const command = entry.command || "(root)";
|
|
685
|
+
return `${status} ${command} [${entry.result.durationMs}ms]`;
|
|
686
|
+
}
|
|
687
|
+
function preventDefault2(keyEvent) {
|
|
688
|
+
if (typeof keyEvent.preventDefault === "function") {
|
|
689
|
+
keyEvent.preventDefault();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function isEscapeKey(keyName) {
|
|
693
|
+
return keyName === "escape" || keyName === "esc";
|
|
694
|
+
}
|
|
695
|
+
function isEnterKey(keyName) {
|
|
696
|
+
return keyName === "enter" || keyName === "return";
|
|
697
|
+
}
|
|
698
|
+
function isSlashKey(keyName) {
|
|
699
|
+
return keyName === "/" || keyName === "slash";
|
|
700
|
+
}
|
|
701
|
+
function toCatalogErrorMessage(command, result) {
|
|
702
|
+
const details = result.error?.details || result.error?.message || "Unknown error.";
|
|
703
|
+
if (details.includes("Architect.Read.All") || details.includes("Architect.ReadWrite.All")) {
|
|
704
|
+
return `${command} failed: missing required scope Architect.Read.All or Architect.ReadWrite.All.`;
|
|
705
|
+
}
|
|
706
|
+
const message = result.error?.message || "Command failed.";
|
|
707
|
+
return `${command} failed: ${message}`;
|
|
708
|
+
}
|
|
709
|
+
function DocyrusOpenTuiApp(props) {
|
|
710
|
+
const renderer = useRenderer();
|
|
711
|
+
const [input, setInput] = useState2("");
|
|
712
|
+
const [entries, setEntries] = useState2([]);
|
|
713
|
+
const [activeEntryId, setActiveEntryId] = useState2(null);
|
|
714
|
+
const [systemMessages, setSystemMessages] = useState2([]);
|
|
715
|
+
const [activeSection, setActiveSection] = useState2("shortcuts");
|
|
716
|
+
const [focusedPanel, setFocusedPanel] = useState2("left");
|
|
717
|
+
const [environment, setEnvironment] = useState2();
|
|
718
|
+
const [context, setContext] = useState2(void 0);
|
|
719
|
+
const [isRunning, setIsRunning] = useState2(false);
|
|
720
|
+
const [spinnerFrame, setSpinnerFrame] = useState2(0);
|
|
721
|
+
const [selectedShortcutIndex, setSelectedShortcutIndex] = useState2(0);
|
|
722
|
+
const [selectedHistoryIndex, setSelectedHistoryIndex] = useState2(0);
|
|
723
|
+
const [isExitConfirmOpen, setIsExitConfirmOpen] = useState2(false);
|
|
724
|
+
const [isEscArmed, setIsEscArmed] = useState2(false);
|
|
725
|
+
const [dataSourceSearch, setDataSourceSearch] = useState2("");
|
|
726
|
+
const [dataSourcePanelFocus, setDataSourcePanelFocus] = useState2("tree");
|
|
727
|
+
const [selectedDataSourceRowIndex, setSelectedDataSourceRowIndex] = useState2(0);
|
|
728
|
+
const [expandedAppKeys, setExpandedAppKeys] = useState2([]);
|
|
729
|
+
const [appsCatalog, setAppsCatalog] = useState2([]);
|
|
730
|
+
const [dataSourcesCatalog, setDataSourcesCatalog] = useState2([]);
|
|
731
|
+
const [isCatalogLoading, setIsCatalogLoading] = useState2(false);
|
|
732
|
+
const [catalogError, setCatalogError] = useState2(null);
|
|
733
|
+
const [catalogLoadedAt, setCatalogLoadedAt] = useState2(null);
|
|
734
|
+
const catalogScopeRef = useRef("");
|
|
735
|
+
const executor = useMemo(() => {
|
|
736
|
+
return createTuiProcessExecutor({
|
|
737
|
+
executionConfig: props.executionConfig
|
|
738
|
+
});
|
|
739
|
+
}, [props.executionConfig]);
|
|
740
|
+
const applyMetadataFromPayload = useCallback2((payload) => {
|
|
741
|
+
const metadata = extractMetadata(payload);
|
|
742
|
+
if (metadata.environment) {
|
|
743
|
+
setEnvironment(metadata.environment);
|
|
744
|
+
}
|
|
745
|
+
if (metadata.context !== void 0) {
|
|
746
|
+
setContext(metadata.context);
|
|
747
|
+
}
|
|
748
|
+
}, []);
|
|
749
|
+
const appendSystemMessages = useCallback2((messages) => {
|
|
750
|
+
if (messages.length === 0) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
setSystemMessages((previous) => [...previous, ...messages]);
|
|
754
|
+
}, []);
|
|
755
|
+
const applyCommandResult = useCallback2((result, command) => {
|
|
756
|
+
const entry = {
|
|
757
|
+
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
758
|
+
command,
|
|
759
|
+
result,
|
|
760
|
+
createdAt: Date.now()
|
|
761
|
+
};
|
|
762
|
+
setEntries((previous) => [entry, ...previous]);
|
|
763
|
+
setActiveEntryId(entry.id);
|
|
764
|
+
setSelectedHistoryIndex(0);
|
|
765
|
+
if (result.messages && result.messages.length > 0) {
|
|
766
|
+
appendSystemMessages(result.messages);
|
|
767
|
+
}
|
|
768
|
+
if (result.ok && result.data !== void 0) {
|
|
769
|
+
applyMetadataFromPayload(result.data);
|
|
770
|
+
}
|
|
771
|
+
}, [appendSystemMessages, applyMetadataFromPayload]);
|
|
772
|
+
const executeArgs = useCallback2(async (args, commandText) => {
|
|
773
|
+
setIsRunning(true);
|
|
774
|
+
try {
|
|
775
|
+
const result = await executor.runCliCommand(args);
|
|
776
|
+
applyCommandResult(result, commandText);
|
|
777
|
+
} finally {
|
|
778
|
+
setIsRunning(false);
|
|
779
|
+
}
|
|
780
|
+
}, [applyCommandResult, executor]);
|
|
781
|
+
const runInternalArgs = useCallback2(async (args) => {
|
|
782
|
+
const result = await executor.runCliCommand(args);
|
|
783
|
+
if (result.ok && result.data !== void 0) {
|
|
784
|
+
applyMetadataFromPayload(result.data);
|
|
785
|
+
}
|
|
786
|
+
return result;
|
|
787
|
+
}, [applyMetadataFromPayload, executor]);
|
|
788
|
+
const executeLine = useCallback2((line) => {
|
|
789
|
+
let args;
|
|
790
|
+
try {
|
|
791
|
+
args = parseCommandLine(line);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
applyCommandResult(toFailedResult(error, line.trim()), line.trim());
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (args.length === 0) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
void executeArgs(args, line.trim());
|
|
800
|
+
}, [applyCommandResult, executeArgs]);
|
|
801
|
+
const runHistoryEntry = useCallback2(async (entry) => {
|
|
802
|
+
if (entry.command === "(root)") {
|
|
803
|
+
await executeArgs([], "(root)");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const args = parseCommandLine(entry.command);
|
|
808
|
+
await executeArgs(args, entry.command);
|
|
809
|
+
} catch (error) {
|
|
810
|
+
applyCommandResult(toFailedResult(error, entry.command), entry.command);
|
|
811
|
+
}
|
|
812
|
+
}, [applyCommandResult, executeArgs]);
|
|
813
|
+
const applyShortcut = useCallback2((shortcutId) => {
|
|
814
|
+
const action = SHORTCUTS.find((shortcut) => shortcut.id === shortcutId);
|
|
815
|
+
if (!action || isRunning || isCatalogLoading) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (action.template) {
|
|
819
|
+
setInput(action.template);
|
|
820
|
+
setFocusedPanel("input");
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (action.run) {
|
|
824
|
+
void executeArgs(parseCommandLine(action.run), action.run);
|
|
825
|
+
}
|
|
826
|
+
}, [executeArgs, isCatalogLoading, isRunning]);
|
|
827
|
+
const groupedDataSourceItems = useMemo(() => {
|
|
828
|
+
return groupDataSourcesByApp({
|
|
829
|
+
apps: appsCatalog,
|
|
830
|
+
dataSources: dataSourcesCatalog
|
|
831
|
+
});
|
|
832
|
+
}, [appsCatalog, dataSourcesCatalog]);
|
|
833
|
+
const filteredDataSourceGroups = useMemo(() => {
|
|
834
|
+
return filterDataSources(groupedDataSourceItems, dataSourceSearch);
|
|
835
|
+
}, [dataSourceSearch, groupedDataSourceItems]);
|
|
836
|
+
const dataSourceCountByAppKey = useMemo(() => {
|
|
837
|
+
const result = /* @__PURE__ */ new Map();
|
|
838
|
+
for (const group of filteredDataSourceGroups) {
|
|
839
|
+
result.set(group.appKey, group.dataSources.length);
|
|
840
|
+
}
|
|
841
|
+
return result;
|
|
842
|
+
}, [filteredDataSourceGroups]);
|
|
843
|
+
const expandedAppKeySet = useMemo(() => {
|
|
844
|
+
return new Set(expandedAppKeys);
|
|
845
|
+
}, [expandedAppKeys]);
|
|
846
|
+
const dataSourceRows = useMemo(() => {
|
|
847
|
+
return flattenTreeRows({
|
|
848
|
+
groups: filteredDataSourceGroups,
|
|
849
|
+
expandedAppKeys: expandedAppKeySet,
|
|
850
|
+
query: dataSourceSearch
|
|
851
|
+
});
|
|
852
|
+
}, [dataSourceSearch, expandedAppKeySet, filteredDataSourceGroups]);
|
|
853
|
+
const selectedDataSourceRow = useMemo(() => {
|
|
854
|
+
if (dataSourceRows.length === 0) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
return dataSourceRows[selectedDataSourceRowIndex] || dataSourceRows[0] || null;
|
|
858
|
+
}, [dataSourceRows, selectedDataSourceRowIndex]);
|
|
859
|
+
const toggleAppExpansion = useCallback2((appKey) => {
|
|
860
|
+
setExpandedAppKeys((previous) => {
|
|
861
|
+
if (previous.includes(appKey)) {
|
|
862
|
+
return previous.filter((key) => key !== appKey);
|
|
863
|
+
}
|
|
864
|
+
return [...previous, appKey];
|
|
865
|
+
});
|
|
866
|
+
}, []);
|
|
867
|
+
const loadDataSourceCatalog = useCallback2(async () => {
|
|
868
|
+
if (isCatalogLoading) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
setIsCatalogLoading(true);
|
|
872
|
+
setCatalogError(null);
|
|
873
|
+
try {
|
|
874
|
+
const nextMessages = [];
|
|
875
|
+
const appsResult = await runInternalArgs(["apps", "list"]);
|
|
876
|
+
const dataSourcesResult = await runInternalArgs(["curl", "/dev/data-sources"]);
|
|
877
|
+
const nextApps = appsResult.ok && appsResult.data !== void 0 ? extractAppsFromPayload(appsResult.data) : [];
|
|
878
|
+
const nextDataSources = dataSourcesResult.ok && dataSourcesResult.data !== void 0 ? extractDataSourcesFromPayload(dataSourcesResult.data) : [];
|
|
879
|
+
if (!appsResult.ok) {
|
|
880
|
+
nextMessages.push(toCatalogErrorMessage("apps list", appsResult));
|
|
881
|
+
}
|
|
882
|
+
if (!dataSourcesResult.ok) {
|
|
883
|
+
nextMessages.push(toCatalogErrorMessage("curl /dev/data-sources", dataSourcesResult));
|
|
884
|
+
}
|
|
885
|
+
setAppsCatalog(nextApps);
|
|
886
|
+
setDataSourcesCatalog(nextDataSources);
|
|
887
|
+
setCatalogLoadedAt(Date.now());
|
|
888
|
+
const nextGroups = groupDataSourcesByApp({
|
|
889
|
+
apps: nextApps,
|
|
890
|
+
dataSources: nextDataSources
|
|
891
|
+
});
|
|
892
|
+
setExpandedAppKeys((previous) => {
|
|
893
|
+
if (previous.length > 0) {
|
|
894
|
+
const allowed = new Set(nextGroups.map((group) => group.appKey));
|
|
895
|
+
return previous.filter((key) => allowed.has(key));
|
|
896
|
+
}
|
|
897
|
+
return nextGroups.map((group) => group.appKey);
|
|
898
|
+
});
|
|
899
|
+
if (nextMessages.length > 0) {
|
|
900
|
+
appendSystemMessages(nextMessages);
|
|
901
|
+
setCatalogError(nextMessages[0] || "Failed to load catalog.");
|
|
902
|
+
} else {
|
|
903
|
+
setCatalogError(null);
|
|
904
|
+
}
|
|
905
|
+
} finally {
|
|
906
|
+
setIsCatalogLoading(false);
|
|
907
|
+
}
|
|
908
|
+
}, [appendSystemMessages, isCatalogLoading, runInternalArgs]);
|
|
909
|
+
const runSelectedDataSourceRow = useCallback2(async (row) => {
|
|
910
|
+
if (!row || isRunning || isCatalogLoading) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (row.kind === "app") {
|
|
914
|
+
toggleAppExpansion(row.appKey);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (!row.appSlug || !row.dataSourceSlug) {
|
|
918
|
+
appendSystemMessages([
|
|
919
|
+
"Cannot execute ds get because selected row is missing appSlug or dataSourceSlug."
|
|
920
|
+
]);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
const command = `ds get ${row.appSlug} ${row.dataSourceSlug}`;
|
|
924
|
+
setFocusedPanel("result");
|
|
925
|
+
await executeArgs(["ds", "get", row.appSlug, row.dataSourceSlug], command);
|
|
926
|
+
}, [appendSystemMessages, executeArgs, isCatalogLoading, isRunning, toggleAppExpansion]);
|
|
927
|
+
const setSectionByIndex = useCallback2((index) => {
|
|
928
|
+
const safeIndex = index < 0 ? SECTION_ORDER.length - 1 : index >= SECTION_ORDER.length ? 0 : index;
|
|
929
|
+
const section = SECTION_ORDER[safeIndex];
|
|
930
|
+
if (section) {
|
|
931
|
+
setActiveSection(section);
|
|
932
|
+
}
|
|
933
|
+
}, []);
|
|
934
|
+
const nextSection = useCallback2(() => {
|
|
935
|
+
const currentIndex = SECTION_ORDER.indexOf(activeSection);
|
|
936
|
+
setSectionByIndex(currentIndex + 1);
|
|
937
|
+
}, [activeSection, setSectionByIndex]);
|
|
938
|
+
const previousSection = useCallback2(() => {
|
|
939
|
+
const currentIndex = SECTION_ORDER.indexOf(activeSection);
|
|
940
|
+
setSectionByIndex(currentIndex - 1);
|
|
941
|
+
}, [activeSection, setSectionByIndex]);
|
|
942
|
+
const setPanelByIndex = useCallback2((index) => {
|
|
943
|
+
const safeIndex = index < 0 ? FOCUS_ORDER.length - 1 : index >= FOCUS_ORDER.length ? 0 : index;
|
|
944
|
+
const panel = FOCUS_ORDER[safeIndex];
|
|
945
|
+
if (panel) {
|
|
946
|
+
setFocusedPanel(panel);
|
|
947
|
+
}
|
|
948
|
+
}, []);
|
|
949
|
+
const nextPanel = useCallback2(() => {
|
|
950
|
+
const currentIndex = FOCUS_ORDER.indexOf(focusedPanel);
|
|
951
|
+
setPanelByIndex(currentIndex + 1);
|
|
952
|
+
}, [focusedPanel, setPanelByIndex]);
|
|
953
|
+
const previousPanel = useCallback2(() => {
|
|
954
|
+
const currentIndex = FOCUS_ORDER.indexOf(focusedPanel);
|
|
955
|
+
setPanelByIndex(currentIndex - 1);
|
|
956
|
+
}, [focusedPanel, setPanelByIndex]);
|
|
957
|
+
const openExitConfirmation = useCallback2(() => {
|
|
958
|
+
setIsEscArmed(false);
|
|
959
|
+
setIsExitConfirmOpen(true);
|
|
960
|
+
}, []);
|
|
961
|
+
const closeExitConfirmation = useCallback2(() => {
|
|
962
|
+
setIsExitConfirmOpen(false);
|
|
963
|
+
setIsEscArmed(false);
|
|
964
|
+
}, []);
|
|
965
|
+
const armEscapeExit = useCallback2(() => {
|
|
966
|
+
setIsEscArmed(true);
|
|
967
|
+
setSystemMessages((previous) => {
|
|
968
|
+
const lastMessage = previous[previous.length - 1];
|
|
969
|
+
const message = "Press Esc again to open exit confirmation.";
|
|
970
|
+
if (lastMessage === message) {
|
|
971
|
+
return previous;
|
|
972
|
+
}
|
|
973
|
+
return [...previous, message];
|
|
974
|
+
});
|
|
975
|
+
}, []);
|
|
976
|
+
const isDataSourceSearchFocused = activeSection === "datasources" && focusedPanel === "left" && dataSourcePanelFocus === "search" && !isRunning && !isExitConfirmOpen;
|
|
977
|
+
const { submitInput } = useCommandInput({
|
|
978
|
+
busy: isRunning || isCatalogLoading,
|
|
979
|
+
input,
|
|
980
|
+
isInputFocused: focusedPanel === "input",
|
|
981
|
+
isModalOpen: isExitConfirmOpen,
|
|
982
|
+
hotkeysEnabled: !isRunning && !isDataSourceSearchFocused,
|
|
983
|
+
setInput,
|
|
984
|
+
onSubmit: executeLine,
|
|
985
|
+
onClear: () => {
|
|
986
|
+
setEntries([]);
|
|
987
|
+
setActiveEntryId(null);
|
|
988
|
+
setSystemMessages([]);
|
|
989
|
+
setSelectedHistoryIndex(0);
|
|
990
|
+
},
|
|
991
|
+
onShortcut: applyShortcut,
|
|
992
|
+
onNextSection: nextSection,
|
|
993
|
+
onPreviousSection: previousSection
|
|
994
|
+
});
|
|
995
|
+
useKeyboard2((keyEvent) => {
|
|
996
|
+
const keyName = keyEvent.name;
|
|
997
|
+
if (isExitConfirmOpen) {
|
|
998
|
+
if (isEscapeKey(keyName)) {
|
|
999
|
+
preventDefault2(keyEvent);
|
|
1000
|
+
closeExitConfirmation();
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
if (isEnterKey(keyName)) {
|
|
1004
|
+
preventDefault2(keyEvent);
|
|
1005
|
+
renderer.destroy();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (keyEvent.ctrl && keyName === "c") {
|
|
1009
|
+
renderer.destroy();
|
|
1010
|
+
}
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (activeSection === "datasources" && focusedPanel === "left" && dataSourcePanelFocus === "search" && isEscapeKey(keyName)) {
|
|
1014
|
+
preventDefault2(keyEvent);
|
|
1015
|
+
setDataSourcePanelFocus("tree");
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (activeSection === "datasources" && focusedPanel === "left" && dataSourcePanelFocus === "tree" && isSlashKey(keyName)) {
|
|
1019
|
+
preventDefault2(keyEvent);
|
|
1020
|
+
setDataSourcePanelFocus("search");
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (keyEvent.ctrl && keyName === "c") {
|
|
1024
|
+
renderer.destroy();
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (isRunning) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (isEscapeKey(keyName)) {
|
|
1031
|
+
preventDefault2(keyEvent);
|
|
1032
|
+
if (focusedPanel !== "input") {
|
|
1033
|
+
setFocusedPanel("input");
|
|
1034
|
+
armEscapeExit();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (!isEscArmed) {
|
|
1038
|
+
armEscapeExit();
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
openExitConfirmation();
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (activeSection === "datasources" && focusedPanel === "left" && dataSourcePanelFocus === "tree" && keyName === "space") {
|
|
1045
|
+
preventDefault2(keyEvent);
|
|
1046
|
+
if (selectedDataSourceRow?.kind === "app") {
|
|
1047
|
+
toggleAppExpansion(selectedDataSourceRow.appKey);
|
|
1048
|
+
}
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (keyName === "left" || keyName === "right") {
|
|
1052
|
+
if (focusedPanel === "input" && input.length > 0) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
preventDefault2(keyEvent);
|
|
1056
|
+
setIsEscArmed(false);
|
|
1057
|
+
if (keyName === "left") {
|
|
1058
|
+
previousPanel();
|
|
1059
|
+
} else {
|
|
1060
|
+
nextPanel();
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (keyName === "r" && activeSection === "datasources" && focusedPanel === "left" && dataSourcePanelFocus === "tree" && !isCatalogLoading) {
|
|
1065
|
+
preventDefault2(keyEvent);
|
|
1066
|
+
void loadDataSourceCatalog();
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (keyName === "r" && activeSection === "history" && focusedPanel === "left" && input.length === 0 && !isRunning && !isCatalogLoading) {
|
|
1070
|
+
const entry = entries[selectedHistoryIndex];
|
|
1071
|
+
if (entry) {
|
|
1072
|
+
void runHistoryEntry(entry);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
useEffect(() => {
|
|
1077
|
+
void executeArgs([], "(root)");
|
|
1078
|
+
}, [executeArgs]);
|
|
1079
|
+
useEffect(() => {
|
|
1080
|
+
if (!isEscArmed) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const timer = setTimeout(() => {
|
|
1084
|
+
setIsEscArmed(false);
|
|
1085
|
+
}, ESC_ARM_TIMEOUT_MS);
|
|
1086
|
+
return () => {
|
|
1087
|
+
clearTimeout(timer);
|
|
1088
|
+
};
|
|
1089
|
+
}, [isEscArmed]);
|
|
1090
|
+
useEffect(() => {
|
|
1091
|
+
if (!isRunning && !isCatalogLoading) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const timer = setInterval(() => {
|
|
1095
|
+
setSpinnerFrame((previous) => (previous + 1) % 4);
|
|
1096
|
+
}, 120);
|
|
1097
|
+
return () => {
|
|
1098
|
+
clearInterval(timer);
|
|
1099
|
+
};
|
|
1100
|
+
}, [isCatalogLoading, isRunning]);
|
|
1101
|
+
useEffect(() => {
|
|
1102
|
+
if (selectedHistoryIndex < entries.length) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const nextIndex = Math.max(entries.length - 1, 0);
|
|
1106
|
+
setSelectedHistoryIndex(nextIndex);
|
|
1107
|
+
}, [entries.length, selectedHistoryIndex]);
|
|
1108
|
+
useEffect(() => {
|
|
1109
|
+
if (selectedDataSourceRowIndex < dataSourceRows.length) {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
const nextIndex = Math.max(dataSourceRows.length - 1, 0);
|
|
1113
|
+
setSelectedDataSourceRowIndex(nextIndex);
|
|
1114
|
+
}, [dataSourceRows.length, selectedDataSourceRowIndex]);
|
|
1115
|
+
useEffect(() => {
|
|
1116
|
+
if (activeSection !== "datasources" && dataSourcePanelFocus !== "tree") {
|
|
1117
|
+
setDataSourcePanelFocus("tree");
|
|
1118
|
+
}
|
|
1119
|
+
}, [activeSection, dataSourcePanelFocus]);
|
|
1120
|
+
useEffect(() => {
|
|
1121
|
+
const scopeKey = `${environment?.id || ""}::${context?.tenantDisplay || ""}`;
|
|
1122
|
+
if (catalogScopeRef.current.length === 0) {
|
|
1123
|
+
catalogScopeRef.current = scopeKey;
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (catalogScopeRef.current === scopeKey) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
catalogScopeRef.current = scopeKey;
|
|
1130
|
+
if (catalogLoadedAt !== null) {
|
|
1131
|
+
setCatalogLoadedAt(null);
|
|
1132
|
+
setAppsCatalog([]);
|
|
1133
|
+
setDataSourcesCatalog([]);
|
|
1134
|
+
setExpandedAppKeys([]);
|
|
1135
|
+
setSelectedDataSourceRowIndex(0);
|
|
1136
|
+
setCatalogError(null);
|
|
1137
|
+
}
|
|
1138
|
+
}, [catalogLoadedAt, context?.tenantDisplay, environment?.id]);
|
|
1139
|
+
useEffect(() => {
|
|
1140
|
+
if (activeSection !== "datasources" || catalogLoadedAt !== null || isCatalogLoading) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
void loadDataSourceCatalog();
|
|
1144
|
+
}, [activeSection, catalogLoadedAt, isCatalogLoading, loadDataSourceCatalog]);
|
|
1145
|
+
const spinner = useMemo(() => {
|
|
1146
|
+
return ["|", "/", "-", "\\"][spinnerFrame] || "|";
|
|
1147
|
+
}, [spinnerFrame]);
|
|
1148
|
+
const activeEntry = useMemo(() => {
|
|
1149
|
+
if (activeEntryId) {
|
|
1150
|
+
const found = entries.find((entry) => entry.id === activeEntryId);
|
|
1151
|
+
if (found) {
|
|
1152
|
+
return found;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return entries[0] || null;
|
|
1156
|
+
}, [activeEntryId, entries]);
|
|
1157
|
+
const sectionOptions = useMemo(() => {
|
|
1158
|
+
const ordered = [
|
|
1159
|
+
activeSection,
|
|
1160
|
+
...SECTION_ORDER.filter((section) => section !== activeSection)
|
|
1161
|
+
];
|
|
1162
|
+
return ordered.map((section) => ({
|
|
1163
|
+
name: section === "shortcuts" ? "Shortcuts" : section === "datasources" ? "DataSources" : section === "history" ? "History" : section === "messages" ? "Messages" : "Help",
|
|
1164
|
+
description: section === "shortcuts" ? "Quick actions" : section === "datasources" ? "App tree" : section === "history" ? "Past runs" : section === "messages" ? "System output" : "Key map",
|
|
1165
|
+
value: section
|
|
1166
|
+
}));
|
|
1167
|
+
}, [activeSection]);
|
|
1168
|
+
const shortcutOptions = useMemo(() => {
|
|
1169
|
+
return SHORTCUTS.map((shortcut) => ({
|
|
1170
|
+
name: `${shortcut.id}. ${shortcut.label}`,
|
|
1171
|
+
description: shortcut.run || shortcut.template || "",
|
|
1172
|
+
value: shortcut.id
|
|
1173
|
+
}));
|
|
1174
|
+
}, []);
|
|
1175
|
+
const historyOptions = useMemo(() => {
|
|
1176
|
+
return entries.map((entry) => ({
|
|
1177
|
+
name: formatHistoryLabel(entry),
|
|
1178
|
+
description: new Date(entry.createdAt).toLocaleTimeString(),
|
|
1179
|
+
value: entry.id
|
|
1180
|
+
}));
|
|
1181
|
+
}, [entries]);
|
|
1182
|
+
const dataSourceOptions = useMemo(() => {
|
|
1183
|
+
return dataSourceRows.map((row) => {
|
|
1184
|
+
if (row.kind === "app") {
|
|
1185
|
+
const itemCount = dataSourceCountByAppKey.get(row.appKey) || 0;
|
|
1186
|
+
return {
|
|
1187
|
+
name: `${row.expanded ? "[-]" : "[+]"} ${row.appName}${row.appSlug ? ` (${row.appSlug})` : ""}`,
|
|
1188
|
+
description: `${itemCount} data source${itemCount === 1 ? "" : "s"}`,
|
|
1189
|
+
value: row.id
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
const dataSourceLabel = row.dataSourceName || row.dataSourceSlug || row.dataSourceId || "Unknown";
|
|
1193
|
+
return {
|
|
1194
|
+
name: ` - ${dataSourceLabel}${row.dataSourceSlug ? ` (${row.dataSourceSlug})` : ""}`,
|
|
1195
|
+
description: row.appSlug ? `app: ${row.appSlug}` : "missing app slug",
|
|
1196
|
+
value: row.id
|
|
1197
|
+
};
|
|
1198
|
+
});
|
|
1199
|
+
}, [dataSourceCountByAppKey, dataSourceRows]);
|
|
1200
|
+
const renderLeftSection = () => {
|
|
1201
|
+
if (activeSection === "shortcuts") {
|
|
1202
|
+
return /* @__PURE__ */ jsx2(
|
|
1203
|
+
"select",
|
|
1204
|
+
{
|
|
1205
|
+
focused: focusedPanel === "left" && !isExitConfirmOpen && !isRunning,
|
|
1206
|
+
options: shortcutOptions,
|
|
1207
|
+
selectedIndex: selectedShortcutIndex,
|
|
1208
|
+
onChange: (index) => {
|
|
1209
|
+
setSelectedShortcutIndex(index);
|
|
1210
|
+
},
|
|
1211
|
+
onSelect: (index, option) => {
|
|
1212
|
+
setSelectedShortcutIndex(index);
|
|
1213
|
+
const shortcutId = Number(option?.value || 0);
|
|
1214
|
+
if (Number.isFinite(shortcutId) && shortcutId > 0) {
|
|
1215
|
+
applyShortcut(shortcutId);
|
|
1216
|
+
}
|
|
1217
|
+
},
|
|
1218
|
+
showScrollIndicator: true,
|
|
1219
|
+
style: {
|
|
1220
|
+
flexGrow: 1
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
if (activeSection === "datasources") {
|
|
1226
|
+
const treeFocused = focusedPanel === "left" && dataSourcePanelFocus === "tree" && !isRunning && !isExitConfirmOpen;
|
|
1227
|
+
const searchFocused = focusedPanel === "left" && dataSourcePanelFocus === "search" && !isRunning && !isExitConfirmOpen;
|
|
1228
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", flexGrow: 1, children: [
|
|
1229
|
+
/* @__PURE__ */ jsx2("box", { border: true, borderStyle: "single", borderColor: searchFocused ? "#7aa2f7" : "#414868", paddingX: 1, children: /* @__PURE__ */ jsx2(
|
|
1230
|
+
"input",
|
|
1231
|
+
{
|
|
1232
|
+
value: dataSourceSearch,
|
|
1233
|
+
placeholder: "Search data sources...",
|
|
1234
|
+
onChange: (value) => {
|
|
1235
|
+
setDataSourceSearch(value);
|
|
1236
|
+
setSelectedDataSourceRowIndex(0);
|
|
1237
|
+
},
|
|
1238
|
+
onSubmit: () => {
|
|
1239
|
+
setDataSourcePanelFocus("tree");
|
|
1240
|
+
},
|
|
1241
|
+
focused: searchFocused,
|
|
1242
|
+
style: {
|
|
1243
|
+
flexGrow: 1
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
) }),
|
|
1247
|
+
/* @__PURE__ */ jsx2("box", { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx2("text", { fg: "gray", children: "/ to search, Esc to tree, Space toggle app, Enter run ds get, r refresh" }) }),
|
|
1248
|
+
isCatalogLoading && /* @__PURE__ */ jsx2("text", { fg: "cyan", children: "Loading DataSources catalog..." }),
|
|
1249
|
+
catalogError && /* @__PURE__ */ jsx2("text", { fg: "red", children: catalogError }),
|
|
1250
|
+
dataSourceOptions.length === 0 ? /* @__PURE__ */ jsx2("text", { fg: "gray", children: dataSourceSearch.trim().length > 0 ? `No data sources match "${dataSourceSearch}".` : "No data sources available for this tenant." }) : /* @__PURE__ */ jsx2(
|
|
1251
|
+
"select",
|
|
1252
|
+
{
|
|
1253
|
+
focused: treeFocused,
|
|
1254
|
+
options: dataSourceOptions,
|
|
1255
|
+
selectedIndex: selectedDataSourceRowIndex,
|
|
1256
|
+
onChange: (index) => {
|
|
1257
|
+
setSelectedDataSourceRowIndex(index);
|
|
1258
|
+
},
|
|
1259
|
+
onSelect: (index) => {
|
|
1260
|
+
setSelectedDataSourceRowIndex(index);
|
|
1261
|
+
const row = dataSourceRows[index] || null;
|
|
1262
|
+
void runSelectedDataSourceRow(row);
|
|
1263
|
+
},
|
|
1264
|
+
showScrollIndicator: true,
|
|
1265
|
+
style: {
|
|
1266
|
+
flexGrow: 1
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
] });
|
|
1271
|
+
}
|
|
1272
|
+
if (activeSection === "history") {
|
|
1273
|
+
if (entries.length === 0) {
|
|
1274
|
+
return /* @__PURE__ */ jsx2("text", { fg: "gray", children: "No command history yet." });
|
|
1275
|
+
}
|
|
1276
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", flexGrow: 1, children: [
|
|
1277
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "Press `r` to re-run selected command." }),
|
|
1278
|
+
/* @__PURE__ */ jsx2(
|
|
1279
|
+
"select",
|
|
1280
|
+
{
|
|
1281
|
+
focused: focusedPanel === "left" && !isExitConfirmOpen && !isRunning,
|
|
1282
|
+
options: historyOptions,
|
|
1283
|
+
selectedIndex: selectedHistoryIndex,
|
|
1284
|
+
onChange: (index, option) => {
|
|
1285
|
+
setSelectedHistoryIndex(index);
|
|
1286
|
+
if (typeof option?.value === "string") {
|
|
1287
|
+
setActiveEntryId(option.value);
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
onSelect: (index, option) => {
|
|
1291
|
+
setSelectedHistoryIndex(index);
|
|
1292
|
+
if (typeof option?.value === "string") {
|
|
1293
|
+
setActiveEntryId(option.value);
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
showScrollIndicator: true,
|
|
1297
|
+
style: {
|
|
1298
|
+
flexGrow: 1
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
)
|
|
1302
|
+
] });
|
|
1303
|
+
}
|
|
1304
|
+
if (activeSection === "messages") {
|
|
1305
|
+
return /* @__PURE__ */ jsxs2("scrollbox", { focused: focusedPanel === "left" && !isExitConfirmOpen && !isRunning, style: { height: "100%" }, children: [
|
|
1306
|
+
systemMessages.length === 0 && /* @__PURE__ */ jsx2("text", { fg: "gray", children: "No messages yet." }),
|
|
1307
|
+
systemMessages.map((line, index) => /* @__PURE__ */ jsx2("text", { children: line }, `message-${index}`))
|
|
1308
|
+
] });
|
|
1309
|
+
}
|
|
1310
|
+
return /* @__PURE__ */ jsxs2("scrollbox", { focused: focusedPanel === "left" && !isExitConfirmOpen && !isRunning, style: { height: "100%" }, children: [
|
|
1311
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "Key Bindings" }),
|
|
1312
|
+
/* @__PURE__ */ jsx2("text", { children: "Left/Right: switch focused panel" }),
|
|
1313
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in left lists: selection" }),
|
|
1314
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in result: scroll" }),
|
|
1315
|
+
/* @__PURE__ */ jsx2("text", { children: "Enter: run command" }),
|
|
1316
|
+
/* @__PURE__ */ jsx2("text", { children: "Up/Down in input: command history" }),
|
|
1317
|
+
/* @__PURE__ */ jsx2("text", { children: "Tab / Shift+Tab: next/prev left section" }),
|
|
1318
|
+
/* @__PURE__ */ jsx2("text", { children: "[ / ]: next/prev left section" }),
|
|
1319
|
+
/* @__PURE__ */ jsx2("text", { children: "1..6: run shortcut" }),
|
|
1320
|
+
/* @__PURE__ */ jsx2("text", { children: "/ in DataSources: focus search" }),
|
|
1321
|
+
/* @__PURE__ */ jsx2("text", { children: "Esc in DataSources search: return to tree" }),
|
|
1322
|
+
/* @__PURE__ */ jsx2("text", { children: "Space in DataSources tree: toggle app folder" }),
|
|
1323
|
+
/* @__PURE__ */ jsx2("text", { children: "Enter in DataSources tree: run ds get or toggle app" }),
|
|
1324
|
+
/* @__PURE__ */ jsx2("text", { children: "Ctrl+L: clear command and message history" }),
|
|
1325
|
+
/* @__PURE__ */ jsx2("text", { children: "Esc (other panel): focus command input" }),
|
|
1326
|
+
/* @__PURE__ */ jsx2("text", { children: "Esc then Esc: open exit confirmation" }),
|
|
1327
|
+
/* @__PURE__ */ jsx2("text", { children: "Enter (confirm): exit, Esc (confirm): cancel" }),
|
|
1328
|
+
/* @__PURE__ */ jsx2("text", { children: "Ctrl+C: force quit" })
|
|
1329
|
+
] });
|
|
1330
|
+
};
|
|
1331
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "column", width: "100%", height: "100%", padding: 1, children: [
|
|
1332
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, paddingX: 1, children: [
|
|
1333
|
+
/* @__PURE__ */ jsx2("ascii-font", { text: "DOCYRUS", font: "tiny", color: "#7aa2f7" }),
|
|
1334
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "column", alignItems: "flex-end", justifyContent: "center", children: [
|
|
1335
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
1336
|
+
"env: ",
|
|
1337
|
+
environment ? `${environment.name} (${environment.id})` : "unknown"
|
|
1338
|
+
] }),
|
|
1339
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
1340
|
+
"status: ",
|
|
1341
|
+
isRunning ? `${spinner} running` : isCatalogLoading ? `${spinner} loading-catalog` : "idle"
|
|
1342
|
+
] }),
|
|
1343
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
1344
|
+
"account: ",
|
|
1345
|
+
context ? context.email : "not-authenticated"
|
|
1346
|
+
] }),
|
|
1347
|
+
/* @__PURE__ */ jsxs2("text", { children: [
|
|
1348
|
+
"tenant: ",
|
|
1349
|
+
context ? context.tenantDisplay : "-"
|
|
1350
|
+
] })
|
|
1351
|
+
] })
|
|
1352
|
+
] }),
|
|
1353
|
+
/* @__PURE__ */ jsxs2("box", { flexDirection: "row", flexGrow: 1, children: [
|
|
1354
|
+
/* @__PURE__ */ jsxs2(
|
|
1355
|
+
"box",
|
|
1356
|
+
{
|
|
1357
|
+
width: 46,
|
|
1358
|
+
marginRight: 1,
|
|
1359
|
+
border: true,
|
|
1360
|
+
borderStyle: "rounded",
|
|
1361
|
+
borderColor: focusedPanel === "left" ? "#7aa2f7" : "#3b4261",
|
|
1362
|
+
padding: 1,
|
|
1363
|
+
flexDirection: "column",
|
|
1364
|
+
children: [
|
|
1365
|
+
/* @__PURE__ */ jsx2(
|
|
1366
|
+
"tab-select",
|
|
1367
|
+
{
|
|
1368
|
+
focused: focusedPanel === "left" && !isExitConfirmOpen && !isRunning,
|
|
1369
|
+
options: sectionOptions,
|
|
1370
|
+
onChange: (_index, option) => {
|
|
1371
|
+
if (typeof option?.value === "string") {
|
|
1372
|
+
setActiveSection(option.value);
|
|
1373
|
+
setDataSourcePanelFocus("tree");
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
),
|
|
1378
|
+
/* @__PURE__ */ jsx2("box", { marginTop: 1, flexGrow: 1, border: true, borderStyle: "single", borderColor: "#414868", padding: 1, children: renderLeftSection() })
|
|
1379
|
+
]
|
|
1380
|
+
}
|
|
1381
|
+
),
|
|
1382
|
+
/* @__PURE__ */ jsx2(
|
|
1383
|
+
"box",
|
|
1384
|
+
{
|
|
1385
|
+
border: true,
|
|
1386
|
+
borderStyle: "double",
|
|
1387
|
+
borderColor: focusedPanel === "result" ? "#9ece6a" : "#3b4261",
|
|
1388
|
+
padding: 1,
|
|
1389
|
+
flexGrow: 1,
|
|
1390
|
+
children: isRunning ? /* @__PURE__ */ jsxs2("box", { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", flexDirection: "column", children: [
|
|
1391
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: spinner }),
|
|
1392
|
+
/* @__PURE__ */ jsx2("text", { fg: "gray", children: "Running command..." })
|
|
1393
|
+
] }) : renderResult({
|
|
1394
|
+
entry: activeEntry,
|
|
1395
|
+
isFocused: focusedPanel === "result" && !isExitConfirmOpen
|
|
1396
|
+
})
|
|
1397
|
+
}
|
|
1398
|
+
)
|
|
1399
|
+
] }),
|
|
1400
|
+
/* @__PURE__ */ jsxs2(
|
|
1401
|
+
"box",
|
|
1402
|
+
{
|
|
1403
|
+
border: true,
|
|
1404
|
+
borderStyle: "rounded",
|
|
1405
|
+
borderColor: focusedPanel === "input" ? "#e0af68" : "#3b4261",
|
|
1406
|
+
padding: 1,
|
|
1407
|
+
marginTop: 1,
|
|
1408
|
+
children: [
|
|
1409
|
+
/* @__PURE__ */ jsx2("text", { fg: "cyan", children: "> " }),
|
|
1410
|
+
/* @__PURE__ */ jsx2(
|
|
1411
|
+
"input",
|
|
1412
|
+
{
|
|
1413
|
+
style: { flexGrow: 1 },
|
|
1414
|
+
value: input,
|
|
1415
|
+
placeholder: "Type a command...",
|
|
1416
|
+
onChange: (value) => {
|
|
1417
|
+
setInput(value);
|
|
1418
|
+
},
|
|
1419
|
+
onSubmit: (value) => {
|
|
1420
|
+
submitInput(typeof value === "string" ? value : void 0);
|
|
1421
|
+
},
|
|
1422
|
+
focused: !isRunning && !isCatalogLoading && focusedPanel === "input" && !isExitConfirmOpen
|
|
1423
|
+
}
|
|
1424
|
+
)
|
|
1425
|
+
]
|
|
1426
|
+
}
|
|
1427
|
+
),
|
|
1428
|
+
isExitConfirmOpen && /* @__PURE__ */ jsx2(
|
|
1429
|
+
"box",
|
|
1430
|
+
{
|
|
1431
|
+
style: {
|
|
1432
|
+
position: "absolute",
|
|
1433
|
+
top: 0,
|
|
1434
|
+
left: 0,
|
|
1435
|
+
width: "100%",
|
|
1436
|
+
height: "100%"
|
|
1437
|
+
},
|
|
1438
|
+
alignItems: "center",
|
|
1439
|
+
justifyContent: "center",
|
|
1440
|
+
children: /* @__PURE__ */ jsxs2(
|
|
1441
|
+
"box",
|
|
1442
|
+
{
|
|
1443
|
+
width: 58,
|
|
1444
|
+
padding: 1,
|
|
1445
|
+
border: true,
|
|
1446
|
+
borderStyle: "double",
|
|
1447
|
+
borderColor: "#f7768e",
|
|
1448
|
+
flexDirection: "column",
|
|
1449
|
+
style: {
|
|
1450
|
+
backgroundColor: "#1f2335"
|
|
1451
|
+
},
|
|
1452
|
+
children: [
|
|
1453
|
+
/* @__PURE__ */ jsx2("text", { fg: "#f7768e", children: "Exit Docyrus TUI?" }),
|
|
1454
|
+
/* @__PURE__ */ jsx2("text", { children: "Press Enter to exit." }),
|
|
1455
|
+
/* @__PURE__ */ jsx2("text", { children: "Press Esc to stay in this session." })
|
|
1456
|
+
]
|
|
1457
|
+
}
|
|
1458
|
+
)
|
|
1459
|
+
}
|
|
1460
|
+
)
|
|
1461
|
+
] });
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/tui/opentuiMain.tsx
|
|
1465
|
+
import { jsx as jsx3 } from "@opentui/react/jsx-runtime";
|
|
1466
|
+
function readRequiredEnv(name) {
|
|
1467
|
+
const value = process.env[name];
|
|
1468
|
+
if (!value || value.trim().length === 0) {
|
|
1469
|
+
throw new Error(`Missing required launch variable: ${name}`);
|
|
1470
|
+
}
|
|
1471
|
+
return value;
|
|
1472
|
+
}
|
|
1473
|
+
function readLaunchConfig() {
|
|
1474
|
+
return {
|
|
1475
|
+
version: process.env.DOCYRUS_TUI_VERSION || "dev",
|
|
1476
|
+
executionConfig: {
|
|
1477
|
+
executable: readRequiredEnv("DOCYRUS_TUI_EXECUTABLE"),
|
|
1478
|
+
scriptPath: readRequiredEnv("DOCYRUS_TUI_SCRIPT")
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async function startTui() {
|
|
1483
|
+
const launchConfig = readLaunchConfig();
|
|
1484
|
+
const renderer = await createCliRenderer({
|
|
1485
|
+
exitOnCtrlC: false
|
|
1486
|
+
});
|
|
1487
|
+
const root = createRoot(renderer);
|
|
1488
|
+
root.render(
|
|
1489
|
+
/* @__PURE__ */ jsx3(
|
|
1490
|
+
DocyrusOpenTuiApp,
|
|
1491
|
+
{
|
|
1492
|
+
executionConfig: launchConfig.executionConfig,
|
|
1493
|
+
version: launchConfig.version
|
|
1494
|
+
}
|
|
1495
|
+
)
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
void startTui().catch((error) => {
|
|
1499
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1500
|
+
console.error(`Failed to start Docyrus OpenTUI: ${message}`);
|
|
1501
|
+
process.exitCode = 1;
|
|
1502
|
+
});
|
|
1503
|
+
//# sourceMappingURL=tui.mjs.map
|