@arbidocs/tui 0.3.16 → 0.3.17
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/dist/index.js +889 -398
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var commander = require('commander');
|
|
5
4
|
var sdk = require('@arbidocs/sdk');
|
|
5
|
+
var commander = require('commander');
|
|
6
6
|
var piTui = require('@mariozechner/pi-tui');
|
|
7
7
|
var chalk = require('chalk');
|
|
8
8
|
require('fake-indexeddb/auto');
|
|
@@ -13,6 +13,78 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
13
13
|
|
|
14
14
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
15
15
|
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
18
|
+
var __esm = (fn, res) => function __init() {
|
|
19
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/tui-helpers.ts
|
|
27
|
+
var tui_helpers_exports = {};
|
|
28
|
+
__export(tui_helpers_exports, {
|
|
29
|
+
applyWorkspaceSelection: () => applyWorkspaceSelection,
|
|
30
|
+
requireAuth: () => requireAuth,
|
|
31
|
+
runInteractiveFlow: () => runInteractiveFlow,
|
|
32
|
+
showMessage: () => showMessage,
|
|
33
|
+
switchWorkspace: () => switchWorkspace
|
|
34
|
+
});
|
|
35
|
+
function requireAuth(tui, message = "Not authenticated. Use /login first.") {
|
|
36
|
+
if (tui.authContext) return true;
|
|
37
|
+
showMessage(tui, message, "error");
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function showMessage(tui, message, level) {
|
|
41
|
+
tui.chatLog.addSystem(message, level);
|
|
42
|
+
tui.requestRender();
|
|
43
|
+
}
|
|
44
|
+
async function switchWorkspace(tui, workspaceId) {
|
|
45
|
+
if (!tui.authContext) return null;
|
|
46
|
+
try {
|
|
47
|
+
const ws = await sdk.selectWorkspaceById(
|
|
48
|
+
tui.authContext.arbi,
|
|
49
|
+
workspaceId,
|
|
50
|
+
tui.authContext.loginResult.serverSessionKey,
|
|
51
|
+
tui.store.requireCredentials().signingPrivateKeyBase64
|
|
52
|
+
);
|
|
53
|
+
tui.state.workspaceId = ws.external_id;
|
|
54
|
+
tui.state.workspaceName = ws.name;
|
|
55
|
+
tui.state.conversationMessageId = null;
|
|
56
|
+
tui.store.clearChatSession();
|
|
57
|
+
tui.store.updateConfig({ selectedWorkspaceId: ws.external_id });
|
|
58
|
+
await tui.refreshWorkspaceContext();
|
|
59
|
+
return ws;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
showMessage(tui, `Failed to switch workspace: ${sdk.getErrorMessage(err)}`, "error");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function applyWorkspaceSelection(tui, workspaceId, workspaceName) {
|
|
66
|
+
if (!workspaceId) return;
|
|
67
|
+
const wsCtx = await sdk.resolveWorkspace(tui.store, workspaceId);
|
|
68
|
+
tui.setWorkspaceContext(wsCtx);
|
|
69
|
+
tui.state.workspaceName = workspaceName ?? null;
|
|
70
|
+
}
|
|
71
|
+
async function runInteractiveFlow(tui, pauseMessage, action, errorPrefix) {
|
|
72
|
+
showMessage(tui, pauseMessage);
|
|
73
|
+
tui.stopTui();
|
|
74
|
+
try {
|
|
75
|
+
const result = await action();
|
|
76
|
+
tui.restartTui();
|
|
77
|
+
return result;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
tui.restartTui();
|
|
80
|
+
showMessage(tui, `${errorPrefix}: ${sdk.getErrorMessage(err)}`, "error");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
var init_tui_helpers = __esm({
|
|
85
|
+
"src/tui-helpers.ts"() {
|
|
86
|
+
}
|
|
87
|
+
});
|
|
16
88
|
var colors = {
|
|
17
89
|
/** Primary accent — teal/cyan used for headings, prompts, highlights */
|
|
18
90
|
accent: chalk__default.default.cyan,
|
|
@@ -99,7 +171,7 @@ var AssistantMessage = class extends piTui.Container {
|
|
|
99
171
|
label;
|
|
100
172
|
constructor() {
|
|
101
173
|
super();
|
|
102
|
-
this.label = new piTui.Text(colors.assistantLabel("
|
|
174
|
+
this.label = new piTui.Text(colors.assistantLabel("ARBI"), 1, 0);
|
|
103
175
|
this.markdown = new piTui.Markdown("", 1, 0, assistantMarkdownTheme);
|
|
104
176
|
this.addChild(this.label);
|
|
105
177
|
this.addChild(this.markdown);
|
|
@@ -124,6 +196,23 @@ var SystemMessage = class extends piTui.Text {
|
|
|
124
196
|
super(styleFn(message), 1, 0);
|
|
125
197
|
}
|
|
126
198
|
};
|
|
199
|
+
var RightAlignedText = class {
|
|
200
|
+
text;
|
|
201
|
+
styleFn;
|
|
202
|
+
constructor(text, styleFn) {
|
|
203
|
+
this.text = text;
|
|
204
|
+
this.styleFn = styleFn;
|
|
205
|
+
}
|
|
206
|
+
invalidate() {
|
|
207
|
+
}
|
|
208
|
+
render(width) {
|
|
209
|
+
if (!this.text || this.text.trim() === "") return [];
|
|
210
|
+
const styled = this.styleFn(this.text);
|
|
211
|
+
const textWidth = piTui.visibleWidth(styled);
|
|
212
|
+
const padding = Math.max(0, width - textWidth);
|
|
213
|
+
return [" ".repeat(padding) + styled];
|
|
214
|
+
}
|
|
215
|
+
};
|
|
127
216
|
var AgentStep = class extends piTui.Text {
|
|
128
217
|
completed = false;
|
|
129
218
|
constructor(focus) {
|
|
@@ -160,6 +249,11 @@ var ChatLog = class extends piTui.Container {
|
|
|
160
249
|
this.addChild(new SystemMessage(message, level));
|
|
161
250
|
this.addChild(new piTui.Spacer(1));
|
|
162
251
|
}
|
|
252
|
+
/** Add a right-aligned summary line (e.g. stream stats). */
|
|
253
|
+
addSummary(text) {
|
|
254
|
+
this.addChild(new RightAlignedText(text, colors.systemInfo));
|
|
255
|
+
this.addChild(new piTui.Spacer(1));
|
|
256
|
+
}
|
|
163
257
|
/** Add a received DM message (cyan label with sender email). */
|
|
164
258
|
addDm(senderEmail, text) {
|
|
165
259
|
const container = new piTui.Container();
|
|
@@ -195,13 +289,17 @@ var ChatLog = class extends piTui.Container {
|
|
|
195
289
|
this.activeSteps.push(step);
|
|
196
290
|
this.addChild(step);
|
|
197
291
|
}
|
|
198
|
-
/**
|
|
199
|
-
|
|
292
|
+
/** Remove all active agent steps from the chat log (e.g. when streaming starts). */
|
|
293
|
+
clearActiveSteps() {
|
|
200
294
|
for (const step of this.activeSteps) {
|
|
201
|
-
|
|
295
|
+
this.removeChild(step);
|
|
202
296
|
}
|
|
203
|
-
this.activeAssistant = null;
|
|
204
297
|
this.activeSteps = [];
|
|
298
|
+
}
|
|
299
|
+
/** Finalize the current assistant response. */
|
|
300
|
+
finalizeAssistant() {
|
|
301
|
+
this.clearActiveSteps();
|
|
302
|
+
this.activeAssistant = null;
|
|
205
303
|
this.addChild(new piTui.Spacer(1));
|
|
206
304
|
}
|
|
207
305
|
};
|
|
@@ -306,30 +404,32 @@ var ToastContainer = class extends piTui.Container {
|
|
|
306
404
|
}
|
|
307
405
|
};
|
|
308
406
|
|
|
309
|
-
// src/
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
{ name: "exit", description: "Exit TUI" },
|
|
329
|
-
{ name: "quit", description: "Exit TUI" }
|
|
330
|
-
];
|
|
407
|
+
// src/command-registry.ts
|
|
408
|
+
init_tui_helpers();
|
|
409
|
+
var registry = /* @__PURE__ */ new Map();
|
|
410
|
+
function registerCommand(def) {
|
|
411
|
+
registry.set(def.name, def);
|
|
412
|
+
}
|
|
413
|
+
function registerCommands(defs) {
|
|
414
|
+
for (const def of defs) {
|
|
415
|
+
registerCommand(def);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function toSlashCommands() {
|
|
419
|
+
const cmds = [];
|
|
420
|
+
for (const def of registry.values()) {
|
|
421
|
+
if (def.hidden) continue;
|
|
422
|
+
cmds.push({ name: def.name, description: def.description });
|
|
423
|
+
}
|
|
424
|
+
return cmds;
|
|
425
|
+
}
|
|
331
426
|
function formatHelpText() {
|
|
332
|
-
const lines =
|
|
427
|
+
const lines = [];
|
|
428
|
+
for (const def of registry.values()) {
|
|
429
|
+
if (def.hidden) continue;
|
|
430
|
+
const hint = def.argHint ? ` <${def.argHint}>` : "";
|
|
431
|
+
lines.push(` /${def.name}${hint} \u2014 ${def.description}`);
|
|
432
|
+
}
|
|
333
433
|
return [
|
|
334
434
|
"Available commands:",
|
|
335
435
|
"",
|
|
@@ -346,6 +446,133 @@ function formatHelpText() {
|
|
|
346
446
|
" Escape \u2014 Abort streaming"
|
|
347
447
|
].join("\n");
|
|
348
448
|
}
|
|
449
|
+
async function dispatchCommand(tui, input2) {
|
|
450
|
+
const trimmed = input2.trim();
|
|
451
|
+
if (!trimmed.startsWith("/")) return false;
|
|
452
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
453
|
+
const cmdName = parts[0]?.toLowerCase();
|
|
454
|
+
const args = parts.slice(1);
|
|
455
|
+
if (!cmdName) return false;
|
|
456
|
+
const def = registry.get(cmdName);
|
|
457
|
+
if (!def) {
|
|
458
|
+
showMessage(tui, `Unknown command: /${cmdName}. Type /help for available commands.`, "warning");
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
if (def.minArgs && args.length < def.minArgs) {
|
|
462
|
+
const hint = def.argHint ? ` <${def.argHint}>` : "";
|
|
463
|
+
showMessage(tui, `Usage: /${def.name}${hint}`, "warning");
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
let ctx;
|
|
467
|
+
if (def.requires === "none") {
|
|
468
|
+
ctx = { requires: "none", args, rawInput: trimmed, tui };
|
|
469
|
+
} else if (def.requires === "auth") {
|
|
470
|
+
if (!tui.authContext) {
|
|
471
|
+
showMessage(tui, "Not authenticated. Use /login first.", "error");
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
ctx = {
|
|
475
|
+
requires: "auth",
|
|
476
|
+
args,
|
|
477
|
+
rawInput: trimmed,
|
|
478
|
+
tui,
|
|
479
|
+
arbi: tui.authContext.arbi,
|
|
480
|
+
authContext: tui.authContext
|
|
481
|
+
};
|
|
482
|
+
} else {
|
|
483
|
+
if (!tui.authContext) {
|
|
484
|
+
showMessage(tui, "Not authenticated. Use /login first.", "error");
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
if (!tui.wsContext) {
|
|
488
|
+
showMessage(tui, "No workspace selected. Use /workspace <id> first.", "warning");
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
ctx = {
|
|
492
|
+
requires: "workspace",
|
|
493
|
+
args,
|
|
494
|
+
rawInput: trimmed,
|
|
495
|
+
tui,
|
|
496
|
+
arbi: tui.wsContext.arbi,
|
|
497
|
+
authContext: tui.authContext,
|
|
498
|
+
wsContext: tui.wsContext,
|
|
499
|
+
authHeaders: {
|
|
500
|
+
baseUrl: tui.wsContext.config.baseUrl,
|
|
501
|
+
accessToken: tui.wsContext.accessToken,
|
|
502
|
+
workspaceKeyHeader: tui.wsContext.workspaceKeyHeader
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const result = await def.run(ctx);
|
|
508
|
+
if (typeof result === "string") {
|
|
509
|
+
showMessage(tui, result);
|
|
510
|
+
} else if (Array.isArray(result)) {
|
|
511
|
+
showMessage(tui, result.join("\n"));
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
showMessage(tui, `Command /${def.name} failed: ${sdk.getErrorMessage(err)}`, "error");
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/commands/general.ts
|
|
520
|
+
var generalCommands = [
|
|
521
|
+
{
|
|
522
|
+
name: "help",
|
|
523
|
+
description: "Show available commands",
|
|
524
|
+
requires: "none",
|
|
525
|
+
run: () => formatHelpText()
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "status",
|
|
529
|
+
description: "Show auth/workspace/connection status",
|
|
530
|
+
requires: "none",
|
|
531
|
+
run: (ctx) => {
|
|
532
|
+
const { tui } = ctx;
|
|
533
|
+
const { state } = tui;
|
|
534
|
+
return [
|
|
535
|
+
`Authenticated: ${state.isAuthenticated ? colors.success("yes") : colors.error("no")}`,
|
|
536
|
+
`Workspace: ${state.workspaceName ? colors.accent(state.workspaceName) : colors.muted("none")}`,
|
|
537
|
+
`Workspace ID: ${state.workspaceId ?? colors.muted("none")}`,
|
|
538
|
+
`Conversation: ${state.conversationMessageId ? colors.muted(state.conversationMessageId) : colors.muted("new")}`,
|
|
539
|
+
`Status: ${state.activityStatus}`,
|
|
540
|
+
`WebSocket: ${tui.wsConnected ? colors.success("connected") : colors.muted("disconnected")}`
|
|
541
|
+
];
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "new",
|
|
546
|
+
description: "Start fresh conversation (clear threading)",
|
|
547
|
+
requires: "none",
|
|
548
|
+
run: (ctx) => {
|
|
549
|
+
const { tui } = ctx;
|
|
550
|
+
tui.state.conversationMessageId = null;
|
|
551
|
+
tui.lastMetadata = null;
|
|
552
|
+
tui.store.clearChatSession();
|
|
553
|
+
return "Started new conversation.";
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "exit",
|
|
558
|
+
description: "Exit TUI",
|
|
559
|
+
requires: "none",
|
|
560
|
+
run: (ctx) => {
|
|
561
|
+
const { tui } = ctx;
|
|
562
|
+
tui.shutdown();
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "quit",
|
|
567
|
+
description: "Exit TUI",
|
|
568
|
+
requires: "none",
|
|
569
|
+
hidden: true,
|
|
570
|
+
run: (ctx) => {
|
|
571
|
+
const { tui } = ctx;
|
|
572
|
+
tui.shutdown();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
];
|
|
349
576
|
async function promptSelect(message, choices) {
|
|
350
577
|
return prompts.select({ message, choices });
|
|
351
578
|
}
|
|
@@ -483,218 +710,396 @@ ${sdk.getErrorMessage(err)}
|
|
|
483
710
|
}
|
|
484
711
|
}
|
|
485
712
|
}
|
|
486
|
-
function requireAuth(tui, message = "Not authenticated. Use /login first.") {
|
|
487
|
-
if (tui.authContext) return true;
|
|
488
|
-
showMessage(tui, message, "error");
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
function showMessage(tui, message, level) {
|
|
492
|
-
tui.chatLog.addSystem(message, level);
|
|
493
|
-
tui.requestRender();
|
|
494
|
-
}
|
|
495
|
-
async function switchWorkspace(tui, workspaceId) {
|
|
496
|
-
if (!tui.authContext) return null;
|
|
497
|
-
try {
|
|
498
|
-
const ws = await sdk.selectWorkspaceById(
|
|
499
|
-
tui.authContext.arbi,
|
|
500
|
-
workspaceId,
|
|
501
|
-
tui.authContext.loginResult.serverSessionKey,
|
|
502
|
-
tui.store.requireCredentials().signingPrivateKeyBase64
|
|
503
|
-
);
|
|
504
|
-
tui.state.workspaceId = ws.external_id;
|
|
505
|
-
tui.state.workspaceName = ws.name;
|
|
506
|
-
tui.state.conversationMessageId = null;
|
|
507
|
-
tui.store.clearChatSession();
|
|
508
|
-
tui.store.updateConfig({ selectedWorkspaceId: ws.external_id });
|
|
509
|
-
await tui.refreshWorkspaceContext();
|
|
510
|
-
return ws;
|
|
511
|
-
} catch (err) {
|
|
512
|
-
showMessage(tui, `Failed to switch workspace: ${sdk.getErrorMessage(err)}`, "error");
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
async function applyWorkspaceSelection(tui, workspaceId, workspaceName) {
|
|
517
|
-
if (!workspaceId) return;
|
|
518
|
-
const wsCtx = await sdk.resolveWorkspace(tui.store, workspaceId);
|
|
519
|
-
tui.setWorkspaceContext(wsCtx);
|
|
520
|
-
tui.state.workspaceName = workspaceName ?? null;
|
|
521
|
-
}
|
|
522
|
-
async function runInteractiveFlow(tui, pauseMessage, action, errorPrefix) {
|
|
523
|
-
showMessage(tui, pauseMessage);
|
|
524
|
-
tui.stopTui();
|
|
525
|
-
try {
|
|
526
|
-
const result = await action();
|
|
527
|
-
tui.restartTui();
|
|
528
|
-
return result;
|
|
529
|
-
} catch (err) {
|
|
530
|
-
tui.restartTui();
|
|
531
|
-
showMessage(tui, `${errorPrefix}: ${sdk.getErrorMessage(err)}`, "error");
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
713
|
|
|
536
|
-
// src/
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return
|
|
592
|
-
case "logout":
|
|
593
|
-
handleLogout(tui);
|
|
594
|
-
return true;
|
|
595
|
-
case "exit":
|
|
596
|
-
case "quit":
|
|
597
|
-
tui.shutdown();
|
|
598
|
-
return true;
|
|
599
|
-
default:
|
|
600
|
-
showMessage(tui, `Unknown command: /${cmd}. Type /help for available commands.`, "warning");
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
async function handleLogin(tui) {
|
|
605
|
-
const result = await runInteractiveFlow(
|
|
606
|
-
tui,
|
|
607
|
-
"Pausing TUI for login...",
|
|
608
|
-
() => interactiveLogin(tui.store),
|
|
609
|
-
"Login failed"
|
|
610
|
-
);
|
|
611
|
-
if (result) {
|
|
612
|
-
tui.setAuthContext(result.authContext);
|
|
613
|
-
await applyWorkspaceSelection(tui, result.selectedWorkspaceId, result.selectedWorkspaceName);
|
|
614
|
-
showMessage(tui, "Logged in successfully.");
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
async function handleRegister(tui) {
|
|
618
|
-
const result = await runInteractiveFlow(
|
|
619
|
-
tui,
|
|
620
|
-
"Pausing TUI for registration...",
|
|
621
|
-
() => interactiveRegister(tui.store),
|
|
622
|
-
"Registration failed"
|
|
623
|
-
);
|
|
624
|
-
if (result) {
|
|
625
|
-
tui.setAuthContext(result.authContext);
|
|
626
|
-
await applyWorkspaceSelection(tui, result.selectedWorkspaceId, result.selectedWorkspaceName);
|
|
627
|
-
showMessage(tui, "Registered and logged in.");
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
async function handleCreateWorkspace(tui, name) {
|
|
631
|
-
if (!requireAuth(tui)) return;
|
|
632
|
-
if (!name.trim()) {
|
|
633
|
-
showMessage(tui, "Usage: /create <workspace name>", "warning");
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
showMessage(tui, `Creating workspace "${name}"...`);
|
|
638
|
-
const ws = await sdk.workspaces.createWorkspace(tui.authContext.arbi, name.trim());
|
|
639
|
-
const selected = await switchWorkspace(tui, ws.external_id);
|
|
640
|
-
if (selected) {
|
|
641
|
-
showMessage(tui, `Created and switched to workspace: ${colors.accentBold(selected.name)}`);
|
|
714
|
+
// src/commands/auth.ts
|
|
715
|
+
init_tui_helpers();
|
|
716
|
+
var authCommands = [
|
|
717
|
+
{
|
|
718
|
+
name: "login",
|
|
719
|
+
description: "Log in (re-authenticate)",
|
|
720
|
+
requires: "none",
|
|
721
|
+
run: async (ctx) => {
|
|
722
|
+
const { tui } = ctx;
|
|
723
|
+
const result = await runInteractiveFlow(
|
|
724
|
+
tui,
|
|
725
|
+
"Pausing TUI for login...",
|
|
726
|
+
() => interactiveLogin(tui.store),
|
|
727
|
+
"Login failed"
|
|
728
|
+
);
|
|
729
|
+
if (result) {
|
|
730
|
+
tui.setAuthContext(result.authContext);
|
|
731
|
+
await applyWorkspaceSelection(tui, result.selectedWorkspaceId, result.selectedWorkspaceName);
|
|
732
|
+
showMessage(tui, "Logged in successfully.");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
name: "register",
|
|
738
|
+
description: "Register a new account",
|
|
739
|
+
requires: "none",
|
|
740
|
+
run: async (ctx) => {
|
|
741
|
+
const { tui } = ctx;
|
|
742
|
+
const result = await runInteractiveFlow(
|
|
743
|
+
tui,
|
|
744
|
+
"Pausing TUI for registration...",
|
|
745
|
+
() => interactiveRegister(tui.store),
|
|
746
|
+
"Registration failed"
|
|
747
|
+
);
|
|
748
|
+
if (result) {
|
|
749
|
+
tui.setAuthContext(result.authContext);
|
|
750
|
+
await applyWorkspaceSelection(tui, result.selectedWorkspaceId, result.selectedWorkspaceName);
|
|
751
|
+
showMessage(tui, "Registered and logged in.");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
name: "logout",
|
|
757
|
+
description: "Log out and clear credentials",
|
|
758
|
+
requires: "none",
|
|
759
|
+
run: (ctx) => {
|
|
760
|
+
const { tui } = ctx;
|
|
761
|
+
tui.store.deleteCredentials();
|
|
762
|
+
tui.store.updateConfig({ selectedWorkspaceId: void 0 });
|
|
763
|
+
tui.store.clearChatSession();
|
|
764
|
+
tui.authContext = null;
|
|
765
|
+
tui.state.isAuthenticated = false;
|
|
766
|
+
tui.state.workspaceId = null;
|
|
767
|
+
tui.state.workspaceName = null;
|
|
768
|
+
tui.state.conversationMessageId = null;
|
|
769
|
+
return "Logged out. Use /login to authenticate again.";
|
|
642
770
|
}
|
|
643
|
-
} catch (err) {
|
|
644
|
-
showMessage(tui, `Failed to create workspace: ${sdk.getErrorMessage(err)}`, "error");
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
async function handleWorkspaceSwitch(tui, workspaceId) {
|
|
648
|
-
if (!requireAuth(tui, "Not authenticated. Please restart and log in.")) return;
|
|
649
|
-
if (!workspaceId) {
|
|
650
|
-
await handleListWorkspaces(tui);
|
|
651
|
-
showMessage(tui, "Use /workspace <id> to switch.");
|
|
652
|
-
return;
|
|
653
771
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
772
|
+
];
|
|
773
|
+
init_tui_helpers();
|
|
774
|
+
var workspaceCommands = [
|
|
775
|
+
{
|
|
776
|
+
name: "workspaces",
|
|
777
|
+
description: "List all workspaces",
|
|
778
|
+
requires: "auth",
|
|
779
|
+
run: async (ctx) => {
|
|
780
|
+
const { arbi, tui } = ctx;
|
|
781
|
+
const wsList = await sdk.workspaces.listWorkspaces(arbi);
|
|
782
|
+
const lines = wsList.map((ws) => {
|
|
783
|
+
const current = ws.external_id === tui.state.workspaceId ? colors.accent(" (current)") : "";
|
|
784
|
+
return ` ${ws.external_id} ${ws.name}${current}`;
|
|
785
|
+
});
|
|
786
|
+
return ["Workspaces:", "", ...lines];
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "workspace",
|
|
791
|
+
description: "Switch workspace",
|
|
792
|
+
argHint: "id",
|
|
793
|
+
requires: "auth",
|
|
794
|
+
run: async (ctx) => {
|
|
795
|
+
const { args, arbi, tui } = ctx;
|
|
796
|
+
if (!args[0]) {
|
|
797
|
+
const wsList = await sdk.workspaces.listWorkspaces(arbi);
|
|
798
|
+
const lines = wsList.map((ws2) => {
|
|
799
|
+
const current = ws2.external_id === tui.state.workspaceId ? colors.accent(" (current)") : "";
|
|
800
|
+
return ` ${ws2.external_id} ${ws2.name}${current}`;
|
|
801
|
+
});
|
|
802
|
+
showMessage(tui, ["Workspaces:", "", ...lines].join("\n"));
|
|
803
|
+
return "Use /workspace <id> to switch.";
|
|
804
|
+
}
|
|
805
|
+
showMessage(tui, `Switching to workspace ${args[0]}...`);
|
|
806
|
+
const ws = await switchWorkspace(tui, args[0]);
|
|
807
|
+
if (ws) {
|
|
808
|
+
return `Switched to workspace: ${colors.accentBold(ws.name)}`;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
name: "create",
|
|
814
|
+
description: "Create a new workspace",
|
|
815
|
+
argHint: "name",
|
|
816
|
+
minArgs: 1,
|
|
817
|
+
requires: "auth",
|
|
818
|
+
run: async (ctx) => {
|
|
819
|
+
const { args, arbi, tui } = ctx;
|
|
820
|
+
const name = args.join(" ").trim();
|
|
821
|
+
showMessage(tui, `Creating workspace "${name}"...`);
|
|
822
|
+
const ws = await sdk.workspaces.createWorkspace(arbi, name);
|
|
823
|
+
const selected = await switchWorkspace(tui, ws.external_id);
|
|
824
|
+
if (selected) {
|
|
825
|
+
return `Created and switched to workspace: ${colors.accentBold(selected.name)}`;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: "ws-delete",
|
|
831
|
+
description: "Delete a workspace",
|
|
832
|
+
argHint: "id",
|
|
833
|
+
minArgs: 1,
|
|
834
|
+
requires: "auth",
|
|
835
|
+
run: async (ctx) => {
|
|
836
|
+
const { args, arbi } = ctx;
|
|
837
|
+
await sdk.workspaces.deleteWorkspaces(arbi, [args[0]]);
|
|
838
|
+
return `${colors.success("Deleted")} workspace ${args[0]}`;
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
name: "ws-users",
|
|
843
|
+
description: "List users in current workspace",
|
|
844
|
+
requires: "workspace",
|
|
845
|
+
run: async (ctx) => {
|
|
846
|
+
const { arbi } = ctx;
|
|
847
|
+
const users = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
848
|
+
if (users.length === 0) return "No users in this workspace.";
|
|
849
|
+
const lines = users.map((u) => {
|
|
850
|
+
const name = sdk.formatUserName(u.user);
|
|
851
|
+
const nameStr = name ? colors.muted(` (${name})`) : "";
|
|
852
|
+
const role = u.role ? colors.muted(` [${u.role}]`) : "";
|
|
853
|
+
return ` ${u.user.email}${nameStr}${role}`;
|
|
854
|
+
});
|
|
855
|
+
return [`Workspace users (${users.length}):`, "", ...lines];
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: "ws-add-user",
|
|
860
|
+
description: "Add a user to current workspace",
|
|
861
|
+
argHint: "email",
|
|
862
|
+
minArgs: 1,
|
|
863
|
+
requires: "workspace",
|
|
864
|
+
run: async (ctx) => {
|
|
865
|
+
const { args, arbi } = ctx;
|
|
866
|
+
const email = args[0].trim();
|
|
867
|
+
await sdk.workspaces.addWorkspaceUsers(arbi, [email]);
|
|
868
|
+
return `${colors.success("Added")} ${email} to workspace.`;
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
name: "ws-remove-user",
|
|
873
|
+
description: "Remove a user from current workspace",
|
|
874
|
+
argHint: "email",
|
|
875
|
+
minArgs: 1,
|
|
876
|
+
requires: "workspace",
|
|
877
|
+
run: async (ctx) => {
|
|
878
|
+
const { args, arbi } = ctx;
|
|
879
|
+
const email = args[0].trim();
|
|
880
|
+
const users = await sdk.workspaces.listWorkspaceUsers(arbi);
|
|
881
|
+
const user = users.find((u) => u.user.email === email);
|
|
882
|
+
if (!user) return `User ${email} not found in this workspace.`;
|
|
883
|
+
const userId = user.user.external_id;
|
|
884
|
+
if (!userId) return `Could not resolve user ID for ${email}.`;
|
|
885
|
+
await sdk.workspaces.removeWorkspaceUsers(arbi, [userId]);
|
|
886
|
+
return `${colors.success("Removed")} ${email} from workspace.`;
|
|
887
|
+
}
|
|
658
888
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
889
|
+
];
|
|
890
|
+
init_tui_helpers();
|
|
891
|
+
var documentCommands = [
|
|
892
|
+
{
|
|
893
|
+
name: "docs",
|
|
894
|
+
description: "List documents in current workspace",
|
|
895
|
+
requires: "workspace",
|
|
896
|
+
run: async (ctx) => {
|
|
897
|
+
const { arbi } = ctx;
|
|
898
|
+
const docs = await sdk.documents.listDocuments(arbi);
|
|
899
|
+
if (docs.length === 0) return "No documents in this workspace.";
|
|
900
|
+
const lines = docs.map((d) => ` ${d.external_id} ${d.file_name ?? "(unnamed)"}`);
|
|
901
|
+
return [`Documents (${docs.length}):`, "", ...lines];
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: "doc",
|
|
906
|
+
description: "Show document details",
|
|
907
|
+
argHint: "id",
|
|
908
|
+
minArgs: 1,
|
|
909
|
+
requires: "workspace",
|
|
910
|
+
run: async (ctx) => {
|
|
911
|
+
const { args, arbi } = ctx;
|
|
912
|
+
const docs = await sdk.documents.getDocuments(arbi, [args[0]]);
|
|
913
|
+
if (docs.length === 0) return `Document ${args[0]} not found.`;
|
|
914
|
+
const doc = docs[0];
|
|
915
|
+
const lines = [
|
|
916
|
+
`Document: ${colors.accent(doc.file_name ?? "(unnamed)")}`,
|
|
917
|
+
"",
|
|
918
|
+
` ID: ${doc.external_id}`,
|
|
919
|
+
` Status: ${doc.status ?? colors.muted("unknown")}`,
|
|
920
|
+
` Size: ${doc.file_size != null ? `${doc.file_size} bytes` : colors.muted("unknown")}`,
|
|
921
|
+
` Created: ${doc.created_at ?? colors.muted("unknown")}`
|
|
922
|
+
];
|
|
923
|
+
return lines;
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
name: "upload",
|
|
928
|
+
description: "Upload a file",
|
|
929
|
+
argHint: "path",
|
|
930
|
+
minArgs: 1,
|
|
931
|
+
requires: "workspace",
|
|
932
|
+
run: async (ctx) => {
|
|
933
|
+
const { args, tui, wsContext } = ctx;
|
|
934
|
+
const filePath = args.join(" ").trim();
|
|
935
|
+
showMessage(tui, `Uploading ${filePath}...`);
|
|
936
|
+
const result = await sdk.documentsNode.uploadLocalFile(
|
|
937
|
+
{
|
|
938
|
+
baseUrl: wsContext.config.baseUrl,
|
|
939
|
+
accessToken: wsContext.accessToken,
|
|
940
|
+
workspaceKeyHeader: wsContext.workspaceKeyHeader
|
|
941
|
+
},
|
|
942
|
+
wsContext.workspaceId,
|
|
943
|
+
filePath
|
|
944
|
+
);
|
|
945
|
+
const ids = result.doc_ext_ids.join(", ");
|
|
946
|
+
showMessage(tui, `${colors.success("Uploaded")} ${result.fileName} \u2014 doc ID(s): ${ids}`);
|
|
947
|
+
if (result.duplicates && result.duplicates.length > 0) {
|
|
948
|
+
showMessage(tui, `Duplicates detected: ${result.duplicates.join(", ")}`, "warning");
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: "upload-url",
|
|
954
|
+
description: "Upload a document from URL",
|
|
955
|
+
argHint: "url",
|
|
956
|
+
minArgs: 1,
|
|
957
|
+
requires: "workspace",
|
|
958
|
+
run: async (ctx) => {
|
|
959
|
+
const { args, arbi, wsContext, tui } = ctx;
|
|
960
|
+
const url = args[0].trim();
|
|
961
|
+
showMessage(tui, `Uploading from ${url}...`);
|
|
962
|
+
const result = await sdk.documents.uploadUrl(arbi, [url], wsContext.workspaceId);
|
|
963
|
+
const ids = result.doc_ext_ids.join(", ");
|
|
964
|
+
return `${colors.success("Uploaded")} from URL \u2014 doc ID(s): ${ids}`;
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: "delete",
|
|
969
|
+
description: "Delete a document",
|
|
970
|
+
argHint: "doc-id",
|
|
971
|
+
minArgs: 1,
|
|
972
|
+
requires: "workspace",
|
|
973
|
+
run: async (ctx) => {
|
|
974
|
+
const { args, arbi, tui } = ctx;
|
|
975
|
+
const docId = args[0].trim();
|
|
976
|
+
showMessage(tui, `Deleting document ${docId}...`);
|
|
977
|
+
await sdk.documents.deleteDocuments(arbi, [docId]);
|
|
978
|
+
return `${colors.success("Deleted")} document ${docId}`;
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
name: "parsed",
|
|
983
|
+
description: "Show parsed content of a document",
|
|
984
|
+
argHint: "doc-id",
|
|
985
|
+
minArgs: 1,
|
|
986
|
+
requires: "workspace",
|
|
987
|
+
run: async (ctx) => {
|
|
988
|
+
const { args, authHeaders } = ctx;
|
|
989
|
+
const docId = args[0].trim();
|
|
990
|
+
const data = await sdk.documents.getParsedContent(authHeaders, docId, "content");
|
|
991
|
+
const content = data.content;
|
|
992
|
+
if (!content) return `No parsed content available for document ${docId}.`;
|
|
993
|
+
const maxLen = 3e3;
|
|
994
|
+
const truncated = content.length > maxLen ? content.slice(0, maxLen) + "\n... (truncated)" : content;
|
|
995
|
+
return [`Parsed content for ${docId}:`, "", truncated];
|
|
996
|
+
}
|
|
671
997
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
998
|
+
];
|
|
999
|
+
var conversationCommands = [
|
|
1000
|
+
{
|
|
1001
|
+
name: "conversations",
|
|
1002
|
+
description: "List conversations",
|
|
1003
|
+
requires: "workspace",
|
|
1004
|
+
run: async (ctx) => {
|
|
1005
|
+
const { arbi } = ctx;
|
|
1006
|
+
const convs = await sdk.conversations.listConversations(arbi);
|
|
1007
|
+
if (convs.length === 0) return "No conversations in this workspace.";
|
|
1008
|
+
const lines = convs.map((c) => ` ${c.external_id} ${c.title ?? "(untitled)"}`);
|
|
1009
|
+
return [`Conversations (${convs.length}):`, "", ...lines];
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
name: "conv-delete",
|
|
1014
|
+
description: "Delete a conversation",
|
|
1015
|
+
argHint: "id",
|
|
1016
|
+
minArgs: 1,
|
|
1017
|
+
requires: "workspace",
|
|
1018
|
+
run: async (ctx) => {
|
|
1019
|
+
const { args, arbi } = ctx;
|
|
1020
|
+
await sdk.conversations.deleteConversation(arbi, args[0]);
|
|
1021
|
+
return `${colors.success("Deleted")} conversation ${args[0]}`;
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "conv-title",
|
|
1026
|
+
description: "Rename a conversation",
|
|
1027
|
+
argHint: "id title",
|
|
1028
|
+
minArgs: 2,
|
|
1029
|
+
requires: "workspace",
|
|
1030
|
+
run: async (ctx) => {
|
|
1031
|
+
const { args, arbi } = ctx;
|
|
1032
|
+
const [id, ...titleParts] = args;
|
|
1033
|
+
const title = titleParts.join(" ");
|
|
1034
|
+
await sdk.conversations.updateConversationTitle(arbi, id, title);
|
|
1035
|
+
return `Renamed conversation ${id} to: ${colors.accent(title)}`;
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
name: "conv-share",
|
|
1040
|
+
description: "Share a conversation",
|
|
1041
|
+
argHint: "id",
|
|
1042
|
+
minArgs: 1,
|
|
1043
|
+
requires: "workspace",
|
|
1044
|
+
run: async (ctx) => {
|
|
1045
|
+
const { args, arbi } = ctx;
|
|
1046
|
+
const result = await sdk.conversations.shareConversation(arbi, args[0]);
|
|
1047
|
+
const shareId = result.share_ext_id ?? result.external_id;
|
|
1048
|
+
return `${colors.success("Shared")} conversation ${args[0]} \u2014 share ID: ${shareId}`;
|
|
1049
|
+
}
|
|
677
1050
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1051
|
+
];
|
|
1052
|
+
var tagCommands = [
|
|
1053
|
+
{
|
|
1054
|
+
name: "tags",
|
|
1055
|
+
description: "List tags in workspace",
|
|
1056
|
+
requires: "workspace",
|
|
1057
|
+
run: async (ctx) => {
|
|
1058
|
+
const { arbi } = ctx;
|
|
1059
|
+
const data = await sdk.tags.listTags(arbi);
|
|
1060
|
+
if (data.length === 0) return "No tags in this workspace.";
|
|
1061
|
+
const lines = data.map((t) => ` ${t.external_id} ${t.name}`);
|
|
1062
|
+
return [`Tags (${data.length}):`, "", ...lines];
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "tag-create",
|
|
1067
|
+
description: "Create a tag",
|
|
1068
|
+
argHint: "name",
|
|
1069
|
+
minArgs: 1,
|
|
1070
|
+
requires: "workspace",
|
|
1071
|
+
run: async (ctx) => {
|
|
1072
|
+
const { args, arbi } = ctx;
|
|
1073
|
+
const name = args.join(" ").trim();
|
|
1074
|
+
const tag = await sdk.tags.createTag(arbi, { name });
|
|
1075
|
+
return `${colors.success("Created")} tag "${tag.name}" \u2014 ID: ${tag.external_id}`;
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "tag-delete",
|
|
1080
|
+
description: "Delete a tag",
|
|
1081
|
+
argHint: "id",
|
|
1082
|
+
minArgs: 1,
|
|
1083
|
+
requires: "workspace",
|
|
1084
|
+
run: async (ctx) => {
|
|
1085
|
+
const { args, arbi } = ctx;
|
|
1086
|
+
await sdk.tags.deleteTag(arbi, args[0]);
|
|
1087
|
+
return `${colors.success("Deleted")} tag ${args[0]}`;
|
|
685
1088
|
}
|
|
686
|
-
} catch (err) {
|
|
687
|
-
showMessage(tui, `Failed to list documents: ${sdk.getErrorMessage(err)}`, "error");
|
|
688
1089
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1090
|
+
];
|
|
1091
|
+
var miscCommands = [
|
|
1092
|
+
{
|
|
1093
|
+
name: "contacts",
|
|
1094
|
+
description: "List contacts (type @name to DM)",
|
|
1095
|
+
requires: "auth",
|
|
1096
|
+
run: async (ctx) => {
|
|
1097
|
+
const { arbi } = ctx;
|
|
1098
|
+
const contactList = await sdk.contacts.listContacts(arbi);
|
|
1099
|
+
const { registered, pending } = sdk.contacts.groupContactsByStatus(contactList);
|
|
1100
|
+
if (registered.length === 0 && pending.length === 0) {
|
|
1101
|
+
return "No contacts. Use the web app to add contacts.";
|
|
1102
|
+
}
|
|
698
1103
|
const lines = [];
|
|
699
1104
|
if (registered.length > 0) {
|
|
700
1105
|
lines.push(`Contacts (${registered.length}):`, "");
|
|
@@ -712,184 +1117,217 @@ async function handleListContacts(tui) {
|
|
|
712
1117
|
}
|
|
713
1118
|
}
|
|
714
1119
|
lines.push("", colors.muted("Type @name to start a DM with a registered contact."));
|
|
715
|
-
|
|
1120
|
+
return lines;
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
name: "invite",
|
|
1125
|
+
description: "Invite a contact",
|
|
1126
|
+
argHint: "email",
|
|
1127
|
+
minArgs: 1,
|
|
1128
|
+
requires: "auth",
|
|
1129
|
+
run: async (ctx) => {
|
|
1130
|
+
const { args, arbi, tui } = ctx;
|
|
1131
|
+
const email = args[0].trim();
|
|
1132
|
+
const { showMessage: showMessage2 } = await Promise.resolve().then(() => (init_tui_helpers(), tui_helpers_exports));
|
|
1133
|
+
showMessage2(tui, `Inviting ${email}...`);
|
|
1134
|
+
const result = await sdk.contacts.addContacts(arbi, [email]);
|
|
1135
|
+
const contact = result[0];
|
|
1136
|
+
if (contact?.status === "registered") {
|
|
1137
|
+
return `${colors.success("Added")} ${email} \u2014 already registered. You can DM them with @${email.split("@")[0]}`;
|
|
1138
|
+
}
|
|
1139
|
+
return `${colors.success("Invited")} ${email} \u2014 invitation sent. They'll appear in /contacts once they register.`;
|
|
1140
|
+
}
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
name: "models",
|
|
1144
|
+
description: "List available AI models",
|
|
1145
|
+
requires: "auth",
|
|
1146
|
+
run: async (ctx) => {
|
|
1147
|
+
const { arbi } = ctx;
|
|
1148
|
+
const data = await sdk.health.getHealthModels(arbi);
|
|
1149
|
+
const models = Array.isArray(data) ? data : data.data ?? [];
|
|
1150
|
+
if (models.length === 0) return "No models available.";
|
|
1151
|
+
const lines = [`Models (${models.length}):`, ""];
|
|
1152
|
+
for (const m of models) {
|
|
1153
|
+
const model = m;
|
|
1154
|
+
const name = model.model_name ?? model.name ?? "unknown";
|
|
1155
|
+
const provider = model.provider ?? "";
|
|
1156
|
+
const apiType = model.api_type ?? "";
|
|
1157
|
+
const parts = [colors.accent(name)];
|
|
1158
|
+
if (provider) parts.push(`provider: ${provider}`);
|
|
1159
|
+
if (apiType) parts.push(`api: ${apiType}`);
|
|
1160
|
+
lines.push(` ${parts.join(" ")}`);
|
|
1161
|
+
}
|
|
1162
|
+
return lines;
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
name: "health",
|
|
1167
|
+
description: "Show system health status",
|
|
1168
|
+
requires: "auth",
|
|
1169
|
+
run: async (ctx) => {
|
|
1170
|
+
const { arbi } = ctx;
|
|
1171
|
+
const data = await sdk.health.getHealth(arbi);
|
|
1172
|
+
const lines = ["System Health:", ""];
|
|
1173
|
+
const status = data.status;
|
|
1174
|
+
if (status) {
|
|
1175
|
+
const statusColor = status === "healthy" ? colors.success(status) : colors.warning(status);
|
|
1176
|
+
lines.push(` Status: ${statusColor}`);
|
|
1177
|
+
}
|
|
1178
|
+
const services = data.services;
|
|
1179
|
+
if (services) {
|
|
1180
|
+
lines.push("", " Services:");
|
|
1181
|
+
for (const [name, info] of Object.entries(services)) {
|
|
1182
|
+
const svc = info;
|
|
1183
|
+
const svcStatus = svc.status;
|
|
1184
|
+
const statusStr = svcStatus ? svcStatus === "healthy" ? colors.success(svcStatus) : colors.error(svcStatus) : colors.muted("unknown");
|
|
1185
|
+
lines.push(` ${name}: ${statusStr}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return lines;
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
name: "settings",
|
|
1193
|
+
description: "Show user settings",
|
|
1194
|
+
requires: "auth",
|
|
1195
|
+
run: async (ctx) => {
|
|
1196
|
+
const { arbi } = ctx;
|
|
1197
|
+
const data = await sdk.settings.getSettings(arbi);
|
|
1198
|
+
const entries = Object.entries(data);
|
|
1199
|
+
if (entries.length === 0) return "No settings configured.";
|
|
1200
|
+
const lines = ["User Settings:", ""];
|
|
1201
|
+
for (const [key, value] of entries) {
|
|
1202
|
+
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
1203
|
+
lines.push(` ${colors.accent(key)}: ${display}`);
|
|
1204
|
+
}
|
|
1205
|
+
return lines;
|
|
716
1206
|
}
|
|
717
|
-
} catch (err) {
|
|
718
|
-
showMessage(tui, `Failed to list contacts: ${sdk.getErrorMessage(err)}`, "error");
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
async function handleInviteContact(tui, email) {
|
|
722
|
-
if (!requireAuth(tui)) return;
|
|
723
|
-
if (!email?.trim()) {
|
|
724
|
-
showMessage(tui, "Usage: /invite user@example.com", "warning");
|
|
725
|
-
return;
|
|
726
1207
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1208
|
+
];
|
|
1209
|
+
var MAX_PASSAGE_CHARS = 2e3;
|
|
1210
|
+
var CitationPanel = class extends piTui.Box {
|
|
1211
|
+
constructor(citation, passageIndex = 0) {
|
|
1212
|
+
super(1, 1);
|
|
1213
|
+
const chunk = citation.chunks[passageIndex];
|
|
1214
|
+
const docTitle = chunk?.metadata?.doc_title ?? "Unknown document";
|
|
1215
|
+
const page = chunk?.metadata?.page_number;
|
|
1216
|
+
const headerParts = [
|
|
1217
|
+
`${colors.accentBold(`[Citation ${citation.citationNum}]`)} ${colors.textBold(docTitle)}`
|
|
1218
|
+
];
|
|
1219
|
+
if (page != null) {
|
|
1220
|
+
headerParts.push(colors.muted(` Page ${page}`));
|
|
1221
|
+
}
|
|
1222
|
+
if (citation.chunks.length > 1) {
|
|
1223
|
+
headerParts.push(colors.muted(` Passage ${passageIndex + 1}/${citation.chunks.length}`));
|
|
1224
|
+
}
|
|
1225
|
+
this.addChild(new piTui.Text(headerParts.join(""), 0, 0));
|
|
1226
|
+
this.addChild(new piTui.Text("", 0, 0));
|
|
1227
|
+
this.addChild(new piTui.Text(colors.textBold("Statement:"), 0, 0));
|
|
1228
|
+
this.addChild(new piTui.Text(` ${citation.citationData.statement}`, 0, 0));
|
|
1229
|
+
this.addChild(new piTui.Text("", 0, 0));
|
|
1230
|
+
this.addChild(new piTui.Text(colors.textBold("Passage:"), 0, 0));
|
|
1231
|
+
if (chunk) {
|
|
1232
|
+
let content = chunk.content;
|
|
1233
|
+
if (content.length > MAX_PASSAGE_CHARS) {
|
|
1234
|
+
content = content.slice(0, MAX_PASSAGE_CHARS) + "\n...(truncated)";
|
|
1235
|
+
}
|
|
1236
|
+
this.addChild(new piTui.Markdown(content, 0, 0, markdownTheme));
|
|
736
1237
|
} else {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1238
|
+
this.addChild(new piTui.Text(colors.muted(" (no passage data available)"), 0, 0));
|
|
1239
|
+
}
|
|
1240
|
+
this.addChild(new piTui.Text("", 0, 0));
|
|
1241
|
+
const hints = [];
|
|
1242
|
+
if (citation.chunks.length > 1) {
|
|
1243
|
+
hints.push(
|
|
1244
|
+
`/cite ${citation.citationNum} <1-${citation.chunks.length}> to view other passages`
|
|
740
1245
|
);
|
|
741
1246
|
}
|
|
742
|
-
|
|
743
|
-
|
|
1247
|
+
hints.push("Press Escape to close");
|
|
1248
|
+
this.addChild(new piTui.Text(colors.muted(hints.join(" \xB7 ")), 0, 0));
|
|
744
1249
|
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
// src/commands/citation.ts
|
|
1253
|
+
function handleCite(ctx) {
|
|
1254
|
+
const { tui, args } = ctx;
|
|
1255
|
+
if (!tui.lastMetadata) {
|
|
1256
|
+
return "No citation data available. Ask a question first.";
|
|
1257
|
+
}
|
|
1258
|
+
const count = sdk.countCitations(tui.lastMetadata);
|
|
1259
|
+
if (count === 0) {
|
|
1260
|
+
return "The last response contained no citations.";
|
|
1261
|
+
}
|
|
1262
|
+
const resolved = sdk.resolveCitations(tui.lastMetadata);
|
|
1263
|
+
if (args.length >= 1) {
|
|
1264
|
+
const citationNum = args[0];
|
|
1265
|
+
const citation = resolved.find((r) => r.citationNum === citationNum);
|
|
1266
|
+
if (!citation) {
|
|
1267
|
+
return `Citation [${citationNum}] not found. Use /cite to list all citations.`;
|
|
758
1268
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
showMessage(tui, "Started new conversation.");
|
|
767
|
-
}
|
|
768
|
-
function handleStatus(tui) {
|
|
769
|
-
const { state } = tui;
|
|
770
|
-
const lines = [
|
|
771
|
-
`Authenticated: ${state.isAuthenticated ? colors.success("yes") : colors.error("no")}`,
|
|
772
|
-
`Workspace: ${state.workspaceName ? colors.accent(state.workspaceName) : colors.muted("none")}`,
|
|
773
|
-
`Workspace ID: ${state.workspaceId ?? colors.muted("none")}`,
|
|
774
|
-
`Conversation: ${state.conversationMessageId ? colors.muted(state.conversationMessageId) : colors.muted("new")}`,
|
|
775
|
-
`Status: ${state.activityStatus}`,
|
|
776
|
-
`WebSocket: ${tui.wsConnected ? colors.success("connected") : colors.muted("disconnected")}`
|
|
777
|
-
];
|
|
778
|
-
showMessage(tui, lines.join("\n"));
|
|
779
|
-
}
|
|
780
|
-
async function handleUpload(tui, filePath) {
|
|
781
|
-
const wsCtx = tui.wsContext;
|
|
782
|
-
if (!wsCtx) {
|
|
783
|
-
showMessage(tui, "No workspace selected. Use /workspace <id> first.", "warning");
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
if (!filePath.trim()) {
|
|
787
|
-
showMessage(tui, "Usage: /upload <file-path>", "warning");
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
try {
|
|
791
|
-
showMessage(tui, `Uploading ${filePath.trim()}...`);
|
|
792
|
-
const result = await sdk.documentsNode.uploadLocalFile(
|
|
793
|
-
{
|
|
794
|
-
baseUrl: wsCtx.config.baseUrl,
|
|
795
|
-
accessToken: wsCtx.accessToken,
|
|
796
|
-
workspaceKeyHeader: wsCtx.workspaceKeyHeader
|
|
797
|
-
},
|
|
798
|
-
wsCtx.workspaceId,
|
|
799
|
-
filePath.trim()
|
|
800
|
-
);
|
|
801
|
-
const ids = result.doc_ext_ids.join(", ");
|
|
802
|
-
showMessage(tui, `${colors.success("Uploaded")} ${result.fileName} \u2014 doc ID(s): ${ids}`);
|
|
803
|
-
if (result.duplicates && result.duplicates.length > 0) {
|
|
804
|
-
showMessage(tui, `Duplicates detected: ${result.duplicates.join(", ")}`, "warning");
|
|
1269
|
+
let passageIndex = 0;
|
|
1270
|
+
if (args.length >= 2) {
|
|
1271
|
+
const p = parseInt(args[1], 10);
|
|
1272
|
+
if (isNaN(p) || p < 1 || p > citation.chunks.length) {
|
|
1273
|
+
return `Passage must be between 1 and ${citation.chunks.length}.`;
|
|
1274
|
+
}
|
|
1275
|
+
passageIndex = p - 1;
|
|
805
1276
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
async function handleDelete(tui, docId) {
|
|
811
|
-
if (!tui.wsContext) {
|
|
812
|
-
showMessage(tui, "No workspace selected. Use /workspace <id> first.", "warning");
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
if (!docId?.trim()) {
|
|
816
|
-
showMessage(tui, "Usage: /delete <doc-id> (use /docs to list document IDs)", "warning");
|
|
1277
|
+
const panel = new CitationPanel(citation, passageIndex);
|
|
1278
|
+
tui.showCitationOverlay(panel);
|
|
817
1279
|
return;
|
|
818
1280
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1281
|
+
const summaries = sdk.summarizeCitations(resolved);
|
|
1282
|
+
const lines = [`Citations (${summaries.length}):
|
|
1283
|
+
`];
|
|
1284
|
+
for (const s of summaries) {
|
|
1285
|
+
const page = s.pageNumber != null ? `, p${s.pageNumber}` : "";
|
|
1286
|
+
const chunks = s.chunkCount > 1 ? ` (${s.chunkCount} passages)` : "";
|
|
1287
|
+
const truncated = s.statement.length > 100 ? s.statement.slice(0, 100).replace(/\n/g, " ").trim() + "..." : s.statement.replace(/\n/g, " ").trim();
|
|
1288
|
+
lines.push(
|
|
1289
|
+
` ${colors.accent(`[${s.citationNum}]`)} ${colors.textBold(s.docTitle)}${page}${chunks}`
|
|
1290
|
+
);
|
|
1291
|
+
lines.push(` ${colors.muted(truncated)}`);
|
|
825
1292
|
}
|
|
1293
|
+
lines.push(colors.muted("\nUse /cite <N> to view full passage."));
|
|
1294
|
+
return lines;
|
|
826
1295
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const data = await sdk.health.getHealth(tui.authContext.arbi);
|
|
842
|
-
const lines = ["System Health:", ""];
|
|
843
|
-
const status = data.status;
|
|
844
|
-
if (status) {
|
|
845
|
-
const statusColor = status === "healthy" ? colors.success(status) : colors.warning(status);
|
|
846
|
-
lines.push(` Status: ${statusColor}`);
|
|
847
|
-
}
|
|
848
|
-
const services = data.services;
|
|
849
|
-
if (services) {
|
|
850
|
-
lines.push("", " Services:");
|
|
851
|
-
for (const [name, info] of Object.entries(services)) {
|
|
852
|
-
const svc = info;
|
|
853
|
-
const svcStatus = svc.status;
|
|
854
|
-
const statusStr = svcStatus ? svcStatus === "healthy" ? colors.success(svcStatus) : colors.error(svcStatus) : colors.muted("unknown");
|
|
855
|
-
lines.push(` ${name}: ${statusStr}`);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
showMessage(tui, lines.join("\n"));
|
|
859
|
-
} catch (err) {
|
|
860
|
-
showMessage(tui, `Failed to fetch health status: ${sdk.getErrorMessage(err)}`, "error");
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
async function handleModels(tui) {
|
|
864
|
-
if (!requireAuth(tui)) return;
|
|
865
|
-
try {
|
|
866
|
-
const data = await sdk.health.getHealthModels(tui.authContext.arbi);
|
|
867
|
-
const models = Array.isArray(data) ? data : data.data ?? [];
|
|
868
|
-
if (models.length === 0) {
|
|
869
|
-
showMessage(tui, "No models available.");
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
const lines = [`Models (${models.length}):`, ""];
|
|
873
|
-
for (const m of models) {
|
|
874
|
-
const model = m;
|
|
875
|
-
const name = model.model_name ?? model.name ?? "unknown";
|
|
876
|
-
const provider = model.provider ?? "";
|
|
877
|
-
const apiType = model.api_type ?? "";
|
|
878
|
-
const parts = [colors.accent(name)];
|
|
879
|
-
if (provider) parts.push(`provider: ${provider}`);
|
|
880
|
-
if (apiType) parts.push(`api: ${apiType}`);
|
|
881
|
-
lines.push(` ${parts.join(" ")}`);
|
|
882
|
-
}
|
|
883
|
-
showMessage(tui, lines.join("\n"));
|
|
884
|
-
} catch (err) {
|
|
885
|
-
showMessage(tui, `Failed to fetch models: ${sdk.getErrorMessage(err)}`, "error");
|
|
1296
|
+
var citationCommands = [
|
|
1297
|
+
{
|
|
1298
|
+
name: "cite",
|
|
1299
|
+
description: "Browse citations from last response",
|
|
1300
|
+
requires: "none",
|
|
1301
|
+
argHint: "[N] [passage]",
|
|
1302
|
+
run: (ctx) => handleCite(ctx)
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
name: "refs",
|
|
1306
|
+
description: "Browse citations from last response",
|
|
1307
|
+
requires: "none",
|
|
1308
|
+
hidden: true,
|
|
1309
|
+
run: (ctx) => handleCite(ctx)
|
|
886
1310
|
}
|
|
1311
|
+
];
|
|
1312
|
+
|
|
1313
|
+
// src/commands/index.ts
|
|
1314
|
+
function registerAllCommands() {
|
|
1315
|
+
registerCommands(generalCommands);
|
|
1316
|
+
registerCommands(authCommands);
|
|
1317
|
+
registerCommands(workspaceCommands);
|
|
1318
|
+
registerCommands(documentCommands);
|
|
1319
|
+
registerCommands(conversationCommands);
|
|
1320
|
+
registerCommands(tagCommands);
|
|
1321
|
+
registerCommands(miscCommands);
|
|
1322
|
+
registerCommands(citationCommands);
|
|
887
1323
|
}
|
|
888
1324
|
async function streamResponse(tui, response) {
|
|
889
1325
|
tui.chatLog.startAssistant();
|
|
890
1326
|
tui.state.activityStatus = "streaming";
|
|
891
1327
|
tui.requestRender();
|
|
892
1328
|
let accumulated = "";
|
|
1329
|
+
let elapsedTime = null;
|
|
1330
|
+
let firstToken = true;
|
|
893
1331
|
const callbacks = {
|
|
894
1332
|
onStreamStart: (data) => {
|
|
895
1333
|
if (data.assistant_message_ext_id) {
|
|
@@ -897,6 +1335,10 @@ async function streamResponse(tui, response) {
|
|
|
897
1335
|
}
|
|
898
1336
|
},
|
|
899
1337
|
onToken: (content) => {
|
|
1338
|
+
if (firstToken) {
|
|
1339
|
+
firstToken = false;
|
|
1340
|
+
tui.chatLog.clearActiveSteps();
|
|
1341
|
+
}
|
|
900
1342
|
accumulated += content;
|
|
901
1343
|
tui.chatLog.updateAssistant(accumulated);
|
|
902
1344
|
tui.requestRender();
|
|
@@ -908,6 +1350,9 @@ async function streamResponse(tui, response) {
|
|
|
908
1350
|
tui.requestRender();
|
|
909
1351
|
}
|
|
910
1352
|
},
|
|
1353
|
+
onElapsedTime: (t) => {
|
|
1354
|
+
elapsedTime = t;
|
|
1355
|
+
},
|
|
911
1356
|
onError: (message) => {
|
|
912
1357
|
tui.chatLog.addSystem(`Stream error: ${message}`, "error");
|
|
913
1358
|
tui.requestRender();
|
|
@@ -916,6 +1361,13 @@ async function streamResponse(tui, response) {
|
|
|
916
1361
|
try {
|
|
917
1362
|
const result = await sdk.streamSSE(response, callbacks);
|
|
918
1363
|
tui.chatLog.finalizeAssistant();
|
|
1364
|
+
tui.lastMetadata = result.metadata ?? null;
|
|
1365
|
+
const summary = sdk.formatStreamSummary(result, elapsedTime);
|
|
1366
|
+
if (summary) {
|
|
1367
|
+
const refs = sdk.countCitations(result.metadata ?? null);
|
|
1368
|
+
const refSuffix = refs > 0 ? ` \xB7 ${refs} ref${refs === 1 ? "" : "s"}` : "";
|
|
1369
|
+
tui.chatLog.addSummary(`[${summary}${refSuffix}]`);
|
|
1370
|
+
}
|
|
919
1371
|
tui.state.activityStatus = "idle";
|
|
920
1372
|
tui.requestRender();
|
|
921
1373
|
return result;
|
|
@@ -927,6 +1379,7 @@ async function streamResponse(tui, response) {
|
|
|
927
1379
|
throw err;
|
|
928
1380
|
}
|
|
929
1381
|
}
|
|
1382
|
+
init_tui_helpers();
|
|
930
1383
|
function deriveEncryptionKeypair(signingPrivateKeyBase64) {
|
|
931
1384
|
const signingPrivateKey = client.base64ToBytes(signingPrivateKeyBase64);
|
|
932
1385
|
const ed25519PublicKey = signingPrivateKey.slice(32, 64);
|
|
@@ -1133,7 +1586,7 @@ async function connectTuiWebSocket(options) {
|
|
|
1133
1586
|
baseUrl,
|
|
1134
1587
|
accessToken,
|
|
1135
1588
|
onMessage: (msg) => handleMessage(msg, toasts, tui ?? null),
|
|
1136
|
-
onClose: (
|
|
1589
|
+
onClose: () => {
|
|
1137
1590
|
toasts.show("WebSocket disconnected", "warning", DURATION_DEFAULT);
|
|
1138
1591
|
},
|
|
1139
1592
|
onReconnecting: (attempt, maxRetries) => {
|
|
@@ -1177,6 +1630,12 @@ var ArbiTui = class {
|
|
|
1177
1630
|
encryptionKeyPair = null;
|
|
1178
1631
|
/** Current DM channel (null = AI chat mode). */
|
|
1179
1632
|
dmChannel = null;
|
|
1633
|
+
/** Last response metadata — used by /cite for citation browsing. */
|
|
1634
|
+
lastMetadata = null;
|
|
1635
|
+
/** Active citation overlay handle (null when not showing). */
|
|
1636
|
+
citationOverlay = null;
|
|
1637
|
+
/** Input listener ID for overlay dismiss (null when no overlay). */
|
|
1638
|
+
overlayInputListener = null;
|
|
1180
1639
|
constructor(store) {
|
|
1181
1640
|
this.store = store;
|
|
1182
1641
|
this.state = {
|
|
@@ -1186,6 +1645,7 @@ var ArbiTui = class {
|
|
|
1186
1645
|
activityStatus: "idle",
|
|
1187
1646
|
isAuthenticated: false
|
|
1188
1647
|
};
|
|
1648
|
+
registerAllCommands();
|
|
1189
1649
|
this.tui = new piTui.TUI(new piTui.ProcessTerminal());
|
|
1190
1650
|
this.header = new piTui.Text(formatHeader(null, "starting..."), 1, 0);
|
|
1191
1651
|
this.chatLog = new ChatLog();
|
|
@@ -1200,11 +1660,11 @@ var ArbiTui = class {
|
|
|
1200
1660
|
this.tui.setFocus(this.editor);
|
|
1201
1661
|
}
|
|
1202
1662
|
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
1203
|
-
/** Start the TUI event loop. */
|
|
1663
|
+
/** Start the TUI event loop (clears screen for fullscreen layout). */
|
|
1204
1664
|
start() {
|
|
1205
1665
|
this.tui.start();
|
|
1206
1666
|
this.updateHeader();
|
|
1207
|
-
this.tui.requestRender();
|
|
1667
|
+
this.tui.requestRender(true);
|
|
1208
1668
|
}
|
|
1209
1669
|
/** Gracefully shut down the TUI and exit. */
|
|
1210
1670
|
shutdown() {
|
|
@@ -1237,12 +1697,12 @@ var ArbiTui = class {
|
|
|
1237
1697
|
this.tui.setFocus(this.editor);
|
|
1238
1698
|
this.tui.start();
|
|
1239
1699
|
this.updateHeader();
|
|
1240
|
-
this.tui.requestRender();
|
|
1700
|
+
this.tui.requestRender(true);
|
|
1241
1701
|
}
|
|
1242
1702
|
/** Create and wire a new editor instance. */
|
|
1243
1703
|
createEditor() {
|
|
1244
1704
|
const editor = new ArbiEditor(this.tui);
|
|
1245
|
-
editor.setAutocompleteProvider(new piTui.CombinedAutocompleteProvider(
|
|
1705
|
+
editor.setAutocompleteProvider(new piTui.CombinedAutocompleteProvider(toSlashCommands()));
|
|
1246
1706
|
editor.onSubmit = (text) => this.handleSubmit(text);
|
|
1247
1707
|
editor.onEscape = () => this.handleAbort();
|
|
1248
1708
|
editor.onCtrlC = () => this.shutdown();
|
|
@@ -1312,7 +1772,7 @@ var ArbiTui = class {
|
|
|
1312
1772
|
return;
|
|
1313
1773
|
}
|
|
1314
1774
|
if (trimmed.startsWith("/")) {
|
|
1315
|
-
const handled = await
|
|
1775
|
+
const handled = await dispatchCommand(this, trimmed);
|
|
1316
1776
|
if (handled) return;
|
|
1317
1777
|
}
|
|
1318
1778
|
if (this.dmChannel) {
|
|
@@ -1385,6 +1845,37 @@ var ArbiTui = class {
|
|
|
1385
1845
|
this.header.setText(formatHeader(this.state.workspaceName, statusText));
|
|
1386
1846
|
}
|
|
1387
1847
|
}
|
|
1848
|
+
// ── Citation overlay ───────────────────────────────────────────────────
|
|
1849
|
+
/** Show a component as a centered overlay (used by /cite). */
|
|
1850
|
+
showCitationOverlay(component) {
|
|
1851
|
+
this.dismissCitationOverlay();
|
|
1852
|
+
this.citationOverlay = this.tui.showOverlay(component, {
|
|
1853
|
+
anchor: "center",
|
|
1854
|
+
width: "80%",
|
|
1855
|
+
maxHeight: "80%",
|
|
1856
|
+
margin: 2
|
|
1857
|
+
});
|
|
1858
|
+
this.overlayInputListener = this.tui.addInputListener((data) => {
|
|
1859
|
+
if (piTui.matchesKey(data, piTui.Key.escape)) {
|
|
1860
|
+
this.dismissCitationOverlay();
|
|
1861
|
+
return { consume: true };
|
|
1862
|
+
}
|
|
1863
|
+
return void 0;
|
|
1864
|
+
});
|
|
1865
|
+
this.tui.requestRender();
|
|
1866
|
+
}
|
|
1867
|
+
/** Dismiss the active citation overlay. */
|
|
1868
|
+
dismissCitationOverlay() {
|
|
1869
|
+
if (this.citationOverlay) {
|
|
1870
|
+
this.citationOverlay.hide();
|
|
1871
|
+
this.citationOverlay = null;
|
|
1872
|
+
}
|
|
1873
|
+
if (this.overlayInputListener) {
|
|
1874
|
+
this.overlayInputListener();
|
|
1875
|
+
this.overlayInputListener = null;
|
|
1876
|
+
}
|
|
1877
|
+
this.tui.requestRender();
|
|
1878
|
+
}
|
|
1388
1879
|
// ── DM channel accessors (used by dm-handler) ────────────────────────────
|
|
1389
1880
|
/** Current DM channel (null = AI chat mode). */
|
|
1390
1881
|
get currentDmChannel() {
|