@tabbio-technologies/cli 1.2.8 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +86 -19
- package/dist/chunks/{chunk-7LNC3FIV.js → chunk-GGJEDG3P.js} +95 -14
- package/dist/chunks/{chunk-7LNC3FIV.js.map → chunk-GGJEDG3P.js.map} +4 -4
- package/dist/chunks/{chunk-Y6GWIFHI.js → chunk-KBIQKZUK.js} +3 -53
- package/dist/chunks/chunk-KBIQKZUK.js.map +7 -0
- package/dist/chunks/{chunk-QVR5KIEQ.js → chunk-KHLGZ5IV.js} +1796 -244
- package/dist/chunks/chunk-KHLGZ5IV.js.map +7 -0
- package/dist/chunks/{chunk-NAWA6NCZ.js → chunk-NRFVKK3S.js} +37 -96
- package/dist/chunks/chunk-NRFVKK3S.js.map +7 -0
- package/dist/chunks/chunk-TP3CRQJD.js +106 -0
- package/dist/chunks/chunk-TP3CRQJD.js.map +7 -0
- package/dist/chunks/{entry-WENOOT6W.js → entry-ZAXWZSEE.js} +2023 -770
- package/dist/chunks/entry-ZAXWZSEE.js.map +7 -0
- package/dist/chunks/{mount-NGUARMCL.js → mount-36JUVX3R.js} +3 -2
- package/dist/chunks/{mount-NGUARMCL.js.map → mount-36JUVX3R.js.map} +1 -1
- package/dist/chunks/{program-O2BA5L6Z.js → program-J6GENE2V.js} +314 -375
- package/dist/chunks/program-J6GENE2V.js.map +7 -0
- package/dist/chunks/{status-I5UAQPBY.js → status-2C7BECKK.js} +3 -3
- package/dist/cli.js +1 -1
- package/package.json +4 -8
- package/dist/chunks/chunk-NAWA6NCZ.js.map +0 -7
- package/dist/chunks/chunk-QVR5KIEQ.js.map +0 -7
- package/dist/chunks/chunk-Y6GWIFHI.js.map +0 -7
- package/dist/chunks/entry-WENOOT6W.js.map +0 -7
- package/dist/chunks/program-O2BA5L6Z.js.map +0 -7
- /package/dist/chunks/{status-I5UAQPBY.js.map → status-2C7BECKK.js.map} +0 -0
|
@@ -4,38 +4,33 @@ import {
|
|
|
4
4
|
Page,
|
|
5
5
|
Spinner,
|
|
6
6
|
Working,
|
|
7
|
-
clipLines,
|
|
8
|
-
displayWidth,
|
|
9
|
-
estimateHeight,
|
|
10
|
-
formatElapsed,
|
|
11
7
|
fromTerminal,
|
|
12
|
-
oneLine,
|
|
13
|
-
padEnd,
|
|
14
|
-
padStart,
|
|
15
8
|
pageMargin,
|
|
16
|
-
safeJson,
|
|
17
|
-
sentenceCase,
|
|
18
|
-
shortId,
|
|
19
|
-
stripAnsi,
|
|
20
|
-
summarizeArgs,
|
|
21
|
-
truncate,
|
|
22
|
-
truncateStart,
|
|
23
9
|
useClockTick,
|
|
24
|
-
useTerminalSize
|
|
25
|
-
|
|
26
|
-
} from "./chunk-NAWA6NCZ.js";
|
|
10
|
+
useTerminalSize
|
|
11
|
+
} from "./chunk-TP3CRQJD.js";
|
|
27
12
|
import {
|
|
28
13
|
CAPABILITY_CHOICES,
|
|
29
14
|
CAPABILITY_CHOICE_HELP,
|
|
15
|
+
CV_EXPORT_FORMATS,
|
|
30
16
|
DEFAULT_CAPABILITIES,
|
|
17
|
+
artifactsDir,
|
|
31
18
|
autoFilledProperties,
|
|
19
|
+
autoSaveDisabled,
|
|
32
20
|
capabilityRequestFields,
|
|
21
|
+
cardFromToolResult,
|
|
22
|
+
cardLines,
|
|
33
23
|
chatReducer,
|
|
34
24
|
confirmApproval,
|
|
35
25
|
describeSchema,
|
|
26
|
+
describeUser,
|
|
27
|
+
displayPath,
|
|
36
28
|
draftSummary,
|
|
37
29
|
exampleInvocation,
|
|
30
|
+
findCardJob,
|
|
31
|
+
findCardLink,
|
|
38
32
|
findListWrapper,
|
|
33
|
+
formatBytes,
|
|
39
34
|
getArtifact,
|
|
40
35
|
getPath,
|
|
41
36
|
getThreadMessages,
|
|
@@ -43,6 +38,8 @@ import {
|
|
|
43
38
|
hyperlink,
|
|
44
39
|
initialChatState,
|
|
45
40
|
isDestructiveTool,
|
|
41
|
+
isTextFile,
|
|
42
|
+
latestCardLinks,
|
|
46
43
|
listModels,
|
|
47
44
|
listThreads,
|
|
48
45
|
loadCapabilityPrefs,
|
|
@@ -52,32 +49,67 @@ import {
|
|
|
52
49
|
pickColumns,
|
|
53
50
|
previewUrl,
|
|
54
51
|
previewVariantFor,
|
|
52
|
+
readSavedPreview,
|
|
55
53
|
rejectApproval,
|
|
56
54
|
renderScreen,
|
|
55
|
+
saveAppSession,
|
|
56
|
+
saveArtifactToDisk,
|
|
57
57
|
saveCapabilityPrefs,
|
|
58
|
+
saveCvExportToDisk,
|
|
59
|
+
saveTargets,
|
|
60
|
+
saveTokenSession,
|
|
61
|
+
savedLines,
|
|
58
62
|
schemaToFlags,
|
|
59
63
|
streamChat,
|
|
60
64
|
supportsHyperlinks,
|
|
61
65
|
unavailableNotices,
|
|
62
66
|
waitForArtifact
|
|
63
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-KHLGZ5IV.js";
|
|
68
|
+
import {
|
|
69
|
+
clipLines,
|
|
70
|
+
displayWidth,
|
|
71
|
+
estimateHeight,
|
|
72
|
+
formatElapsed,
|
|
73
|
+
oneLine,
|
|
74
|
+
padEnd,
|
|
75
|
+
padStart,
|
|
76
|
+
safeJson,
|
|
77
|
+
sentenceCase,
|
|
78
|
+
shortId,
|
|
79
|
+
stripAnsi,
|
|
80
|
+
summarizeArgs,
|
|
81
|
+
truncate,
|
|
82
|
+
truncateStart,
|
|
83
|
+
wrapWords
|
|
84
|
+
} from "./chunk-NRFVKK3S.js";
|
|
64
85
|
import {
|
|
86
|
+
ApiClient,
|
|
65
87
|
CLI_VERSION,
|
|
88
|
+
ExitCode,
|
|
89
|
+
PRODUCTION_APP_URL,
|
|
90
|
+
createCommandContext,
|
|
91
|
+
debug,
|
|
92
|
+
deviceLabel,
|
|
66
93
|
getMcpAccessState,
|
|
94
|
+
hasDisplay,
|
|
67
95
|
interruptedError,
|
|
68
96
|
isCliError,
|
|
97
|
+
loadStoredCredentials,
|
|
98
|
+
loginWithBrowser,
|
|
99
|
+
loginWithEmailOtp,
|
|
100
|
+
logoutProfile,
|
|
69
101
|
readCachedCatalog,
|
|
70
102
|
relativeTime,
|
|
71
103
|
toCliError
|
|
72
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-GGJEDG3P.js";
|
|
73
105
|
import "./chunk-NK5RPNSV.js";
|
|
74
106
|
import {
|
|
75
107
|
theme
|
|
76
108
|
} from "./chunk-VHHZFMIF.js";
|
|
77
109
|
|
|
78
110
|
// src/ui/entry.tsx
|
|
79
|
-
import { Box as
|
|
80
|
-
import { useEffect as
|
|
111
|
+
import { Box as Box27, Static as Static2, Text as Text27 } from "ink";
|
|
112
|
+
import { useEffect as useEffect12, useState as useState11 } from "react";
|
|
81
113
|
|
|
82
114
|
// src/ui/components/ApprovalCard.tsx
|
|
83
115
|
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
@@ -430,8 +462,8 @@ function formKeyAction(state, input, key) {
|
|
|
430
462
|
if (key.ctrl || key.meta) return null;
|
|
431
463
|
return input ? { type: "type", text: input } : null;
|
|
432
464
|
}
|
|
433
|
-
function shellQuote(
|
|
434
|
-
return /^[\w@%+=:,./-]+$/.test(
|
|
465
|
+
function shellQuote(text2) {
|
|
466
|
+
return /^[\w@%+=:,./-]+$/.test(text2) ? text2 : `'${text2.replace(/'/g, `'\\''`)}'`;
|
|
435
467
|
}
|
|
436
468
|
function equivalentCommand(tool, input, skip = []) {
|
|
437
469
|
const parts = ["tabbio", ...tool.commandPath];
|
|
@@ -442,8 +474,8 @@ function equivalentCommand(tool, input, skip = []) {
|
|
|
442
474
|
parts.push(value ? `--${spec.flag}` : `--${spec.flag}=false`);
|
|
443
475
|
continue;
|
|
444
476
|
}
|
|
445
|
-
const
|
|
446
|
-
parts.push(`--${spec.flag}`, shellQuote(
|
|
477
|
+
const text2 = spec.kind === "array" && Array.isArray(value) && value.every((v) => typeof v !== "object") ? value.join(",") : typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
|
|
478
|
+
parts.push(`--${spec.flag}`, shellQuote(text2));
|
|
447
479
|
}
|
|
448
480
|
return parts.join(" ");
|
|
449
481
|
}
|
|
@@ -460,8 +492,8 @@ function valueText(field, raw, focused2) {
|
|
|
460
492
|
return `${focused2 ? theme.accent(box) : box} ${label}`;
|
|
461
493
|
}
|
|
462
494
|
if (kind === "enum") {
|
|
463
|
-
const
|
|
464
|
-
return focused2 ? `${theme.accent("\u2039")} ${
|
|
495
|
+
const text2 = raw || theme.dim("none");
|
|
496
|
+
return focused2 ? `${theme.accent("\u2039")} ${text2} ${theme.accent("\u203A")}` : text2;
|
|
465
497
|
}
|
|
466
498
|
return raw;
|
|
467
499
|
}
|
|
@@ -545,11 +577,14 @@ function Form(props) {
|
|
|
545
577
|
}
|
|
546
578
|
|
|
547
579
|
// src/ui/screens/chat/Chat.tsx
|
|
548
|
-
import { Box as
|
|
549
|
-
import {
|
|
580
|
+
import { Box as Box17, Static, Text as Text17, useInput as useInput7 } from "ink";
|
|
581
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo5, useReducer as useReducer4, useRef as useRef7, useState as useState5 } from "react";
|
|
550
582
|
|
|
551
583
|
// src/ui/components/editor-model.ts
|
|
552
584
|
var emptyEditor = { value: "", cursor: 0, history: [], historyIndex: null, draft: "" };
|
|
585
|
+
function editorWith(value) {
|
|
586
|
+
return { ...emptyEditor, value, cursor: [...value].length };
|
|
587
|
+
}
|
|
553
588
|
function chars(value) {
|
|
554
589
|
return [...value];
|
|
555
590
|
}
|
|
@@ -560,8 +595,8 @@ function editorReducer(state, action) {
|
|
|
560
595
|
const list = chars(state.value);
|
|
561
596
|
switch (action.type) {
|
|
562
597
|
case "insert": {
|
|
563
|
-
const
|
|
564
|
-
const inserted = chars(
|
|
598
|
+
const text2 = action.text.replace(/\r\n?/g, "\n");
|
|
599
|
+
const inserted = chars(text2);
|
|
565
600
|
return place(state, [...list.slice(0, state.cursor), ...inserted, ...list.slice(state.cursor)], state.cursor + inserted.length);
|
|
566
601
|
}
|
|
567
602
|
case "newline":
|
|
@@ -753,33 +788,10 @@ function SelectList(props) {
|
|
|
753
788
|
] });
|
|
754
789
|
}
|
|
755
790
|
|
|
756
|
-
// src/ui/components/
|
|
757
|
-
import { Box as Box6, Text as Text6 } from "ink";
|
|
791
|
+
// src/ui/components/TextInput.tsx
|
|
792
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
758
793
|
import { memo as memo5 } from "react";
|
|
759
794
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
760
|
-
function fitStatusBar(segments, hints, width) {
|
|
761
|
-
const sep = ` ${theme.symbols.middot} `;
|
|
762
|
-
let segs = [...segments];
|
|
763
|
-
let fitted = [...hints];
|
|
764
|
-
const measure = () => displayWidth(segs.join(sep)) + (fitted.length ? displayWidth(formatHints(fitted)) + 2 : 0);
|
|
765
|
-
while (measure() > width && fitted.length > 1) fitted = fitHints(fitted, displayWidth(formatHints(fitted)) - 1);
|
|
766
|
-
while (measure() > width && segs.length > 1) segs = segs.slice(0, -1);
|
|
767
|
-
if (measure() > width) fitted = [];
|
|
768
|
-
return { left: theme.dim(segs.join(sep)), right: fitted.length ? formatHints(fitted) : "" };
|
|
769
|
-
}
|
|
770
|
-
var StatusBar = memo5(function StatusBar2({ segments, hints = [], width }) {
|
|
771
|
-
const clean = segments.filter((s) => typeof s === "string" && s.length > 0);
|
|
772
|
-
const { left, right } = fitStatusBar(clean, hints, width);
|
|
773
|
-
return /* @__PURE__ */ jsxs5(Box6, { width, children: [
|
|
774
|
-
/* @__PURE__ */ jsx7(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx7(Text6, { wrap: "truncate-end", children: left }) }),
|
|
775
|
-
right ? /* @__PURE__ */ jsx7(Text6, { wrap: "truncate-end", children: right }) : null
|
|
776
|
-
] });
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
// src/ui/components/TextInput.tsx
|
|
780
|
-
import { Box as Box7, Text as Text7, useInput as useInput4 } from "ink";
|
|
781
|
-
import { memo as memo6 } from "react";
|
|
782
|
-
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
783
795
|
function cursorCell(char) {
|
|
784
796
|
if (theme.chalk.level > 0) return theme.inverse(char);
|
|
785
797
|
return `${theme.symbols.cursor}${char === " " ? "" : char}`;
|
|
@@ -814,7 +826,7 @@ function renderComposerLine(state, width, placeholder, showCursor) {
|
|
|
814
826
|
if (end === cells.length && !showCursor) out.pop();
|
|
815
827
|
return `${start > 0 ? theme.dim(theme.symbols.ellipsis) : ""}${out.join("")}`;
|
|
816
828
|
}
|
|
817
|
-
var TextInput =
|
|
829
|
+
var TextInput = memo5(function TextInput2(props) {
|
|
818
830
|
const { editor, dispatch, onSubmit, onKey, isActive, width, placeholder = "", boxed = false, prompt } = props;
|
|
819
831
|
useInput4(
|
|
820
832
|
(input, key) => {
|
|
@@ -829,23 +841,286 @@ var TextInput = memo6(function TextInput2(props) {
|
|
|
829
841
|
const promptText = prompt ?? theme.symbols.pointer;
|
|
830
842
|
const inner = boxed ? width - 4 : width;
|
|
831
843
|
const line = renderComposerLine(editor, Math.max(4, inner - displayWidth(promptText) - 1), placeholder, isActive);
|
|
832
|
-
const content = /* @__PURE__ */
|
|
844
|
+
const content = /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate-end", children: [
|
|
833
845
|
isActive ? theme.accent(promptText) : theme.dim(promptText),
|
|
834
846
|
" ",
|
|
835
847
|
line
|
|
836
848
|
] });
|
|
837
849
|
if (!boxed) return content;
|
|
838
|
-
return /* @__PURE__ */
|
|
850
|
+
return /* @__PURE__ */ jsx7(Box6, { borderStyle: theme.boxStyle, borderDimColor: true, paddingX: 1, width, children: content });
|
|
839
851
|
});
|
|
840
852
|
|
|
853
|
+
// src/ui/screens/login/LoginPanel.tsx
|
|
854
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
855
|
+
import { useCallback, useEffect as useEffect3, useReducer as useReducer2, useRef } from "react";
|
|
856
|
+
|
|
857
|
+
// src/ui/screens/login/login-model.ts
|
|
858
|
+
var LOGIN_CHOICES = [
|
|
859
|
+
{ method: "browser", label: "Open the browser (recommended)", hint: "Approve this computer in Tabbio" },
|
|
860
|
+
{ method: "email", label: "Email code", hint: "A 6-digit code, for SSH and headless machines" },
|
|
861
|
+
{ method: "token", label: "Personal token", hint: "tabbio_mcp_... from Settings; tools only" }
|
|
862
|
+
];
|
|
863
|
+
var LOGIN_USAGE = "Use /login, /login browser, /login email you@example.com or /login token";
|
|
864
|
+
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
865
|
+
function parseLoginArgs(arg) {
|
|
866
|
+
const [first = "", second = "", ...rest] = arg.trim().split(/\s+/).filter(Boolean);
|
|
867
|
+
const word = first.toLowerCase();
|
|
868
|
+
if (!word) return { method: "choose" };
|
|
869
|
+
if (rest.length > 0) return { error: LOGIN_USAGE };
|
|
870
|
+
if ((word === "browser" || word === "web") && !second) return { method: "browser" };
|
|
871
|
+
if (word === "token" && !second) return { method: "token" };
|
|
872
|
+
if (word === "email" || word === "code") return second ? { method: "email", email: second } : { method: "email" };
|
|
873
|
+
if (EMAIL.test(first) && !second) return { method: "email", email: first };
|
|
874
|
+
return { error: LOGIN_USAGE };
|
|
875
|
+
}
|
|
876
|
+
function isEmail(value) {
|
|
877
|
+
return EMAIL.test(value.trim());
|
|
878
|
+
}
|
|
879
|
+
function initialLoginPhase(start, now) {
|
|
880
|
+
return loginReducer({ name: "choose", index: 0 }, { type: "start", start, now });
|
|
881
|
+
}
|
|
882
|
+
function printable(text2) {
|
|
883
|
+
return text2.replace(/[\u0000-\u001f\u007f]/g, "");
|
|
884
|
+
}
|
|
885
|
+
function loginReducer(phase, action) {
|
|
886
|
+
switch (action.type) {
|
|
887
|
+
case "move":
|
|
888
|
+
if (phase.name !== "choose") return phase;
|
|
889
|
+
return { ...phase, index: (phase.index + action.delta + LOGIN_CHOICES.length) % LOGIN_CHOICES.length };
|
|
890
|
+
case "start":
|
|
891
|
+
switch (action.start.method) {
|
|
892
|
+
case "choose":
|
|
893
|
+
return { name: "choose", index: 0 };
|
|
894
|
+
case "browser":
|
|
895
|
+
return { name: "browser", since: action.now };
|
|
896
|
+
case "token":
|
|
897
|
+
return { name: "token-input", value: "" };
|
|
898
|
+
default:
|
|
899
|
+
return action.start.email && isEmail(action.start.email) ? { name: "email-sending", email: action.start.email.trim().toLowerCase(), since: action.now } : { name: "email-address", value: action.start.email ?? "", ...action.start.email ? { error: `Not a valid email address: ${action.start.email}` } : {} };
|
|
900
|
+
}
|
|
901
|
+
case "type": {
|
|
902
|
+
const text2 = printable(action.text);
|
|
903
|
+
if (!text2) return phase;
|
|
904
|
+
if (phase.name === "email-address" || phase.name === "token-input") return { ...phase, value: phase.value + text2, error: void 0 };
|
|
905
|
+
if (phase.name === "email-code") return { ...phase, value: (phase.value + text2).replace(/\s+/g, "").slice(0, 12), error: phase.error };
|
|
906
|
+
return phase;
|
|
907
|
+
}
|
|
908
|
+
case "erase":
|
|
909
|
+
if (phase.name === "email-address" || phase.name === "token-input" || phase.name === "email-code") {
|
|
910
|
+
return { ...phase, value: [...phase.value].slice(0, -1).join("") };
|
|
911
|
+
}
|
|
912
|
+
return phase;
|
|
913
|
+
case "browser-waiting":
|
|
914
|
+
return phase.name === "browser" ? { ...phase, url: action.url, opened: action.opened } : phase;
|
|
915
|
+
case "email-sending":
|
|
916
|
+
return { name: "email-sending", email: action.email, since: action.now };
|
|
917
|
+
case "code-needed":
|
|
918
|
+
if (phase.name === "email-sending" || phase.name === "email-verifying" || phase.name === "email-code") {
|
|
919
|
+
const error = phase.name === "email-code" ? phase.error : void 0;
|
|
920
|
+
return { name: "email-code", email: phase.email, value: "", attempt: action.attempt, ...error ? { error } : {} };
|
|
921
|
+
}
|
|
922
|
+
return phase;
|
|
923
|
+
case "code-rejected":
|
|
924
|
+
if (phase.name !== "email-verifying" && phase.name !== "email-code") return phase;
|
|
925
|
+
return {
|
|
926
|
+
name: "email-code",
|
|
927
|
+
email: phase.email,
|
|
928
|
+
value: "",
|
|
929
|
+
attempt: phase.name === "email-code" ? phase.attempt : 1,
|
|
930
|
+
error: `That code did not match. ${action.attemptsLeft} attempt${action.attemptsLeft === 1 ? "" : "s"} left.`
|
|
931
|
+
};
|
|
932
|
+
case "verifying":
|
|
933
|
+
return phase.name === "email-code" ? { name: "email-verifying", email: phase.email, since: action.now } : phase;
|
|
934
|
+
case "token-checking":
|
|
935
|
+
return phase.name === "token-input" ? { name: "token-checking", since: action.now } : phase;
|
|
936
|
+
case "invalid":
|
|
937
|
+
if (phase.name === "email-address" || phase.name === "token-input" || phase.name === "email-code") return { ...phase, error: action.error };
|
|
938
|
+
return phase;
|
|
939
|
+
default:
|
|
940
|
+
return phase;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function inputDisplay(phase, mask) {
|
|
944
|
+
if (phase.name === "token-input") return mask.repeat(Math.min(48, [...phase.value].length));
|
|
945
|
+
if (phase.name === "email-address" || phase.name === "email-code") return phase.value;
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
function waitingLabel(phase) {
|
|
949
|
+
switch (phase.name) {
|
|
950
|
+
case "browser":
|
|
951
|
+
return phase.url ? "Waiting for you to approve in the browser" : "Opening the browser";
|
|
952
|
+
case "email-sending":
|
|
953
|
+
return `Sending a sign-in code to ${phase.email}`;
|
|
954
|
+
case "email-verifying":
|
|
955
|
+
return "Checking the code";
|
|
956
|
+
case "token-checking":
|
|
957
|
+
return "Checking the token";
|
|
958
|
+
default:
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/ui/screens/login/LoginPanel.tsx
|
|
964
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
965
|
+
var CHOICE_WIDTH = Math.max(...LOGIN_CHOICES.map((c) => c.label.length)) + 5;
|
|
966
|
+
function LoginPanel(props) {
|
|
967
|
+
const { services, width, onDone, isActive = true } = props;
|
|
968
|
+
const [phase, dispatch] = useReducer2(loginReducer, props.start, (start) => initialLoginPhase(start, Date.now()));
|
|
969
|
+
const abort = useRef(null);
|
|
970
|
+
const pending = useRef(null);
|
|
971
|
+
const finished = useRef(false);
|
|
972
|
+
const flow = useRef(false);
|
|
973
|
+
const release = useCallback(() => {
|
|
974
|
+
abort.current?.abort();
|
|
975
|
+
pending.current?.reject(interruptedError("Login cancelled"));
|
|
976
|
+
pending.current = null;
|
|
977
|
+
}, []);
|
|
978
|
+
const finish = useCallback(
|
|
979
|
+
(outcome) => {
|
|
980
|
+
if (finished.current) return;
|
|
981
|
+
finished.current = true;
|
|
982
|
+
release();
|
|
983
|
+
onDone(outcome);
|
|
984
|
+
},
|
|
985
|
+
[onDone, release]
|
|
986
|
+
);
|
|
987
|
+
useEffect3(
|
|
988
|
+
() => () => {
|
|
989
|
+
finished.current = true;
|
|
990
|
+
release();
|
|
991
|
+
},
|
|
992
|
+
[release]
|
|
993
|
+
);
|
|
994
|
+
const run = useCallback(
|
|
995
|
+
(work) => {
|
|
996
|
+
work.then(
|
|
997
|
+
(change) => finish({ status: "done", change }),
|
|
998
|
+
(error2) => {
|
|
999
|
+
const cli = toCliError(error2);
|
|
1000
|
+
finish(cli.exitCode === 130 ? { status: "cancelled" } : { status: "failed", message: cli.message, hint: cli.hint });
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
1003
|
+
},
|
|
1004
|
+
[finish]
|
|
1005
|
+
);
|
|
1006
|
+
const session = services.session;
|
|
1007
|
+
useEffect3(() => {
|
|
1008
|
+
if (flow.current || finished.current) return;
|
|
1009
|
+
if (!session && phase.name !== "choose") {
|
|
1010
|
+
finish({ status: "failed", message: "Signing in is not available here", hint: "Run `tabbio login` in a terminal." });
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (!session) return;
|
|
1014
|
+
if (phase.name === "browser") {
|
|
1015
|
+
flow.current = true;
|
|
1016
|
+
const controller = new AbortController();
|
|
1017
|
+
abort.current = controller;
|
|
1018
|
+
run(session.loginWithBrowser({ signal: controller.signal, onWaiting: (url, opened) => dispatch({ type: "browser-waiting", url, opened }) }));
|
|
1019
|
+
} else if (phase.name === "email-sending") {
|
|
1020
|
+
flow.current = true;
|
|
1021
|
+
run(
|
|
1022
|
+
session.loginWithEmail({
|
|
1023
|
+
email: phase.email,
|
|
1024
|
+
promptCode: (attempt) => new Promise((resolve, reject) => {
|
|
1025
|
+
if (finished.current) return reject(interruptedError("Login cancelled"));
|
|
1026
|
+
pending.current = { resolve, reject };
|
|
1027
|
+
dispatch({ type: "code-needed", attempt });
|
|
1028
|
+
}),
|
|
1029
|
+
onInvalidCode: (attemptsLeft) => dispatch({ type: "code-rejected", attemptsLeft })
|
|
1030
|
+
})
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
}, [phase, session, run, finish]);
|
|
1034
|
+
useInput5(
|
|
1035
|
+
(input, key) => {
|
|
1036
|
+
if (key.escape || key.ctrl && input === "c") {
|
|
1037
|
+
finish({ status: "cancelled" });
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (phase.name === "choose") {
|
|
1041
|
+
if (key.upArrow || input === "k") dispatch({ type: "move", delta: -1 });
|
|
1042
|
+
else if (key.downArrow || input === "j") dispatch({ type: "move", delta: 1 });
|
|
1043
|
+
const digit = /^[1-3]$/.test(input) ? Number(input) - 1 : null;
|
|
1044
|
+
const choice = LOGIN_CHOICES[digit ?? (key.return ? phase.index : -1)];
|
|
1045
|
+
if (choice) dispatch({ type: "start", start: choice.method === "email" ? { method: "email" } : { method: choice.method }, now: Date.now() });
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (phase.name !== "email-address" && phase.name !== "email-code" && phase.name !== "token-input") return;
|
|
1049
|
+
if (key.return) {
|
|
1050
|
+
const value = phase.value.trim();
|
|
1051
|
+
if (phase.name === "email-address") {
|
|
1052
|
+
if (isEmail(value)) dispatch({ type: "email-sending", email: value.toLowerCase(), now: Date.now() });
|
|
1053
|
+
else dispatch({ type: "invalid", error: value ? `Not a valid email address: ${value}` : "Type your email address" });
|
|
1054
|
+
} else if (phase.name === "email-code") {
|
|
1055
|
+
if (value.length < 4) dispatch({ type: "invalid", error: "Enter the code from the email" });
|
|
1056
|
+
else {
|
|
1057
|
+
pending.current?.resolve(value);
|
|
1058
|
+
pending.current = null;
|
|
1059
|
+
dispatch({ type: "verifying", now: Date.now() });
|
|
1060
|
+
}
|
|
1061
|
+
} else if (!value) dispatch({ type: "invalid", error: "Paste a token first" });
|
|
1062
|
+
else if (session) {
|
|
1063
|
+
dispatch({ type: "token-checking", now: Date.now() });
|
|
1064
|
+
run(session.loginWithToken(value));
|
|
1065
|
+
}
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (key.backspace || key.delete) dispatch({ type: "erase" });
|
|
1069
|
+
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !key.leftArrow && !key.rightArrow && input) {
|
|
1070
|
+
dispatch({ type: "type", text: input });
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
{ isActive }
|
|
1074
|
+
);
|
|
1075
|
+
const inner = Math.max(10, width - 4);
|
|
1076
|
+
const hints = (list) => /* @__PURE__ */ jsx8(Text7, { wrap: "truncate-end", children: formatHints(list) });
|
|
1077
|
+
const waiting = waitingLabel(phase);
|
|
1078
|
+
const display = inputDisplay(phase, theme.symbols.bullet);
|
|
1079
|
+
const error = "error" in phase && phase.error ? /* @__PURE__ */ jsx8(Text7, { wrap: "wrap", children: theme.attention(`${theme.symbols.attention} ${phase.error}`) }) : null;
|
|
1080
|
+
return /* @__PURE__ */ jsxs6(Box7, { borderStyle: theme.boxStyle, borderDimColor: true, paddingX: 1, flexDirection: "column", width, children: [
|
|
1081
|
+
/* @__PURE__ */ jsx8(Text7, { children: theme.bold("Sign in to Tabbio") }),
|
|
1082
|
+
phase.name === "choose" ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1083
|
+
LOGIN_CHOICES.map((choice, i) => {
|
|
1084
|
+
const label = padEnd(`${i + 1} ${choice.label}`, CHOICE_WIDTH);
|
|
1085
|
+
return /* @__PURE__ */ jsxs6(Text7, { wrap: "truncate-end", children: [
|
|
1086
|
+
i === phase.index ? theme.accent(`${theme.symbols.pointer} ${label}`) : ` ${label}`,
|
|
1087
|
+
theme.dim(choice.hint)
|
|
1088
|
+
] }, choice.method);
|
|
1089
|
+
}),
|
|
1090
|
+
hints([
|
|
1091
|
+
{ key: theme.symbols.upDown, label: "choose" },
|
|
1092
|
+
{ key: "enter", label: "select" },
|
|
1093
|
+
{ key: "esc", label: "cancel" }
|
|
1094
|
+
])
|
|
1095
|
+
] }) : null,
|
|
1096
|
+
waiting ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1097
|
+
/* @__PURE__ */ jsx8(Working, { label: waiting, since: "since" in phase ? phase.since : Date.now() }),
|
|
1098
|
+
phase.name === "browser" && phase.url ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
1099
|
+
/* @__PURE__ */ jsx8(Text7, { children: theme.dim(phase.opened ? "If your browser did not open, visit:" : "Open this link to continue:") }),
|
|
1100
|
+
/* @__PURE__ */ jsx8(Text7, { wrap: "wrap", children: theme.accent(services.hyperlinks ? hyperlink(phase.url, phase.url) : phase.url) })
|
|
1101
|
+
] }) : null,
|
|
1102
|
+
hints([{ key: "esc", label: "cancel" }])
|
|
1103
|
+
] }) : null,
|
|
1104
|
+
display !== null ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1105
|
+
/* @__PURE__ */ jsx8(Text7, { wrap: "wrap", children: phase.name === "email-address" ? "Your email address" : phase.name === "email-code" ? `Code sent to ${phase.email}. Enter it here:` : "Paste a personal token (tabbio_mcp_...). It stays on this computer." }),
|
|
1106
|
+
/* @__PURE__ */ jsxs6(Text7, { wrap: "truncate-end", children: [
|
|
1107
|
+
theme.accent(theme.symbols.pointer),
|
|
1108
|
+
" ",
|
|
1109
|
+
renderComposerLine(editorWith(display), inner - 2, phase.name === "email-address" ? "you@example.com" : "", true)
|
|
1110
|
+
] }),
|
|
1111
|
+
error,
|
|
1112
|
+
hints([
|
|
1113
|
+
{ key: "enter", label: phase.name === "email-address" ? "send code" : phase.name === "email-code" ? "sign in" : "check" },
|
|
1114
|
+
{ key: "esc", label: "cancel" }
|
|
1115
|
+
])
|
|
1116
|
+
] }) : null
|
|
1117
|
+
] });
|
|
1118
|
+
}
|
|
1119
|
+
|
|
841
1120
|
// src/ui/screens/Palette.tsx
|
|
842
1121
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
843
|
-
import { memo as
|
|
1122
|
+
import { Fragment, memo as memo6 } from "react";
|
|
844
1123
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
845
|
-
function paletteMatches(commands, draft) {
|
|
846
|
-
if (!draft.startsWith("/") || /\s/.test(draft)) return [];
|
|
847
|
-
return fuzzyFilter(commands, draft.slice(1), (c) => [c.name, c.description]);
|
|
848
|
-
}
|
|
849
1124
|
function paletteArgMatches(commands, draft) {
|
|
850
1125
|
const match = /^\/([a-z][\w-]*) (\S*)$/i.exec(draft);
|
|
851
1126
|
if (!match) return [];
|
|
@@ -859,27 +1134,197 @@ function paletteArgMatches(commands, draft) {
|
|
|
859
1134
|
completion: `/${command.name} ${arg.value}`
|
|
860
1135
|
}));
|
|
861
1136
|
}
|
|
862
|
-
function parseSlash(
|
|
863
|
-
const match = /^\/([a-z][\w-]*)\s*([\s\S]*)$/i.exec(
|
|
1137
|
+
function parseSlash(text2) {
|
|
1138
|
+
const match = /^\/([a-z][\w-]*)\s*([\s\S]*)$/i.exec(text2.trim());
|
|
864
1139
|
return match ? { name: match[1].toLowerCase(), arg: (match[2] ?? "").trim() } : null;
|
|
865
1140
|
}
|
|
866
|
-
var Palette =
|
|
867
|
-
const { commands, selected, width, maxRows = 6 } = props;
|
|
1141
|
+
var Palette = memo6(function Palette2(props) {
|
|
1142
|
+
const { commands, selected, width, maxRows = 6, footer } = props;
|
|
1143
|
+
const footerRow = footer ? /* @__PURE__ */ jsx9(Text8, { wrap: "truncate-end", children: theme.dim(` ${footer}`) }) : null;
|
|
1144
|
+
if (commands.length === 0) {
|
|
1145
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", width, children: [
|
|
1146
|
+
/* @__PURE__ */ jsx9(Text8, { children: theme.dim(" No commands match") }),
|
|
1147
|
+
footerRow
|
|
1148
|
+
] });
|
|
1149
|
+
}
|
|
868
1150
|
const nameWidth = Math.min(22, Math.max(8, ...commands.map((c) => displayWidth(c.usage ?? `/${c.name}`)))) + 2;
|
|
869
1151
|
const { start, end } = windowAround(commands.length, selected, maxRows);
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1152
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", width, children: [
|
|
1153
|
+
commands.slice(start, end).map((command, i) => {
|
|
1154
|
+
const index = start + i;
|
|
1155
|
+
const active = index === selected;
|
|
1156
|
+
const name = padEnd(truncate(command.usage ?? `/${command.name}`, nameWidth - 1), nameWidth);
|
|
1157
|
+
const title = command.group && (i === 0 || commands[index - 1]?.group !== command.group) ? command.group : null;
|
|
1158
|
+
return /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1159
|
+
title ? /* @__PURE__ */ jsx9(Text8, { wrap: "truncate-end", children: theme.dim(` ${title}`) }) : null,
|
|
1160
|
+
/* @__PURE__ */ jsxs7(Text8, { wrap: "truncate-end", children: [
|
|
1161
|
+
active ? theme.accent(theme.symbols.pointer) : " ",
|
|
1162
|
+
" ",
|
|
1163
|
+
active ? theme.accent(name) : name,
|
|
1164
|
+
theme.dim(command.description),
|
|
1165
|
+
command.hint ? ` ${command.hint}` : ""
|
|
1166
|
+
] })
|
|
1167
|
+
] }, `${command.group ?? ""}:${command.name}`);
|
|
1168
|
+
}),
|
|
1169
|
+
footerRow
|
|
1170
|
+
] });
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// src/ui/screens/palette-model.ts
|
|
1174
|
+
var SKILLS_GROUP = "Skills";
|
|
1175
|
+
function isRecord2(value) {
|
|
1176
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1177
|
+
}
|
|
1178
|
+
function text(value) {
|
|
1179
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1180
|
+
}
|
|
1181
|
+
var SKILL_ID = /^[a-z0-9][\w.-]*$/i;
|
|
1182
|
+
function parseSkillsResult(result) {
|
|
1183
|
+
const list = Array.isArray(result) ? result : isRecord2(result) ? [result.skills, result.items, result.data, result.results].find(Array.isArray) ?? [] : [];
|
|
1184
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1185
|
+
const out = [];
|
|
1186
|
+
for (const entry of list) {
|
|
1187
|
+
if (!isRecord2(entry)) continue;
|
|
1188
|
+
const id = text(entry.skillId) ?? text(entry.slug) ?? text(entry.id);
|
|
1189
|
+
if (!id || !SKILL_ID.test(id)) continue;
|
|
1190
|
+
const key = id.toLowerCase();
|
|
1191
|
+
if (seen.has(key)) continue;
|
|
1192
|
+
seen.add(key);
|
|
1193
|
+
const name = text(entry.name) ?? text(entry.title) ?? id;
|
|
1194
|
+
const description = text(entry.description);
|
|
1195
|
+
out.push({ id, name, ...description ? { description } : {} });
|
|
1196
|
+
}
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
1199
|
+
function skillCommands(skills, local) {
|
|
1200
|
+
const taken = new Set(local.map((c) => c.name.toLowerCase()));
|
|
1201
|
+
return skills.filter((s) => !taken.has(s.id.toLowerCase())).map((s) => ({ name: s.id, description: s.name, group: SKILLS_GROUP, skillId: s.id }));
|
|
1202
|
+
}
|
|
1203
|
+
function isBareSlash(draft) {
|
|
1204
|
+
return /^\/(?:\s[\s\S]*)?$/.test(draft.trimStart());
|
|
1205
|
+
}
|
|
1206
|
+
var BARE_SLASH_HINT = "Type a command, Esc to clear";
|
|
1207
|
+
function paletteView(commands, skills, draft, opts = {}) {
|
|
1208
|
+
if (!draft.startsWith("/")) return { items: [], args: false, open: false };
|
|
1209
|
+
const skillRows = skillCommands(skills.skills, commands);
|
|
1210
|
+
const loading = skills.status === "loading" ? `Loading skills${opts.ellipsis ?? "..."}` : void 0;
|
|
1211
|
+
if (isBareSlash(draft)) {
|
|
1212
|
+
const more = skillRows.length ? ` ${opts.separator ?? "-"} ${skillRows.length} skill${skillRows.length === 1 ? "" : "s"} below, type to filter` : "";
|
|
1213
|
+
return { items: [...commands, ...skillRows], args: false, open: true, footer: loading ?? `${BARE_SLASH_HINT}${more}` };
|
|
1214
|
+
}
|
|
1215
|
+
if (!/\s/.test(draft)) {
|
|
1216
|
+
const query = draft.slice(1);
|
|
1217
|
+
const local = fuzzyFilter(commands, query, (c) => [c.name, c.description]);
|
|
1218
|
+
const remote = fuzzyFilter(skillRows, query, (c) => [c.name, c.description]);
|
|
1219
|
+
return { items: [...local, ...remote], args: false, open: true, ...loading ? { footer: loading } : {} };
|
|
1220
|
+
}
|
|
1221
|
+
const args = paletteArgMatches(commands, draft);
|
|
1222
|
+
return { items: args, args: args.length > 0, open: args.length > 0 };
|
|
1223
|
+
}
|
|
1224
|
+
function skillMessage(id, arg) {
|
|
1225
|
+
return arg ? `/${id} ${arg}` : `/${id}`;
|
|
1226
|
+
}
|
|
1227
|
+
function resolveSubmit(value, ctx) {
|
|
1228
|
+
const trimmed = value.trim();
|
|
1229
|
+
if (!trimmed) return { kind: "empty" };
|
|
1230
|
+
if (!trimmed.startsWith("/")) return { kind: "message", text: trimmed };
|
|
1231
|
+
const pick = (row, arg) => row.skillId ? { kind: "skill", id: row.skillId, arg, message: skillMessage(row.skillId, arg) } : { kind: "command", name: row.name, arg, picked: true };
|
|
1232
|
+
if (isBareSlash(trimmed)) return ctx.highlighted && ctx.navigated ? pick(ctx.highlighted, "") : { kind: "bare" };
|
|
1233
|
+
const slash = parseSlash(trimmed);
|
|
1234
|
+
if (!slash) return { kind: "unknown", name: trimmed.slice(1).split(/\s/, 1)[0] ?? "" };
|
|
1235
|
+
if (ctx.commands.some((c) => c.name === slash.name) || ctx.aliases?.includes(slash.name)) return { kind: "command", name: slash.name, arg: slash.arg, picked: false };
|
|
1236
|
+
const skill = ctx.skills.skills.find((s) => s.id.toLowerCase() === slash.name);
|
|
1237
|
+
if (skill) return { kind: "skill", id: skill.id, arg: slash.arg, message: skillMessage(skill.id, slash.arg) };
|
|
1238
|
+
if (!slash.arg && ctx.highlighted && !ctx.highlighted.completion) return pick(ctx.highlighted, "");
|
|
1239
|
+
if (ctx.skills.status === "loading") return { kind: "loading", name: slash.name };
|
|
1240
|
+
return { kind: "unknown", name: slash.name };
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// src/ui/screens/useSkills.ts
|
|
1244
|
+
import { useEffect as useEffect4, useRef as useRef2, useState as useState3 } from "react";
|
|
1245
|
+
var IDLE = { status: "idle", skills: [] };
|
|
1246
|
+
function useSkills(services, wanted) {
|
|
1247
|
+
const [entry, setEntry] = useState3({ owner: services, state: IDLE });
|
|
1248
|
+
const mounted = useRef2(true);
|
|
1249
|
+
useEffect4(
|
|
1250
|
+
() => () => {
|
|
1251
|
+
mounted.current = false;
|
|
1252
|
+
},
|
|
1253
|
+
[]
|
|
1254
|
+
);
|
|
1255
|
+
const current = entry.owner === services ? entry.state : IDLE;
|
|
1256
|
+
const load = services.account.signedIn ? services.loadSkills : void 0;
|
|
1257
|
+
useEffect4(() => {
|
|
1258
|
+
if (!wanted || !load || current.status !== "idle") return;
|
|
1259
|
+
const settle = (state) => {
|
|
1260
|
+
if (mounted.current) setEntry((e) => e.owner === services ? { owner: services, state } : e);
|
|
1261
|
+
};
|
|
1262
|
+
setEntry({ owner: services, state: { status: "loading", skills: [] } });
|
|
1263
|
+
load().then(
|
|
1264
|
+
(skills) => settle({ status: "ready", skills }),
|
|
1265
|
+
(error) => settle({ status: "failed", skills: [], error: toCliError(error).message })
|
|
1266
|
+
);
|
|
1267
|
+
}, [wanted, load, current.status, services]);
|
|
1268
|
+
return current;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// src/ui/screens/chat/ChatStatus.tsx
|
|
1272
|
+
import { useInput as useInput6 } from "ink";
|
|
1273
|
+
import { memo as memo8 } from "react";
|
|
1274
|
+
|
|
1275
|
+
// src/ui/components/StatusBar.tsx
|
|
1276
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1277
|
+
import { memo as memo7 } from "react";
|
|
1278
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1279
|
+
function fitStatusBar(segments, hints, width) {
|
|
1280
|
+
const sep = ` ${theme.symbols.middot} `;
|
|
1281
|
+
let segs = [...segments];
|
|
1282
|
+
let fitted = [...hints];
|
|
1283
|
+
const measure = () => displayWidth(segs.join(sep)) + (fitted.length ? displayWidth(formatHints(fitted)) + 2 : 0);
|
|
1284
|
+
while (measure() > width && fitted.length > 1) fitted = fitHints(fitted, displayWidth(formatHints(fitted)) - 1);
|
|
1285
|
+
while (measure() > width && segs.length > 1) segs = segs.slice(0, -1);
|
|
1286
|
+
if (measure() > width) fitted = [];
|
|
1287
|
+
return { left: theme.dim(segs.join(sep)), right: fitted.length ? formatHints(fitted) : "" };
|
|
1288
|
+
}
|
|
1289
|
+
var StatusBar = memo7(function StatusBar2({ segments, hints = [], width }) {
|
|
1290
|
+
const clean = segments.filter((s) => typeof s === "string" && s.length > 0);
|
|
1291
|
+
const { left, right } = fitStatusBar(clean, hints, width);
|
|
1292
|
+
return /* @__PURE__ */ jsxs8(Box9, { width, children: [
|
|
1293
|
+
/* @__PURE__ */ jsx10(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx10(Text9, { wrap: "truncate-end", children: left }) }),
|
|
1294
|
+
right ? /* @__PURE__ */ jsx10(Text9, { wrap: "truncate-end", children: right }) : null
|
|
1295
|
+
] });
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
// src/ui/screens/chat/ChatStatus.tsx
|
|
1299
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1300
|
+
var ChatStatus = memo8(function ChatStatus2(props) {
|
|
1301
|
+
useClockTick(props.since !== null);
|
|
1302
|
+
const elapsed = props.since !== null ? formatElapsed(Date.now() - props.since) : null;
|
|
1303
|
+
const hints = props.approval ? [
|
|
1304
|
+
{ key: "y", label: "approve" },
|
|
1305
|
+
{ key: "n", label: "reject" },
|
|
1306
|
+
{ key: "l", label: "later" }
|
|
1307
|
+
] : props.overlay ? [{ key: "esc", label: "close" }] : props.streaming ? [
|
|
1308
|
+
{ key: "enter", label: "queue" },
|
|
1309
|
+
{ key: "esc", label: "interrupt" },
|
|
1310
|
+
{ key: "/", label: "commands" }
|
|
1311
|
+
] : [
|
|
1312
|
+
{ key: "enter", label: "send" },
|
|
1313
|
+
{ key: "/", label: "commands" },
|
|
1314
|
+
{ key: "ctrl+c", label: "quit" }
|
|
1315
|
+
];
|
|
1316
|
+
return /* @__PURE__ */ jsx11(StatusBar, { width: props.width, segments: [...props.segments, elapsed], hints });
|
|
882
1317
|
});
|
|
1318
|
+
function capabilitySegments(mode, capabilities) {
|
|
1319
|
+
if (mode === "employer") return [];
|
|
1320
|
+
return ["research", "image", "result"].filter((key) => capabilities[key] !== DEFAULT_CAPABILITIES[key]).map((key) => `${key} ${capabilities[key]}`);
|
|
1321
|
+
}
|
|
1322
|
+
function SelectEscape({ onClose }) {
|
|
1323
|
+
useInput6((input, key) => {
|
|
1324
|
+
if (key.escape || input === "q") onClose();
|
|
1325
|
+
});
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
883
1328
|
|
|
884
1329
|
// src/ui/screens/chat/commands.ts
|
|
885
1330
|
function choices2(key) {
|
|
@@ -902,7 +1347,14 @@ function chatCommands(opts) {
|
|
|
902
1347
|
{ name: "research", usage: "/research auto|on|off", description: "Web research for the next messages", args: choices2("research") },
|
|
903
1348
|
{ name: "image", usage: "/image auto|on|off", description: "Image generation", args: choices2("image") },
|
|
904
1349
|
{ name: "result", usage: "/result auto|summary|document|\u2026", description: "What to make: answer, document, slides, page, image", args: choices2("result") },
|
|
1350
|
+
{ name: "open", usage: "/open <n|id>", description: "Open link n of the last result, or a file" },
|
|
1351
|
+
{ name: "apply", usage: "/apply <n>", description: "Prepare an application for job n" },
|
|
1352
|
+
{ name: "save", usage: "/save <n|id> [folder]", description: "Save a file Tabbio made (1 = newest)" },
|
|
1353
|
+
{ name: "show", usage: "/show <n|id>", description: "Print a saved file here (1 = newest)" },
|
|
1354
|
+
{ name: "download", usage: "/download [cvId] [pdf|docx]", description: "Save a CV (your main CV without an id)" },
|
|
905
1355
|
{ name: "help", description: "Keys and commands" },
|
|
1356
|
+
{ name: "login", usage: "/login [email|token]", description: "Sign in or switch account" },
|
|
1357
|
+
{ name: "logout", description: "Sign out of this profile" },
|
|
906
1358
|
...opts.hostedByHome ? [{ name: "home", description: "Back to the home screen" }] : [],
|
|
907
1359
|
{ name: "exit", description: "Quit tabbio" }
|
|
908
1360
|
];
|
|
@@ -912,21 +1364,33 @@ function parseMode(value) {
|
|
|
912
1364
|
return v === "seeker" || v === "employer" ? v : null;
|
|
913
1365
|
}
|
|
914
1366
|
var CHAT_HELP = [
|
|
915
|
-
"
|
|
916
|
-
" /
|
|
917
|
-
"
|
|
918
|
-
"
|
|
919
|
-
"
|
|
1367
|
+
"Chat /new /threads /mode seeker|employer /model <id|auto>",
|
|
1368
|
+
" /help /home /exit",
|
|
1369
|
+
"Modes /research auto|on|off /image auto|on|off",
|
|
1370
|
+
" /result auto|summary|document|slides|page|image",
|
|
1371
|
+
" (saved for this profile; sent with every seeker message)",
|
|
1372
|
+
"Results /open <n> opens link n of the last result",
|
|
1373
|
+
" /apply <n> prepares the application for job n (no chat)",
|
|
1374
|
+
"Files /show 1 prints the newest file of this chat, /show 2 the one",
|
|
1375
|
+
" before; /save <n|id> [folder] /open <n|id> (ids work too)",
|
|
1376
|
+
" /download [cvId] [pdf|docx|txt|md] (main CV without an id)",
|
|
1377
|
+
" generated files are saved to ./tabbio-artifacts",
|
|
1378
|
+
"Account /login /login email you@example.com /login token /logout",
|
|
1379
|
+
"Skills type / to list your skills; enter runs /<skill> <details>",
|
|
1380
|
+
"Keys enter send (queues while streaming) ctrl+j newline",
|
|
1381
|
+
" esc interrupt, or back to home \u2191\u2193 history",
|
|
1382
|
+
" shift+tab switch mode ctrl+o expand the last tool",
|
|
1383
|
+
" ctrl+c twice quit"
|
|
920
1384
|
].join("\n");
|
|
921
1385
|
|
|
922
1386
|
// src/ui/screens/chat/Transcript.tsx
|
|
923
|
-
import { Box as
|
|
924
|
-
import { memo as
|
|
1387
|
+
import { Box as Box16, Text as Text16 } from "ink";
|
|
1388
|
+
import { memo as memo15 } from "react";
|
|
925
1389
|
|
|
926
1390
|
// src/ui/components/ArtifactRow.tsx
|
|
927
|
-
import { Box as
|
|
928
|
-
import { memo as
|
|
929
|
-
import { jsx as
|
|
1391
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1392
|
+
import { memo as memo9 } from "react";
|
|
1393
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
930
1394
|
function kindGlyph(kind) {
|
|
931
1395
|
switch (kind) {
|
|
932
1396
|
case "document":
|
|
@@ -951,7 +1415,7 @@ function artifactStatusText(artifact, draft) {
|
|
|
951
1415
|
}
|
|
952
1416
|
return artifact.status;
|
|
953
1417
|
}
|
|
954
|
-
var ArtifactRow =
|
|
1418
|
+
var ArtifactRow = memo9(function ArtifactRow2({ artifact, draft, width, hint = true }) {
|
|
955
1419
|
const status = artifactStatusText(artifact, draft);
|
|
956
1420
|
const tone = artifact.status === "failed" ? theme.error : artifact.status === "ready" ? theme.success : theme.dim;
|
|
957
1421
|
const meta = [kindLabel(artifact.kind).toLowerCase(), status, shortId(artifact.id, 6, 4)];
|
|
@@ -960,28 +1424,46 @@ var ArtifactRow = memo8(function ArtifactRow2({ artifact, draft, width, hint = t
|
|
|
960
1424
|
const titleBudget = Math.max(8, Math.min(48, width - 4 - displayWidth(meta.join(" \xB7 ")) - 6));
|
|
961
1425
|
const title = truncate(artifact.title || "Untitled", titleBudget);
|
|
962
1426
|
const linked = artifact.status === "ready" && artifact.previewUrl;
|
|
963
|
-
return /* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
965
|
-
artifact.status === "pending" ? /* @__PURE__ */
|
|
966
|
-
/* @__PURE__ */
|
|
1427
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", width, children: [
|
|
1428
|
+
/* @__PURE__ */ jsxs9(Box10, { width, children: [
|
|
1429
|
+
artifact.status === "pending" ? /* @__PURE__ */ jsx12(Spinner, {}) : /* @__PURE__ */ jsx12(Text10, { children: artifact.status === "ready" ? theme.success(theme.symbols.success) : theme.error(theme.symbols.error) }),
|
|
1430
|
+
/* @__PURE__ */ jsx12(Box10, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
967
1431
|
theme.accent(kindGlyph(artifact.kind)),
|
|
968
1432
|
" ",
|
|
969
1433
|
linked ? hyperlink(theme.bold(title), artifact.previewUrl) : theme.bold(title)
|
|
970
1434
|
] }) }),
|
|
971
|
-
/* @__PURE__ */
|
|
1435
|
+
/* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsxs9(Text10, { wrap: "truncate-end", children: [
|
|
972
1436
|
theme.dim(` ${theme.symbols.middot} `),
|
|
973
1437
|
tone(meta.join(` ${theme.symbols.middot} `)),
|
|
974
1438
|
openHint ? theme.dim(` ${openHint}`) : ""
|
|
975
1439
|
] }) })
|
|
976
1440
|
] }),
|
|
977
|
-
artifact.failure?.message ? /* @__PURE__ */
|
|
1441
|
+
artifact.failure?.message ? /* @__PURE__ */ jsx12(Text10, { wrap: "truncate-end", children: theme.dim(` ${theme.symbols.arrowRight} ${artifact.failure.message}`) }) : null
|
|
978
1442
|
] });
|
|
979
1443
|
});
|
|
980
1444
|
|
|
981
|
-
// src/ui/components/
|
|
1445
|
+
// src/ui/components/cards/CardView.tsx
|
|
982
1446
|
import { Box as Box11, Text as Text11 } from "ink";
|
|
1447
|
+
import { memo as memo10 } from "react";
|
|
1448
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1449
|
+
function Line({ text: text2, width }) {
|
|
1450
|
+
const cells = displayWidth(text2);
|
|
1451
|
+
if (cells <= width) return /* @__PURE__ */ jsx13(Text11, { children: text2 });
|
|
1452
|
+
return /* @__PURE__ */ jsx13(Box11, { width: cells, flexShrink: 0, children: /* @__PURE__ */ jsx13(Text11, { wrap: "truncate-end", children: text2 }) });
|
|
1453
|
+
}
|
|
1454
|
+
var CardView = memo10(function CardView2({ card, width, links }) {
|
|
1455
|
+
const lines = cardLines(card, { width, links: links ?? supportsHyperlinks() });
|
|
1456
|
+
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", width, children: lines.map((line, i) => /* @__PURE__ */ jsx13(Line, { text: line, width }, i)) });
|
|
1457
|
+
});
|
|
1458
|
+
var SavedFilesView = memo10(function SavedFilesView2({ saved, width }) {
|
|
1459
|
+
const lines = savedLines(saved, { width });
|
|
1460
|
+
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", width, children: lines.map((line, i) => /* @__PURE__ */ jsx13(Line, { text: line, width }, i)) });
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
// src/ui/components/Markdown.tsx
|
|
1464
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
983
1465
|
import { marked } from "marked";
|
|
984
|
-
import { memo as
|
|
1466
|
+
import { memo as memo12, useMemo as useMemo3 } from "react";
|
|
985
1467
|
|
|
986
1468
|
// src/ui/components/markdown-inline.ts
|
|
987
1469
|
var ENTITIES = {
|
|
@@ -993,8 +1475,8 @@ var ENTITIES = {
|
|
|
993
1475
|
"'": "'",
|
|
994
1476
|
" ": " "
|
|
995
1477
|
};
|
|
996
|
-
function decodeEntities(
|
|
997
|
-
return
|
|
1478
|
+
function decodeEntities(text2) {
|
|
1479
|
+
return text2.replace(/&(?:amp|lt|gt|quot|apos|nbsp|#39);/g, (m) => ENTITIES[m] ?? m).replace(/&#(\d{1,6});/g, (m, code) => {
|
|
998
1480
|
const n = Number(code);
|
|
999
1481
|
return n > 0 && n < 1114111 ? String.fromCodePoint(n) : m;
|
|
1000
1482
|
});
|
|
@@ -1004,8 +1486,8 @@ function childTokens(token) {
|
|
|
1004
1486
|
return Array.isArray(tokens) ? tokens : void 0;
|
|
1005
1487
|
}
|
|
1006
1488
|
function textOf(token) {
|
|
1007
|
-
const
|
|
1008
|
-
if (typeof
|
|
1489
|
+
const text2 = token.text;
|
|
1490
|
+
if (typeof text2 === "string") return text2;
|
|
1009
1491
|
const raw = token.raw;
|
|
1010
1492
|
return typeof raw === "string" ? raw : "";
|
|
1011
1493
|
}
|
|
@@ -1041,8 +1523,8 @@ function renderToken(token) {
|
|
|
1041
1523
|
return `${theme.dim("[image]")} ${image.text || ""}${image.href ? ` ${theme.dim(`(${image.href})`)}` : ""}`.trim();
|
|
1042
1524
|
}
|
|
1043
1525
|
case "html": {
|
|
1044
|
-
const
|
|
1045
|
-
return /^<br\s*\/?>$/i.test(
|
|
1526
|
+
const text2 = textOf(token);
|
|
1527
|
+
return /^<br\s*\/?>$/i.test(text2.trim()) ? "\n" : "";
|
|
1046
1528
|
}
|
|
1047
1529
|
case "checkbox":
|
|
1048
1530
|
return "";
|
|
@@ -1064,8 +1546,8 @@ function renderInline(tokens, fallback = "") {
|
|
|
1064
1546
|
}
|
|
1065
1547
|
|
|
1066
1548
|
// src/ui/components/Table.tsx
|
|
1067
|
-
import { Box as
|
|
1068
|
-
import { memo as
|
|
1549
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1550
|
+
import { memo as memo11, useMemo as useMemo2 } from "react";
|
|
1069
1551
|
|
|
1070
1552
|
// src/ui/components/table-layout.ts
|
|
1071
1553
|
var TABLE_GAP = 2;
|
|
@@ -1103,8 +1585,8 @@ function layoutTable(columns, rows, width, gap = TABLE_GAP) {
|
|
|
1103
1585
|
function formatTableRow(layout, cells, gap = TABLE_GAP) {
|
|
1104
1586
|
return layout.columns.map((column, i) => {
|
|
1105
1587
|
const w = layout.widths[i];
|
|
1106
|
-
const
|
|
1107
|
-
return column.align === "right" ? padStart(
|
|
1588
|
+
const text2 = truncate((cells[column.key] ?? "").replace(/\s+/g, " "), w);
|
|
1589
|
+
return column.align === "right" ? padStart(text2, w) : padEnd(text2, w);
|
|
1108
1590
|
}).join(" ".repeat(gap)).trimEnd();
|
|
1109
1591
|
}
|
|
1110
1592
|
function formatTableHeader(layout, gap = TABLE_GAP) {
|
|
@@ -1114,23 +1596,23 @@ function formatTableHeader(layout, gap = TABLE_GAP) {
|
|
|
1114
1596
|
}
|
|
1115
1597
|
|
|
1116
1598
|
// src/ui/components/Table.tsx
|
|
1117
|
-
import { jsx as
|
|
1118
|
-
var Table =
|
|
1599
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1600
|
+
var Table = memo11(function Table2({ columns, rows, width, offset = 0, limit }) {
|
|
1119
1601
|
const layout = useMemo2(() => layoutTable(columns, rows, width), [columns, rows, width]);
|
|
1120
1602
|
const shown = rows.slice(offset, limit === void 0 ? void 0 : offset + limit);
|
|
1121
|
-
return /* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1123
|
-
shown.map((row, i) => /* @__PURE__ */
|
|
1603
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", width, children: [
|
|
1604
|
+
/* @__PURE__ */ jsx14(Text12, { wrap: "truncate-end", children: theme.dim(formatTableHeader(layout)) }),
|
|
1605
|
+
shown.map((row, i) => /* @__PURE__ */ jsx14(Text12, { wrap: "truncate-end", children: formatTableRow(layout, row) }, offset + i))
|
|
1124
1606
|
] });
|
|
1125
1607
|
});
|
|
1126
1608
|
|
|
1127
1609
|
// src/ui/components/Markdown.tsx
|
|
1128
|
-
import { jsx as
|
|
1129
|
-
function lexMarkdown(
|
|
1610
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1611
|
+
function lexMarkdown(text2) {
|
|
1130
1612
|
try {
|
|
1131
|
-
return marked.lexer(
|
|
1613
|
+
return marked.lexer(text2.replace(/\r\n?/g, "\n"), { gfm: true });
|
|
1132
1614
|
} catch {
|
|
1133
|
-
return [{ type: "paragraph", raw:
|
|
1615
|
+
return [{ type: "paragraph", raw: text2, text: text2, tokens: [{ type: "text", raw: text2, text: text2 }] }];
|
|
1134
1616
|
}
|
|
1135
1617
|
}
|
|
1136
1618
|
function tokensOf(token) {
|
|
@@ -1138,8 +1620,8 @@ function tokensOf(token) {
|
|
|
1138
1620
|
return Array.isArray(tokens) ? tokens : void 0;
|
|
1139
1621
|
}
|
|
1140
1622
|
function textOf2(token) {
|
|
1141
|
-
const
|
|
1142
|
-
return typeof
|
|
1623
|
+
const text2 = token.text;
|
|
1624
|
+
return typeof text2 === "string" ? text2 : typeof token.raw === "string" ? token.raw : "";
|
|
1143
1625
|
}
|
|
1144
1626
|
function renderList(list, ctx, key, trailing = "") {
|
|
1145
1627
|
const start = typeof list.start === "number" ? list.start : 1;
|
|
@@ -1147,9 +1629,9 @@ function renderList(list, ctx, key, trailing = "") {
|
|
|
1147
1629
|
(item, i) => list.ordered ? `${start + i}.` : item.task ? item.checked ? "[x]" : "[ ]" : theme.symbols.bullet
|
|
1148
1630
|
);
|
|
1149
1631
|
const markerWidth = Math.max(...markers.map((m) => stripAnsi(m).length)) + 1;
|
|
1150
|
-
return /* @__PURE__ */
|
|
1151
|
-
/* @__PURE__ */
|
|
1152
|
-
/* @__PURE__ */
|
|
1632
|
+
return /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", children: list.items.map((item, i) => /* @__PURE__ */ jsxs11(Box13, { children: [
|
|
1633
|
+
/* @__PURE__ */ jsx15(Box13, { width: markerWidth, flexShrink: 0, children: /* @__PURE__ */ jsx15(Text13, { children: list.ordered ? markers[i] : theme.dim(markers[i]) }) }),
|
|
1634
|
+
/* @__PURE__ */ jsx15(Box13, { flexDirection: "column", width: Math.max(8, ctx.width - markerWidth), children: renderBlocks(
|
|
1153
1635
|
item.tokens,
|
|
1154
1636
|
{ width: Math.max(8, ctx.width - markerWidth), trailing: i === list.items.length - 1 && trailing ? trailing : void 0 },
|
|
1155
1637
|
`${key}-${i}`,
|
|
@@ -1170,18 +1652,18 @@ function renderTable(table, ctx, key) {
|
|
|
1170
1652
|
});
|
|
1171
1653
|
return record;
|
|
1172
1654
|
});
|
|
1173
|
-
return /* @__PURE__ */
|
|
1655
|
+
return /* @__PURE__ */ jsx15(Table, { columns, rows, width: ctx.width }, key);
|
|
1174
1656
|
}
|
|
1175
1657
|
function renderBlock(token, ctx, key, trailing) {
|
|
1176
1658
|
switch (token.type) {
|
|
1177
1659
|
case "heading":
|
|
1178
|
-
return /* @__PURE__ */
|
|
1660
|
+
return /* @__PURE__ */ jsxs11(Text13, { bold: true, children: [
|
|
1179
1661
|
renderInline(tokensOf(token), textOf2(token)),
|
|
1180
1662
|
trailing
|
|
1181
1663
|
] }, key);
|
|
1182
1664
|
case "paragraph":
|
|
1183
1665
|
case "text":
|
|
1184
|
-
return /* @__PURE__ */
|
|
1666
|
+
return /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
1185
1667
|
renderInline(tokensOf(token), textOf2(token)),
|
|
1186
1668
|
trailing
|
|
1187
1669
|
] }, key);
|
|
@@ -1189,8 +1671,8 @@ function renderBlock(token, ctx, key, trailing) {
|
|
|
1189
1671
|
return renderList(token, ctx, key, trailing);
|
|
1190
1672
|
case "code": {
|
|
1191
1673
|
const lines = textOf2(token).replace(/\n$/, "").split("\n");
|
|
1192
|
-
return /* @__PURE__ */
|
|
1193
|
-
|
|
1674
|
+
return /* @__PURE__ */ jsx15(
|
|
1675
|
+
Box13,
|
|
1194
1676
|
{
|
|
1195
1677
|
flexDirection: "column",
|
|
1196
1678
|
marginLeft: 2,
|
|
@@ -1200,24 +1682,24 @@ function renderBlock(token, ctx, key, trailing) {
|
|
|
1200
1682
|
borderBottom: false,
|
|
1201
1683
|
borderLeftDimColor: true,
|
|
1202
1684
|
paddingLeft: 1,
|
|
1203
|
-
children: lines.map((line, i) => /* @__PURE__ */
|
|
1685
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsx15(Text13, { children: line || " " }, i))
|
|
1204
1686
|
},
|
|
1205
1687
|
key
|
|
1206
1688
|
);
|
|
1207
1689
|
}
|
|
1208
1690
|
case "blockquote":
|
|
1209
|
-
return /* @__PURE__ */
|
|
1691
|
+
return /* @__PURE__ */ jsx15(Rail, { tone: "muted", children: renderBlocks(tokensOf(token) ?? [], { width: Math.max(8, ctx.width - 2) }, key, false) }, key);
|
|
1210
1692
|
case "hr":
|
|
1211
|
-
return /* @__PURE__ */
|
|
1693
|
+
return /* @__PURE__ */ jsx15(Text13, { children: theme.dim(theme.symbols.line.repeat(Math.max(3, Math.min(ctx.width, 60)))) }, key);
|
|
1212
1694
|
case "table":
|
|
1213
1695
|
return renderTable(token, ctx, key);
|
|
1214
1696
|
case "html":
|
|
1215
|
-
return /* @__PURE__ */
|
|
1697
|
+
return /* @__PURE__ */ jsx15(Text13, { children: theme.dim(textOf2(token).trim()) }, key);
|
|
1216
1698
|
case "space":
|
|
1217
1699
|
case "def":
|
|
1218
1700
|
return null;
|
|
1219
1701
|
default:
|
|
1220
|
-
return /* @__PURE__ */
|
|
1702
|
+
return /* @__PURE__ */ jsx15(Text13, { children: textOf2(token) }, key);
|
|
1221
1703
|
}
|
|
1222
1704
|
}
|
|
1223
1705
|
var INLINE_TAIL = /* @__PURE__ */ new Set(["paragraph", "text", "heading", "list"]);
|
|
@@ -1231,24 +1713,24 @@ function renderBlocks(tokens, ctx, keyPrefix, spaced = true) {
|
|
|
1231
1713
|
try {
|
|
1232
1714
|
node = renderBlock(token, ctx, `${keyPrefix}-${i}`, trailing);
|
|
1233
1715
|
} catch {
|
|
1234
|
-
node = /* @__PURE__ */
|
|
1716
|
+
node = /* @__PURE__ */ jsx15(Text13, { children: token.raw }, `${keyPrefix}-${i}`);
|
|
1235
1717
|
}
|
|
1236
|
-
if (spaced && i > 0) out.push(/* @__PURE__ */
|
|
1718
|
+
if (spaced && i > 0) out.push(/* @__PURE__ */ jsx15(Text13, { children: " " }, `${keyPrefix}-${i}-gap`));
|
|
1237
1719
|
out.push(node);
|
|
1238
|
-
if (isLast && ctx.trailing && !trailing) out.push(/* @__PURE__ */
|
|
1720
|
+
if (isLast && ctx.trailing && !trailing) out.push(/* @__PURE__ */ jsx15(Text13, { children: ctx.trailing }, `${keyPrefix}-tail`));
|
|
1239
1721
|
});
|
|
1240
|
-
if (blocks.length === 0 && ctx.trailing) out.push(/* @__PURE__ */
|
|
1722
|
+
if (blocks.length === 0 && ctx.trailing) out.push(/* @__PURE__ */ jsx15(Text13, { children: ctx.trailing }, `${keyPrefix}-tail`));
|
|
1241
1723
|
return out;
|
|
1242
1724
|
}
|
|
1243
|
-
var Markdown =
|
|
1244
|
-
const tokens = useMemo3(() => lexMarkdown(
|
|
1245
|
-
return /* @__PURE__ */
|
|
1725
|
+
var Markdown = memo12(function Markdown2({ text: text2, width, trailing }) {
|
|
1726
|
+
const tokens = useMemo3(() => lexMarkdown(text2), [text2]);
|
|
1727
|
+
return /* @__PURE__ */ jsx15(Box13, { flexDirection: "column", width, children: renderBlocks(tokens, { width, trailing }, "md") });
|
|
1246
1728
|
});
|
|
1247
1729
|
|
|
1248
1730
|
// src/ui/components/SourcesList.tsx
|
|
1249
|
-
import { Box as
|
|
1250
|
-
import { memo as
|
|
1251
|
-
import { jsx as
|
|
1731
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1732
|
+
import { memo as memo13 } from "react";
|
|
1733
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1252
1734
|
var MAX_SOURCES = 8;
|
|
1253
1735
|
function hostOf(source) {
|
|
1254
1736
|
if (source.domain) return source.domain;
|
|
@@ -1258,64 +1740,65 @@ function hostOf(source) {
|
|
|
1258
1740
|
return "";
|
|
1259
1741
|
}
|
|
1260
1742
|
}
|
|
1261
|
-
var SourcesList =
|
|
1743
|
+
var SourcesList = memo13(function SourcesList2(props) {
|
|
1262
1744
|
const { sources, width } = props;
|
|
1263
1745
|
const links = props.links ?? supportsHyperlinks();
|
|
1264
1746
|
const shown = sources.slice(0, MAX_SOURCES);
|
|
1265
1747
|
const numberWidth = String(shown.length).length;
|
|
1266
|
-
return /* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1748
|
+
return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", width, children: [
|
|
1749
|
+
/* @__PURE__ */ jsx16(Text14, { children: theme.dim("Sources") }),
|
|
1268
1750
|
shown.map((source, i) => {
|
|
1269
1751
|
const host = hostOf(source);
|
|
1270
1752
|
const title = truncate(source.title, Math.max(10, Math.min(60, width - numberWidth - displayWidth(host) - 8)));
|
|
1271
|
-
return /* @__PURE__ */
|
|
1272
|
-
/* @__PURE__ */
|
|
1753
|
+
return /* @__PURE__ */ jsxs12(Box14, { width, children: [
|
|
1754
|
+
/* @__PURE__ */ jsx16(Box14, { flexShrink: 0, children: /* @__PURE__ */ jsxs12(Text14, { children: [
|
|
1273
1755
|
theme.dim(String(i + 1).padStart(numberWidth)),
|
|
1274
1756
|
" ",
|
|
1275
1757
|
links ? hyperlink(title, source.url) : title
|
|
1276
1758
|
] }) }),
|
|
1277
|
-
/* @__PURE__ */
|
|
1759
|
+
/* @__PURE__ */ jsx16(Box14, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsxs12(Text14, { wrap: "truncate-end", children: [
|
|
1278
1760
|
host && host !== source.title ? theme.dim(` ${theme.symbols.middot} ${host}`) : "",
|
|
1279
1761
|
links ? "" : theme.dim(` ${source.url}`)
|
|
1280
1762
|
] }) })
|
|
1281
1763
|
] }, `${i}-${source.url}`);
|
|
1282
1764
|
}),
|
|
1283
|
-
sources.length > shown.length ? /* @__PURE__ */
|
|
1765
|
+
sources.length > shown.length ? /* @__PURE__ */ jsx16(Text14, { children: theme.dim(`+${sources.length - shown.length} more`) }) : null
|
|
1284
1766
|
] });
|
|
1285
1767
|
});
|
|
1286
1768
|
|
|
1287
1769
|
// src/ui/components/ToolCallRow.tsx
|
|
1288
|
-
import { Box as
|
|
1289
|
-
import { memo as
|
|
1290
|
-
import { jsx as
|
|
1291
|
-
function
|
|
1770
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
1771
|
+
import { memo as memo14 } from "react";
|
|
1772
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1773
|
+
function isRecord3(value) {
|
|
1292
1774
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1293
1775
|
}
|
|
1776
|
+
var LIST_RESULT_KEYS = ["jobs", "cvs", "items", "skills", "automations", "results", "applications"];
|
|
1294
1777
|
function resultSummary(result) {
|
|
1295
1778
|
if (Array.isArray(result)) return `${result.length} found`;
|
|
1296
|
-
if (!
|
|
1779
|
+
if (!isRecord3(result)) return "";
|
|
1297
1780
|
if (result.ok === false || result.error === true) return "failed";
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
const
|
|
1302
|
-
return
|
|
1781
|
+
const key = LIST_RESULT_KEYS.find((k) => Array.isArray(result[k]));
|
|
1782
|
+
if (!key) return "";
|
|
1783
|
+
const shown = result[key].length;
|
|
1784
|
+
const total = typeof result.total === "number" && Number.isFinite(result.total) ? result.total : void 0;
|
|
1785
|
+
return `${total !== void 0 && total >= shown ? total : shown} found`;
|
|
1303
1786
|
}
|
|
1304
1787
|
function glyph(call) {
|
|
1305
|
-
if (call.decision === "approve") return /* @__PURE__ */
|
|
1306
|
-
if (call.decision === "reject") return /* @__PURE__ */
|
|
1307
|
-
if (call.left) return /* @__PURE__ */
|
|
1788
|
+
if (call.decision === "approve") return /* @__PURE__ */ jsx17(Text15, { children: theme.success(theme.symbols.success) });
|
|
1789
|
+
if (call.decision === "reject") return /* @__PURE__ */ jsx17(Text15, { children: theme.error(theme.symbols.error) });
|
|
1790
|
+
if (call.left) return /* @__PURE__ */ jsx17(Text15, { children: theme.attention(theme.symbols.attention) });
|
|
1308
1791
|
switch (call.stage) {
|
|
1309
1792
|
case "running":
|
|
1310
|
-
return /* @__PURE__ */
|
|
1793
|
+
return /* @__PURE__ */ jsx17(Spinner, {});
|
|
1311
1794
|
case "pending_approval":
|
|
1312
|
-
return /* @__PURE__ */
|
|
1795
|
+
return /* @__PURE__ */ jsx17(Text15, { children: theme.attention(theme.symbols.attention) });
|
|
1313
1796
|
case "completed":
|
|
1314
|
-
return /* @__PURE__ */
|
|
1797
|
+
return /* @__PURE__ */ jsx17(Text15, { children: theme.success(theme.symbols.success) });
|
|
1315
1798
|
case "failed":
|
|
1316
|
-
return /* @__PURE__ */
|
|
1799
|
+
return /* @__PURE__ */ jsx17(Text15, { children: theme.error(theme.symbols.error) });
|
|
1317
1800
|
default:
|
|
1318
|
-
return /* @__PURE__ */
|
|
1801
|
+
return /* @__PURE__ */ jsx17(Text15, { children: theme.dim(theme.symbols.error) });
|
|
1319
1802
|
}
|
|
1320
1803
|
}
|
|
1321
1804
|
function titleText(call) {
|
|
@@ -1330,17 +1813,17 @@ function formatToolDuration(ms) {
|
|
|
1330
1813
|
if (ms === void 0) return null;
|
|
1331
1814
|
return ms < 100 ? "<0.1s" : formatElapsed(ms);
|
|
1332
1815
|
}
|
|
1333
|
-
var RunningTime =
|
|
1816
|
+
var RunningTime = memo14(function RunningTime2({ since }) {
|
|
1334
1817
|
useClockTick(true);
|
|
1335
1818
|
const elapsed = Date.now() - since;
|
|
1336
|
-
return elapsed >= 1e3 ? /* @__PURE__ */
|
|
1819
|
+
return elapsed >= 1e3 ? /* @__PURE__ */ jsx17(Text15, { children: theme.dim(formatElapsed(elapsed)) }) : null;
|
|
1337
1820
|
});
|
|
1338
|
-
var ToolCallRow =
|
|
1821
|
+
var ToolCallRow = memo14(function ToolCallRow2({ call, width, expanded = false }) {
|
|
1339
1822
|
const card = cardOf(call);
|
|
1340
1823
|
if (card && !(call.stage === "pending_approval" && !call.decision)) {
|
|
1341
|
-
return /* @__PURE__ */
|
|
1342
|
-
/* @__PURE__ */
|
|
1343
|
-
expanded ? /* @__PURE__ */
|
|
1824
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", width, children: [
|
|
1825
|
+
/* @__PURE__ */ jsx17(ArtifactRow, { artifact: card, draft: call.draft, width }),
|
|
1826
|
+
expanded ? /* @__PURE__ */ jsx17(ToolCallDetail, { call, width }) : null
|
|
1344
1827
|
] });
|
|
1345
1828
|
}
|
|
1346
1829
|
const waiting = call.stage === "pending_approval" && !call.decision && !call.left;
|
|
@@ -1350,23 +1833,23 @@ var ToolCallRow = memo12(function ToolCallRow2({ call, width, expanded = false }
|
|
|
1350
1833
|
const running = call.stage === "running" && call.endedAt === void 0;
|
|
1351
1834
|
const rightWidth = displayWidth(summary) + 2 + (duration ? displayWidth(duration) : 6);
|
|
1352
1835
|
const args = summarizeArgs(call.args, Math.max(8, width - displayWidth(titleText(call)) - rightWidth - 7));
|
|
1353
|
-
return /* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
1836
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", width, children: [
|
|
1837
|
+
/* @__PURE__ */ jsxs13(Box15, { width, children: [
|
|
1355
1838
|
glyph(call),
|
|
1356
|
-
/* @__PURE__ */
|
|
1839
|
+
/* @__PURE__ */ jsx17(Box15, { flexGrow: 1, flexShrink: 1, marginLeft: 1, children: /* @__PURE__ */ jsxs13(Text15, { wrap: "truncate-end", children: [
|
|
1357
1840
|
titleText(call),
|
|
1358
1841
|
args ? theme.dim(` ${theme.symbols.middot} ${args}`) : ""
|
|
1359
1842
|
] }) }),
|
|
1360
|
-
/* @__PURE__ */
|
|
1361
|
-
summary ? /* @__PURE__ */
|
|
1843
|
+
/* @__PURE__ */ jsxs13(Box15, { flexShrink: 0, marginLeft: 1, children: [
|
|
1844
|
+
summary ? /* @__PURE__ */ jsxs13(Text15, { children: [
|
|
1362
1845
|
summary,
|
|
1363
1846
|
" "
|
|
1364
1847
|
] }) : null,
|
|
1365
|
-
duration ? /* @__PURE__ */
|
|
1848
|
+
duration ? /* @__PURE__ */ jsx17(Text15, { children: duration }) : running ? /* @__PURE__ */ jsx17(RunningTime, { since: call.startedAt }) : null
|
|
1366
1849
|
] })
|
|
1367
1850
|
] }),
|
|
1368
|
-
call.denial ? /* @__PURE__ */
|
|
1369
|
-
expanded ? /* @__PURE__ */
|
|
1851
|
+
call.denial ? /* @__PURE__ */ jsx17(Text15, { wrap: "truncate-end", children: theme.dim(` ${theme.symbols.arrowRight} ${call.denial.message}${call.denial.reason ? ` (${call.denial.reason})` : ""}`) }) : null,
|
|
1852
|
+
expanded ? /* @__PURE__ */ jsx17(ToolCallDetail, { call, width }) : null
|
|
1370
1853
|
] });
|
|
1371
1854
|
});
|
|
1372
1855
|
function cardOf(call) {
|
|
@@ -1381,43 +1864,44 @@ function sourcesSummary(call) {
|
|
|
1381
1864
|
return n ? `${n} ${n === 1 ? "source" : "sources"}` : "no sources";
|
|
1382
1865
|
}
|
|
1383
1866
|
function researchLines(call) {
|
|
1384
|
-
const result =
|
|
1867
|
+
const result = isRecord3(call.result) ? call.result : {};
|
|
1385
1868
|
const queries = Array.isArray(result.queries) ? result.queries.filter((q) => typeof q === "string") : [];
|
|
1386
1869
|
const lines = queries.map((q) => `query ${q}`);
|
|
1387
1870
|
(call.sources ?? []).forEach((source, i) => lines.push(`${i + 1} ${source.title} ${theme.dim(source.url)}`));
|
|
1388
1871
|
return lines;
|
|
1389
1872
|
}
|
|
1390
|
-
var ToolCallDetail =
|
|
1873
|
+
var ToolCallDetail = memo14(function ToolCallDetail2({ call, width }) {
|
|
1391
1874
|
if (call.research && call.result !== void 0 && !call.denial) {
|
|
1392
1875
|
const { lines, hidden } = clipLines(researchLines(call).join("\n"), 16);
|
|
1393
|
-
return /* @__PURE__ */
|
|
1394
|
-
lines.map((line, i) => /* @__PURE__ */
|
|
1876
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", marginLeft: 2, width: Math.max(10, width - 2), children: [
|
|
1877
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs13(Text15, { wrap: "truncate-end", children: [
|
|
1395
1878
|
theme.dim(theme.symbols.gutter),
|
|
1396
1879
|
" ",
|
|
1397
1880
|
line
|
|
1398
1881
|
] }, i)),
|
|
1399
|
-
hidden > 0 ? /* @__PURE__ */
|
|
1882
|
+
hidden > 0 ? /* @__PURE__ */ jsx17(Text15, { children: theme.dim(` +${hidden} lines`) }) : null
|
|
1400
1883
|
] });
|
|
1401
1884
|
}
|
|
1402
1885
|
const blocks = [["args", call.args]];
|
|
1403
1886
|
if (call.result !== void 0) blocks.push(["result", call.result]);
|
|
1404
|
-
return /* @__PURE__ */
|
|
1887
|
+
return /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", marginLeft: 2, width: Math.max(10, width - 2), children: blocks.map(([label, value]) => {
|
|
1405
1888
|
const { lines, hidden } = clipLines(safeJson(value), 16);
|
|
1406
|
-
return /* @__PURE__ */
|
|
1407
|
-
/* @__PURE__ */
|
|
1408
|
-
lines.map((line, i) => /* @__PURE__ */
|
|
1889
|
+
return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", children: [
|
|
1890
|
+
/* @__PURE__ */ jsx17(Text15, { children: theme.dim(label) }),
|
|
1891
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs13(Text15, { wrap: "truncate-end", children: [
|
|
1409
1892
|
theme.dim(theme.symbols.gutter),
|
|
1410
1893
|
" ",
|
|
1411
1894
|
line
|
|
1412
1895
|
] }, i)),
|
|
1413
|
-
hidden > 0 ? /* @__PURE__ */
|
|
1896
|
+
hidden > 0 ? /* @__PURE__ */ jsx17(Text15, { children: theme.dim(` +${hidden} lines`) }) : null
|
|
1414
1897
|
] }, label);
|
|
1415
1898
|
}) });
|
|
1416
1899
|
});
|
|
1417
1900
|
|
|
1418
1901
|
// src/ui/screens/chat/Transcript.tsx
|
|
1419
|
-
import { jsx as
|
|
1902
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1420
1903
|
var ASSISTANT_INDENT = 2;
|
|
1904
|
+
var CARD_INDENT = ASSISTANT_INDENT + 2;
|
|
1421
1905
|
var USER_MAX_LINES = 6;
|
|
1422
1906
|
var WAITING_AFTER_MS = 1e4;
|
|
1423
1907
|
function noticeGlyph(tone) {
|
|
@@ -1426,65 +1910,69 @@ function noticeGlyph(tone) {
|
|
|
1426
1910
|
if (tone === "attention") return theme.attention(theme.symbols.attention);
|
|
1427
1911
|
return theme.dim(theme.symbols.dot);
|
|
1428
1912
|
}
|
|
1429
|
-
function UserTurn({ text, width }) {
|
|
1430
|
-
const lines =
|
|
1913
|
+
function UserTurn({ text: text2, width }) {
|
|
1914
|
+
const lines = text2.split("\n");
|
|
1431
1915
|
const shown = lines.slice(0, USER_MAX_LINES);
|
|
1432
|
-
return /* @__PURE__ */
|
|
1433
|
-
shown.map((line, i) => /* @__PURE__ */
|
|
1434
|
-
lines.length > USER_MAX_LINES ? /* @__PURE__ */
|
|
1916
|
+
return /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Rail, { tone: "accent", width, children: [
|
|
1917
|
+
shown.map((line, i) => /* @__PURE__ */ jsx18(Text16, { wrap: "wrap", children: line || " " }, i)),
|
|
1918
|
+
lines.length > USER_MAX_LINES ? /* @__PURE__ */ jsx18(Text16, { children: theme.dim(`+${lines.length - USER_MAX_LINES} lines`) }) : null
|
|
1435
1919
|
] }) });
|
|
1436
1920
|
}
|
|
1437
|
-
var EntryView =
|
|
1921
|
+
var EntryView = memo15(function EntryView2({ entry, width }) {
|
|
1438
1922
|
const inner = Math.max(10, width - ASSISTANT_INDENT);
|
|
1439
1923
|
switch (entry.kind) {
|
|
1440
1924
|
case "header": {
|
|
1441
1925
|
const fill = Math.max(1, width - displayWidth(entry.text) - displayWidth(entry.right));
|
|
1442
|
-
return /* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1926
|
+
return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", width, children: [
|
|
1927
|
+
/* @__PURE__ */ jsxs14(Text16, { wrap: "truncate-end", children: [
|
|
1444
1928
|
entry.text,
|
|
1445
1929
|
" ".repeat(fill),
|
|
1446
1930
|
theme.dim(entry.right)
|
|
1447
1931
|
] }),
|
|
1448
|
-
/* @__PURE__ */
|
|
1932
|
+
/* @__PURE__ */ jsx18(Text16, { children: theme.dim(theme.symbols.line.repeat(width)) })
|
|
1449
1933
|
] });
|
|
1450
1934
|
}
|
|
1451
1935
|
case "user":
|
|
1452
|
-
return /* @__PURE__ */
|
|
1936
|
+
return /* @__PURE__ */ jsx18(UserTurn, { text: entry.text, width });
|
|
1453
1937
|
case "assistant":
|
|
1454
|
-
return /* @__PURE__ */
|
|
1938
|
+
return /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text16, { children: [
|
|
1455
1939
|
theme.accent(theme.symbols.dot),
|
|
1456
1940
|
" ",
|
|
1457
1941
|
theme.bold("Tabbio")
|
|
1458
1942
|
] }) });
|
|
1459
1943
|
case "markdown":
|
|
1460
|
-
return /* @__PURE__ */
|
|
1944
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: ASSISTANT_INDENT, marginTop: entry.gap ? 1 : 0, width, children: /* @__PURE__ */ jsx18(Markdown, { text: entry.text, width: inner }) });
|
|
1461
1945
|
case "tool":
|
|
1462
|
-
return /* @__PURE__ */
|
|
1946
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: ASSISTANT_INDENT, marginTop: entry.gap ? 1 : 0, width, children: /* @__PURE__ */ jsx18(ToolCallRow, { call: entry.call, width: inner }) });
|
|
1463
1947
|
case "tool-detail":
|
|
1464
|
-
return /* @__PURE__ */
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1948
|
+
return /* @__PURE__ */ jsxs14(Box16, { paddingLeft: ASSISTANT_INDENT, flexDirection: "column", width, children: [
|
|
1949
|
+
/* @__PURE__ */ jsx18(Text16, { children: theme.dim(`${theme.symbols.expanded} ${entry.call.title}`) }),
|
|
1950
|
+
/* @__PURE__ */ jsx18(ToolCallDetail, { call: entry.call, width: inner })
|
|
1467
1951
|
] });
|
|
1468
1952
|
case "artifact":
|
|
1469
|
-
return /* @__PURE__ */
|
|
1953
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: ASSISTANT_INDENT, width, children: /* @__PURE__ */ jsx18(ArtifactRow, { artifact: entry.artifact, width: inner }) });
|
|
1954
|
+
case "card":
|
|
1955
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: CARD_INDENT, width, children: /* @__PURE__ */ jsx18(CardView, { card: entry.card, width: Math.max(10, width - CARD_INDENT) }) });
|
|
1956
|
+
case "saved":
|
|
1957
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: CARD_INDENT, width, children: /* @__PURE__ */ jsx18(SavedFilesView, { saved: entry.saved, width: Math.max(10, width - CARD_INDENT) }) });
|
|
1470
1958
|
case "sources":
|
|
1471
|
-
return /* @__PURE__ */
|
|
1959
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: ASSISTANT_INDENT, marginTop: 1, width, children: /* @__PURE__ */ jsx18(SourcesList, { sources: entry.sources, width: inner }) });
|
|
1472
1960
|
case "notice":
|
|
1473
|
-
return /* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
1961
|
+
return /* @__PURE__ */ jsxs14(Box16, { paddingLeft: ASSISTANT_INDENT, marginTop: 1, flexDirection: "column", width, children: [
|
|
1962
|
+
/* @__PURE__ */ jsxs14(Text16, { wrap: "wrap", children: [
|
|
1475
1963
|
noticeGlyph(entry.tone),
|
|
1476
1964
|
" ",
|
|
1477
1965
|
entry.tone === "error" ? theme.error(entry.text) : entry.tone === "info" ? theme.dim(entry.text) : entry.text
|
|
1478
1966
|
] }),
|
|
1479
|
-
entry.hint ? /* @__PURE__ */
|
|
1967
|
+
entry.hint ? /* @__PURE__ */ jsx18(Text16, { wrap: "wrap", children: theme.dim(` ${theme.symbols.arrowRight} ${entry.hint}`) }) : null
|
|
1480
1968
|
] });
|
|
1481
1969
|
default:
|
|
1482
1970
|
return null;
|
|
1483
1971
|
}
|
|
1484
1972
|
});
|
|
1485
|
-
function clipLiveText(
|
|
1486
|
-
if (estimateHeight(
|
|
1487
|
-
const lines =
|
|
1973
|
+
function clipLiveText(text2, width, maxLines) {
|
|
1974
|
+
if (estimateHeight(text2, width) <= maxLines) return { text: text2, hidden: 0 };
|
|
1975
|
+
const lines = text2.split("\n");
|
|
1488
1976
|
const kept = [];
|
|
1489
1977
|
let used = 1;
|
|
1490
1978
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -1501,23 +1989,24 @@ function WaitingLine({ turn }) {
|
|
|
1501
1989
|
const empty = turn.items.length === 0;
|
|
1502
1990
|
if (!empty && idleFor < WAITING_AFTER_MS) return null;
|
|
1503
1991
|
const label = empty && idleFor < WAITING_AFTER_MS ? "Thinking\u2026" : "waiting for Tabbio\u2026";
|
|
1504
|
-
return /* @__PURE__ */
|
|
1992
|
+
return /* @__PURE__ */ jsx18(Box16, { paddingLeft: ASSISTANT_INDENT, children: /* @__PURE__ */ jsx18(Spinner, { label: theme.dim(`${label} ${formatElapsed(Date.now() - (empty ? turn.startedAt : turn.lastEventAt))}`) }) });
|
|
1505
1993
|
}
|
|
1506
1994
|
function liveGap(prev, item) {
|
|
1507
1995
|
if (item.kind === "tool") return prev === "markdown" || prev === "text";
|
|
1508
1996
|
return prev === "tool" || prev === "markdown" || item.committed > 0;
|
|
1509
1997
|
}
|
|
1510
|
-
var LiveItemView =
|
|
1511
|
-
const { item, width, expanded, cursor, maxLines, gap } = props;
|
|
1998
|
+
var LiveItemView = memo15(function LiveItemView2(props) {
|
|
1999
|
+
const { item, width, expanded, cursor, maxLines, gap, echoAt } = props;
|
|
1512
2000
|
if (item.kind === "tool") {
|
|
1513
|
-
return /* @__PURE__ */
|
|
2001
|
+
return /* @__PURE__ */ jsx18(Box16, { marginTop: gap ? 1 : 0, children: /* @__PURE__ */ jsx18(ToolCallRow, { call: item.call, width, expanded }) });
|
|
1514
2002
|
}
|
|
1515
|
-
const
|
|
2003
|
+
const end = echoAt !== void 0 && item.start !== void 0 ? Math.max(item.committed, echoAt - item.start) : item.text.length;
|
|
2004
|
+
const pending = item.text.slice(item.committed, end);
|
|
1516
2005
|
if (!pending && !cursor) return null;
|
|
1517
2006
|
const clipped = clipLiveText(pending, width, maxLines);
|
|
1518
|
-
return /* @__PURE__ */
|
|
1519
|
-
clipped.hidden > 0 ? /* @__PURE__ */
|
|
1520
|
-
/* @__PURE__ */
|
|
2007
|
+
return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", width, marginTop: gap ? 1 : 0, children: [
|
|
2008
|
+
clipped.hidden > 0 ? /* @__PURE__ */ jsx18(Text16, { children: theme.dim(`${theme.symbols.ellipsis} ${clipped.hidden} more lines above (streaming)`) }) : null,
|
|
2009
|
+
/* @__PURE__ */ jsx18(Markdown, { text: clipped.text, width, trailing: cursor ? theme.accent(theme.symbols.cursor) : void 0 })
|
|
1521
2010
|
] });
|
|
1522
2011
|
});
|
|
1523
2012
|
function LiveTurn(props) {
|
|
@@ -1526,65 +2015,393 @@ function LiveTurn(props) {
|
|
|
1526
2015
|
const last = turn.items[turn.items.length - 1];
|
|
1527
2016
|
const tools = turn.items.filter((i) => i.kind === "tool").length;
|
|
1528
2017
|
const textBudget = Math.max(3, maxLines - tools - 1);
|
|
1529
|
-
return /* @__PURE__ */
|
|
1530
|
-
/* @__PURE__ */
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2018
|
+
return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", width, children: [
|
|
2019
|
+
/* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", paddingLeft: ASSISTANT_INDENT, width, children: [
|
|
2020
|
+
expandTools ? (turn.hidden ?? []).map((call) => /* @__PURE__ */ jsx18(ToolCallRow, { call, width: inner, expanded: true }, `hidden-${call.id}`)) : null,
|
|
2021
|
+
turn.items.map((item, i) => {
|
|
2022
|
+
const before = turn.items[i - 1];
|
|
2023
|
+
const prev = before ? before.kind === "tool" ? "tool" : "text" : turn.lastKind;
|
|
2024
|
+
return /* @__PURE__ */ jsx18(
|
|
2025
|
+
LiveItemView,
|
|
2026
|
+
{
|
|
2027
|
+
item,
|
|
2028
|
+
width: inner,
|
|
2029
|
+
expanded: expandTools,
|
|
2030
|
+
cursor: streaming && item === last && item.kind === "text",
|
|
2031
|
+
maxLines: textBudget,
|
|
2032
|
+
gap: liveGap(prev, item),
|
|
2033
|
+
echoAt: turn.echoAt
|
|
2034
|
+
},
|
|
2035
|
+
item.id
|
|
2036
|
+
);
|
|
2037
|
+
})
|
|
2038
|
+
] }),
|
|
2039
|
+
streaming ? /* @__PURE__ */ jsx18(WaitingLine, { turn }) : null
|
|
1547
2040
|
] });
|
|
1548
2041
|
}
|
|
1549
2042
|
|
|
1550
2043
|
// src/ui/screens/chat/useChatCommands.ts
|
|
1551
|
-
import { useCallback, useRef } from "react";
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
2044
|
+
import { useCallback as useCallback3, useRef as useRef4 } from "react";
|
|
2045
|
+
|
|
2046
|
+
// src/ui/screens/chat/useFileCommands.ts
|
|
2047
|
+
import { homedir as homedir2 } from "node:os";
|
|
2048
|
+
import { extname, resolve as resolvePath } from "node:path";
|
|
2049
|
+
import { useCallback as useCallback2, useRef as useRef3 } from "react";
|
|
2050
|
+
|
|
2051
|
+
// src/ui/screens/chat/apply-command.ts
|
|
2052
|
+
var CARD_TYPE = "application-preview";
|
|
2053
|
+
function notFound(result) {
|
|
2054
|
+
return !result.ok && (result.error.exitCode === ExitCode.NotFound || /not found/i.test(result.error.message));
|
|
2055
|
+
}
|
|
2056
|
+
function applyNotes(result, tool, appUrl) {
|
|
2057
|
+
const record = typeof result === "object" && result !== null ? result : {};
|
|
2058
|
+
if (record.redirectRequired !== true) return [];
|
|
2059
|
+
const card = cardFromToolResult({ id: "apply", name: tool, args: {}, result, cardType: CARD_TYPE }, { appUrl });
|
|
2060
|
+
const link = card?.links[0];
|
|
2061
|
+
return link ? [{ tone: "info", text: `/open ${link.n} opens the employer's application page` }] : [];
|
|
2062
|
+
}
|
|
2063
|
+
function prepareApplicationTool(opts) {
|
|
2064
|
+
const { services, job, source } = opts;
|
|
2065
|
+
const tool = source === "jobs" ? "applicationPrepare" : "catalogApplyPrepare";
|
|
2066
|
+
const appUrl = services.appUrl ?? PRODUCTION_APP_URL;
|
|
2067
|
+
return {
|
|
2068
|
+
text: `/apply ${opts.n}`,
|
|
2069
|
+
name: tool,
|
|
2070
|
+
title: "Prepare application",
|
|
2071
|
+
cardType: CARD_TYPE,
|
|
2072
|
+
args: { job: job.title, ...source === "jobs" ? { jobId: job.id } : { catalogJobId: job.id } },
|
|
2073
|
+
async run(signal) {
|
|
2074
|
+
if (source === "jobs") return services.callTool("application.prepare", { jobId: job.id }, { signal });
|
|
2075
|
+
const first = await services.callTool("catalog.applyPrepare", { catalogJobId: job.id }, { signal });
|
|
2076
|
+
if (source === void 0 && notFound(first)) return services.callTool("application.prepare", { jobId: job.id }, { signal });
|
|
2077
|
+
return first;
|
|
1563
2078
|
},
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
2079
|
+
after: (result) => applyNotes(result, tool, appUrl)
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/ui/screens/chat/card-commands.ts
|
|
2084
|
+
import { homedir } from "node:os";
|
|
2085
|
+
function placedCards(state) {
|
|
2086
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
2087
|
+
const placed = [];
|
|
2088
|
+
state.entries.forEach((entry, at) => {
|
|
2089
|
+
if (entry.kind === "tool") toolNames.set(entry.call.id, entry.call.name);
|
|
2090
|
+
if (entry.kind === "card") placed.push({ card: entry.card, tool: toolNames.get(entry.toolCallId), at });
|
|
2091
|
+
});
|
|
2092
|
+
(state.turn?.items ?? []).forEach((item, i) => {
|
|
2093
|
+
if (item.kind === "tool" && item.call.card) placed.push({ card: item.call.card, tool: item.call.name, at: state.entries.length + i });
|
|
2094
|
+
});
|
|
2095
|
+
return placed;
|
|
2096
|
+
}
|
|
2097
|
+
function chatCards(state) {
|
|
2098
|
+
return placedCards(state).map((p) => p.card);
|
|
2099
|
+
}
|
|
2100
|
+
function applySource(state) {
|
|
2101
|
+
const withJobs = placedCards(state).filter((p) => p.card.rows.some((row) => row.job));
|
|
2102
|
+
const tool = withJobs[withJobs.length - 1]?.tool;
|
|
2103
|
+
if (!tool) return void 0;
|
|
2104
|
+
if (tool.startsWith("catalog")) return "catalog";
|
|
2105
|
+
if (/^(job|application)[A-Z]/.test(tool)) return "jobs";
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2108
|
+
function recentFiles(state, touched = []) {
|
|
2109
|
+
const seen = [];
|
|
2110
|
+
const add = (id, at) => {
|
|
2111
|
+
if (id) seen.push({ id, at });
|
|
2112
|
+
};
|
|
2113
|
+
state.entries.forEach((entry, at) => {
|
|
2114
|
+
if (entry.kind === "tool") {
|
|
2115
|
+
if (entry.call.artifact?.status === "ready") add(entry.call.artifact.id, at);
|
|
2116
|
+
if (entry.call.saved?.files.length) add(entry.call.saved.artifactId, at);
|
|
2117
|
+
} else if (entry.kind === "artifact" && entry.artifact.status === "ready") add(entry.artifact.id, at);
|
|
2118
|
+
else if (entry.kind === "saved" && entry.saved.files.length) add(entry.saved.artifactId, at);
|
|
2119
|
+
});
|
|
2120
|
+
(state.turn?.items ?? []).forEach((item, i) => {
|
|
2121
|
+
if (item.kind === "tool" && item.call.artifact?.status === "ready") add(item.call.artifact.id, state.entries.length + i);
|
|
2122
|
+
});
|
|
2123
|
+
for (const t of touched) add(t.id, t.at);
|
|
2124
|
+
const newest = /* @__PURE__ */ new Map();
|
|
2125
|
+
for (const { id, at } of seen) newest.set(id, Math.max(at, newest.get(id) ?? -1));
|
|
2126
|
+
return [...newest.entries()].map(([id, at]) => ({ id, at })).sort((a, b) => b.at - a.at);
|
|
2127
|
+
}
|
|
2128
|
+
function resolveFileRef(arg, files) {
|
|
2129
|
+
const value = arg.trim();
|
|
2130
|
+
if (!/^\d+$/.test(value)) return { kind: "id", id: value };
|
|
2131
|
+
const n = Number(value);
|
|
2132
|
+
const file = n >= 1 ? files[n - 1] : void 0;
|
|
2133
|
+
if (file) return { kind: "id", id: file.id };
|
|
2134
|
+
return {
|
|
2135
|
+
kind: "error",
|
|
2136
|
+
message: files.length ? `There is no file ${n}: this chat has ${files.length} file${files.length === 1 ? "" : "s"} (${range(files.length)}, 1 is the newest)` : `There is no file ${n}: no file in this chat yet (use the file id)`
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function range(count) {
|
|
2140
|
+
return count === 1 ? "1" : `1 to ${count}`;
|
|
2141
|
+
}
|
|
2142
|
+
var ARTIFACT_ID = /^[A-Za-z][\w-]{5,}$/;
|
|
2143
|
+
function resolveOpenTarget(arg, placed, files = []) {
|
|
2144
|
+
const linkCards = placed.filter((p) => p.card.links.length > 0);
|
|
2145
|
+
const newestLinks = linkCards[linkCards.length - 1];
|
|
2146
|
+
const plain = placed.map((p) => p.card);
|
|
2147
|
+
const count = latestCardLinks(plain).length;
|
|
2148
|
+
const value = arg.trim();
|
|
2149
|
+
const filesFirst = files.length > 0 && (!newestLinks || files[0].at > newestLinks.at);
|
|
2150
|
+
if (!value) {
|
|
2151
|
+
return {
|
|
2152
|
+
kind: "error",
|
|
2153
|
+
message: filesFirst ? `Use /open <n> (${range(files.length)}, 1 is the newest file) or /open <file id>` : count ? `Use /open <n> (${range(count)}) or /open <file id>` : "Use /open <n> after a result with links, or /open <file id>"
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
if (/^\d+$/.test(value)) {
|
|
2157
|
+
if (filesFirst) {
|
|
2158
|
+
const ref = resolveFileRef(value, files);
|
|
2159
|
+
return ref.kind === "id" ? { kind: "artifact", id: ref.id } : ref;
|
|
2160
|
+
}
|
|
2161
|
+
const n = Number(value);
|
|
2162
|
+
const link = findCardLink(plain, n);
|
|
2163
|
+
if (link) return { kind: "link", link };
|
|
2164
|
+
return {
|
|
2165
|
+
kind: "error",
|
|
2166
|
+
message: count ? `There is no link ${n}: the last result has ${count} link${count === 1 ? "" : "s"} (${range(count)})` : `There is no link ${n}: no result with links yet`
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
if (ARTIFACT_ID.test(value)) return { kind: "artifact", id: value };
|
|
2170
|
+
return { kind: "error", message: `Not a link number or a file id: ${value}` };
|
|
2171
|
+
}
|
|
2172
|
+
function jobCount(cards) {
|
|
2173
|
+
for (let i = cards.length - 1; i >= 0; i -= 1) {
|
|
2174
|
+
const rows = cards[i].rows.filter((row) => row.job);
|
|
2175
|
+
if (rows.length) return rows.length;
|
|
2176
|
+
}
|
|
2177
|
+
return 0;
|
|
2178
|
+
}
|
|
2179
|
+
function resolveApply(arg, cards) {
|
|
2180
|
+
const count = jobCount(cards);
|
|
2181
|
+
const value = arg.trim();
|
|
2182
|
+
if (!/^\d+$/.test(value)) {
|
|
2183
|
+
return { kind: "error", message: count ? `Use /apply <n> (${range(count)})` : "Use /apply <n> after a job search result" };
|
|
2184
|
+
}
|
|
2185
|
+
const n = Number(value);
|
|
2186
|
+
const job = findCardJob(cards, n);
|
|
2187
|
+
if (!job) {
|
|
2188
|
+
return {
|
|
2189
|
+
kind: "error",
|
|
2190
|
+
message: count ? `There is no job ${n}: the last result lists ${count} job${count === 1 ? "" : "s"} (${range(count)})` : `There is no job ${n}: no job result yet`
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
return { kind: "apply", n, job };
|
|
2194
|
+
}
|
|
2195
|
+
function savedLine(files, cwd) {
|
|
2196
|
+
return `Saved ${files.map((f) => `${displayPath(f.path, cwd)} (${formatBytes(f.bytes)})`).join(", ")}`;
|
|
2197
|
+
}
|
|
2198
|
+
function previewBlock(path, preview, gutter, cwd) {
|
|
2199
|
+
const shown = displayPath(path, cwd);
|
|
2200
|
+
const body = preview.lines.map((line) => `${gutter} ${line.replace(/\t/g, " ")}`);
|
|
2201
|
+
const more = preview.total > preview.lines.length ? [`+${preview.total - preview.lines.length} more lines in ${shown}`] : [];
|
|
2202
|
+
return [`${shown} (${preview.total} line${preview.total === 1 ? "" : "s"})`, ...body, ...more].join("\n");
|
|
2203
|
+
}
|
|
2204
|
+
var FORMATS = CV_EXPORT_FORMATS;
|
|
2205
|
+
function parseDownloadArgs(arg) {
|
|
2206
|
+
const words = arg.trim().split(/\s+/).filter(Boolean);
|
|
2207
|
+
if (words.length > 2) return { error: `Use /download [cvId] [${FORMATS.join("|")}]` };
|
|
2208
|
+
const [first, second] = words;
|
|
2209
|
+
const firstIsFormat = first !== void 0 && second === void 0 && FORMATS.includes(first.toLowerCase());
|
|
2210
|
+
const cvId = firstIsFormat ? null : first ?? null;
|
|
2211
|
+
const format = (firstIsFormat ? first : second ?? "pdf").toLowerCase();
|
|
2212
|
+
if (!FORMATS.includes(format)) return { error: `Unknown format ${second ?? first}: use ${FORMATS.join(", ")}` };
|
|
2213
|
+
return { cvId, format };
|
|
2214
|
+
}
|
|
2215
|
+
function parseSaveArgs(arg) {
|
|
2216
|
+
const match = /^(\S+)(?:\s+(.+))?$/.exec(arg.trim());
|
|
2217
|
+
if (!match) return { error: "Use /save <file id> [folder]" };
|
|
2218
|
+
const dir = match[2]?.trim().replace(/^["']|["']$/g, "").replace(/^~(?=$|[\\/])/, homedir());
|
|
2219
|
+
return { id: match[1], ...dir ? { dir } : {} };
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// src/ui/screens/chat/useFileCommands.ts
|
|
2223
|
+
var SHOW_MAX_LINES = 200;
|
|
2224
|
+
function looksLikePath(value) {
|
|
2225
|
+
return /[\\/]/.test(value) || value.startsWith(".") || value.startsWith("~") || isTextFile(value) || Boolean(extname(value) && /\.\w{2,5}$/.test(value));
|
|
2226
|
+
}
|
|
2227
|
+
function useFileCommands(session, services) {
|
|
2228
|
+
const { state, dispatch } = session;
|
|
2229
|
+
const touched = useRef3([]);
|
|
2230
|
+
const touch = useCallback2((id) => touched.current.push({ id, at: state.entries.length }), [state.entries.length]);
|
|
2231
|
+
const note = useCallback2(
|
|
2232
|
+
(tone, text2, hint) => dispatch({ type: "notice", tone, text: text2, ...hint ? { hint } : {} }),
|
|
2233
|
+
[dispatch]
|
|
2234
|
+
);
|
|
2235
|
+
const fail = useCallback2(
|
|
2236
|
+
(prefix) => (error) => {
|
|
2237
|
+
const cli = toCliError(error);
|
|
2238
|
+
note("error", `${prefix}: ${cli.message}`, cli.hint);
|
|
2239
|
+
},
|
|
2240
|
+
[note]
|
|
2241
|
+
);
|
|
2242
|
+
const openLink = useCallback2(
|
|
2243
|
+
async (url, label) => {
|
|
2244
|
+
const opened = services.openUrl ? await services.openUrl(url).catch(() => false) : false;
|
|
2245
|
+
note(opened ? "success" : "info", opened ? `Opened ${label} in the browser` : `Open ${label}:`, services.hyperlinks ? hyperlink(url, url) : url);
|
|
2246
|
+
},
|
|
2247
|
+
[services, note]
|
|
2248
|
+
);
|
|
2249
|
+
const openArtifact = useCallback2(
|
|
2250
|
+
async (id) => {
|
|
2251
|
+
if (!services.getArtifact || !services.artifactPreviewUrl) {
|
|
2252
|
+
note("error", "Opening files is not available here", `Run \`tabbio artifacts open ${id}\`.`);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
const artifact = await services.getArtifact(id);
|
|
2256
|
+
if (artifact.status === "failed") {
|
|
2257
|
+
note("error", `${artifact.title} failed: ${artifact.error?.message ?? "unknown error"}`);
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
if (artifact.status !== "ready") {
|
|
2261
|
+
note("info", `${artifact.title} is still being created`, `Try /open ${id} again in a moment.`);
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
await openLink(await services.artifactPreviewUrl(id, artifact.kind), artifact.title);
|
|
2265
|
+
},
|
|
2266
|
+
[services, note, openLink]
|
|
2267
|
+
);
|
|
2268
|
+
const show = useCallback2(
|
|
2269
|
+
async (target) => {
|
|
2270
|
+
let files;
|
|
2271
|
+
if (looksLikePath(target)) files = [{ path: resolvePath(target.replace(/^~(?=$|[\\/])/, homedir2())), bytes: 0, kind: "file" }];
|
|
2272
|
+
else {
|
|
2273
|
+
files = services.savedFiles?.(target);
|
|
2274
|
+
if (!files?.length) {
|
|
2275
|
+
if (!services.saveArtifact) {
|
|
2276
|
+
note("error", "Saving files is not available here", `Run \`tabbio artifacts download ${target}\`.`);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
files = await services.saveArtifact(target);
|
|
2280
|
+
if (files.length) note("success", savedLine(files));
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
const text2 = files.find((f) => isTextFile(f.path));
|
|
2284
|
+
if (!text2) {
|
|
2285
|
+
note(
|
|
2286
|
+
"info",
|
|
2287
|
+
files.length ? `No text to print for ${files.map((f) => displayPath(f.path)).join(", ")}` : `Nothing saved for ${target} yet`,
|
|
2288
|
+
looksLikePath(target) ? void 0 : `/open ${target} opens it in the browser`
|
|
2289
|
+
);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if (!services.readSavedPreview) {
|
|
2293
|
+
note("error", "Printing files is not available here");
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
const preview = await services.readSavedPreview(text2.path, SHOW_MAX_LINES);
|
|
2297
|
+
note("info", previewBlock(text2.path, preview, theme.symbols.gutter), looksLikePath(target) ? void 0 : `/open ${target} opens it in the browser`);
|
|
2298
|
+
},
|
|
2299
|
+
[services, note]
|
|
2300
|
+
);
|
|
2301
|
+
return useCallback2(
|
|
2302
|
+
(name, arg) => {
|
|
2303
|
+
const files = recentFiles(state, touched.current);
|
|
2304
|
+
const fileId = (value) => {
|
|
2305
|
+
const ref = resolveFileRef(value, files);
|
|
2306
|
+
if (ref.kind === "error") note("error", ref.message);
|
|
2307
|
+
return ref.kind === "id" ? ref.id : null;
|
|
2308
|
+
};
|
|
2309
|
+
switch (name) {
|
|
2310
|
+
case "open": {
|
|
2311
|
+
const target = resolveOpenTarget(arg, placedCards(state), files);
|
|
2312
|
+
if (target.kind === "error") note("error", target.message);
|
|
2313
|
+
else if (target.kind === "link") void openLink(target.link.url, `${target.link.n}. ${target.link.label}`).catch(fail("Could not open the link"));
|
|
2314
|
+
else {
|
|
2315
|
+
touch(target.id);
|
|
2316
|
+
void openArtifact(target.id).catch(fail(`Could not open ${target.id}`));
|
|
2317
|
+
}
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
case "apply": {
|
|
2321
|
+
const target = resolveApply(arg, chatCards(state));
|
|
2322
|
+
if (target.kind === "error") return note("error", target.message);
|
|
2323
|
+
const started = session.runTool(prepareApplicationTool({ services, n: target.n, job: target.job, source: applySource(state) }));
|
|
2324
|
+
if (!started) note("attention", "Tabbio is still answering", `Wait for the reply (esc stops it), then /apply ${target.n} again.`);
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
case "save": {
|
|
2328
|
+
const parsed = parseSaveArgs(arg);
|
|
2329
|
+
if ("error" in parsed) return note("error", parsed.error);
|
|
2330
|
+
const id = fileId(parsed.id);
|
|
2331
|
+
if (!id) return;
|
|
2332
|
+
if (!services.saveArtifact) return note("error", "Saving files is not available here", `Run \`tabbio artifacts download ${id}\`.`);
|
|
2333
|
+
note("info", `Saving ${id}${theme.symbols.ellipsis}`);
|
|
2334
|
+
services.saveArtifact(id, parsed.dir ? { dir: parsed.dir } : {}).then((saved) => {
|
|
2335
|
+
if (!saved.length) return note("attention", `Nothing was saved for ${id}`, "It may still be rendering: try again in a moment.");
|
|
2336
|
+
touch(id);
|
|
2337
|
+
note("success", savedLine(saved), `/show ${id} prints it ${theme.symbols.middot} /open ${id} opens it in the browser`);
|
|
2338
|
+
}, fail(`Could not save ${id}`));
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
case "show": {
|
|
2342
|
+
const value = arg.trim();
|
|
2343
|
+
if (!value) return note("error", "Use /show <n> (1 = the newest file of this chat), /show <file id>, or a saved file path");
|
|
2344
|
+
const target = looksLikePath(value) ? value : fileId(value);
|
|
2345
|
+
if (!target) return;
|
|
2346
|
+
if (!looksLikePath(target)) touch(target);
|
|
2347
|
+
void show(target).catch(fail(`Could not show ${target}`));
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
case "download": {
|
|
2351
|
+
const parsed = parseDownloadArgs(arg);
|
|
2352
|
+
if ("error" in parsed) return note("error", parsed.error);
|
|
2353
|
+
const label = !parsed.cvId || /^(master|main)$/i.test(parsed.cvId) ? "your main CV" : parsed.cvId;
|
|
2354
|
+
if (!services.saveCvExport) return note("error", "Saving CVs is not available here");
|
|
2355
|
+
note("info", `Exporting ${label} as ${parsed.format.toUpperCase()}${theme.symbols.ellipsis}`);
|
|
2356
|
+
services.saveCvExport(parsed.cvId, { format: parsed.format }).then((file) => note("success", savedLine([file])), fail(`Could not export ${label}`));
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
default:
|
|
2360
|
+
note("error", `Unknown command /${name}`);
|
|
2361
|
+
}
|
|
2362
|
+
},
|
|
2363
|
+
[state, session, services, note, fail, openLink, openArtifact, show, touch]
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// src/ui/screens/chat/useChatCommands.ts
|
|
2368
|
+
function useChatCommands(deps) {
|
|
2369
|
+
const { session, services, companyId, hostedByHome, onExit, openThreads, openLogin, onAccountChange } = deps;
|
|
2370
|
+
const { state } = session;
|
|
2371
|
+
const availability = useRef4(null);
|
|
2372
|
+
const runFileCommand = useFileCommands(session, services);
|
|
2373
|
+
const warnIfUnavailable = useCallback3(
|
|
2374
|
+
(capabilities) => {
|
|
2375
|
+
if (!services.loadCapabilities) return;
|
|
2376
|
+
availability.current ??= services.loadCapabilities().catch(() => null);
|
|
2377
|
+
void availability.current.then((answer) => {
|
|
2378
|
+
for (const text2 of unavailableNotices(capabilities, answer)) session.dispatch({ type: "notice", tone: "info", text: text2 });
|
|
2379
|
+
});
|
|
2380
|
+
},
|
|
2381
|
+
[services, session]
|
|
2382
|
+
);
|
|
2383
|
+
const setCapability = useCallback3(
|
|
2384
|
+
(key, arg) => {
|
|
2385
|
+
if (!arg) {
|
|
2386
|
+
session.dispatch({ type: "notice", tone: "info", text: `${key} ${state.capabilities[key]} ${theme.symbols.middot} /${key} <mode> to change it` });
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
let value;
|
|
2390
|
+
try {
|
|
2391
|
+
value = parseCapabilityMode(key, arg);
|
|
2392
|
+
} catch (error) {
|
|
2393
|
+
session.dispatch({ type: "notice", tone: "error", text: toCliError(error).message.replace(/^--/, "/") });
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
const next = { ...state.capabilities, [key]: value };
|
|
2397
|
+
session.dispatch({ type: "set-capability", key, value });
|
|
2398
|
+
let saved = false;
|
|
2399
|
+
try {
|
|
2400
|
+
services.saveCapabilityPrefs?.(next);
|
|
2401
|
+
saved = Boolean(services.saveCapabilityPrefs);
|
|
2402
|
+
} catch {
|
|
2403
|
+
saved = false;
|
|
2404
|
+
}
|
|
1588
2405
|
const scope = state.mode === "employer" ? " (employer chats ignore it)" : "";
|
|
1589
2406
|
session.dispatch({
|
|
1590
2407
|
type: "notice",
|
|
@@ -1595,7 +2412,7 @@ function useChatCommands(deps) {
|
|
|
1595
2412
|
},
|
|
1596
2413
|
[session, services, state.capabilities, state.mode, warnIfUnavailable]
|
|
1597
2414
|
);
|
|
1598
|
-
return
|
|
2415
|
+
return useCallback3(
|
|
1599
2416
|
(name, arg) => {
|
|
1600
2417
|
switch (name) {
|
|
1601
2418
|
case "new":
|
|
@@ -1636,6 +2453,35 @@ function useChatCommands(deps) {
|
|
|
1636
2453
|
case "help":
|
|
1637
2454
|
session.dispatch({ type: "notice", tone: "info", text: CHAT_HELP });
|
|
1638
2455
|
return;
|
|
2456
|
+
case "open":
|
|
2457
|
+
case "apply":
|
|
2458
|
+
case "save":
|
|
2459
|
+
case "show":
|
|
2460
|
+
case "download":
|
|
2461
|
+
runFileCommand(name, arg);
|
|
2462
|
+
return;
|
|
2463
|
+
case "login": {
|
|
2464
|
+
const start = parseLoginArgs(arg);
|
|
2465
|
+
if ("error" in start) session.dispatch({ type: "notice", tone: "error", text: start.error });
|
|
2466
|
+
else if (!services.session || !onAccountChange) {
|
|
2467
|
+
session.dispatch({ type: "notice", tone: "error", text: "Signing in is not available here", hint: "Run `tabbio login` in a terminal." });
|
|
2468
|
+
} else openLogin(start);
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
case "logout": {
|
|
2472
|
+
const controls = services.session;
|
|
2473
|
+
if (!controls || !onAccountChange) {
|
|
2474
|
+
session.dispatch({ type: "notice", tone: "error", text: "Signing out is not available here", hint: "Run `tabbio logout` in a terminal." });
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
session.interrupt();
|
|
2478
|
+
session.dispatch({ type: "notice", tone: "info", text: `Signing out${theme.symbols.ellipsis}` });
|
|
2479
|
+
controls.logout().then(onAccountChange, (error) => {
|
|
2480
|
+
const cli = toCliError(error);
|
|
2481
|
+
session.dispatch({ type: "notice", tone: "error", text: `Could not sign out: ${cli.message}`, hint: cli.hint });
|
|
2482
|
+
});
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
1639
2485
|
case "home":
|
|
1640
2486
|
onExit(hostedByHome ? "home" : "exit");
|
|
1641
2487
|
return;
|
|
@@ -1647,24 +2493,83 @@ function useChatCommands(deps) {
|
|
|
1647
2493
|
session.dispatch({ type: "notice", tone: "error", text: `Unknown command /${name}`, hint: "Type / to see the commands." });
|
|
1648
2494
|
}
|
|
1649
2495
|
},
|
|
1650
|
-
[session, state.mode, state.modelId, openThreads, onExit, hostedByHome, services, companyId, setCapability]
|
|
2496
|
+
[session, state.mode, state.modelId, openThreads, onExit, hostedByHome, services, companyId, setCapability, runFileCommand, openLogin, onAccountChange]
|
|
1651
2497
|
);
|
|
1652
2498
|
}
|
|
1653
2499
|
|
|
1654
2500
|
// src/ui/screens/chat/useChatSession.ts
|
|
1655
|
-
import { useCallback as
|
|
2501
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useReducer as useReducer3, useRef as useRef6, useState as useState4 } from "react";
|
|
2502
|
+
|
|
2503
|
+
// src/ui/screens/chat/useArtifactFollowUp.ts
|
|
2504
|
+
import { useEffect as useEffect6, useRef as useRef5 } from "react";
|
|
2505
|
+
|
|
2506
|
+
// src/ui/screens/chat/useAutoSave.ts
|
|
2507
|
+
import { useEffect as useEffect5, useMemo as useMemo4 } from "react";
|
|
2508
|
+
var PREVIEW_LINES = 12;
|
|
2509
|
+
async function previewOf(services, files) {
|
|
2510
|
+
const text2 = files.find((file) => isTextFile(file.path));
|
|
2511
|
+
if (!text2) return void 0;
|
|
2512
|
+
try {
|
|
2513
|
+
const read = services.readSavedPreview ?? readSavedPreview;
|
|
2514
|
+
const { lines, total } = await read(text2.path, PREVIEW_LINES);
|
|
2515
|
+
return { path: text2.path, lines, total };
|
|
2516
|
+
} catch {
|
|
2517
|
+
return void 0;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
async function save(services, target, signal) {
|
|
2521
|
+
if (target.type === "artifact") {
|
|
2522
|
+
const base2 = { artifactId: target.artifactId, title: target.title, kind: target.kind };
|
|
2523
|
+
try {
|
|
2524
|
+
if (!services.saveArtifact) return { ...base2, files: [] };
|
|
2525
|
+
const files = await services.saveArtifact(target.artifactId, { versionId: target.versionId, signal });
|
|
2526
|
+
if (signal.aborted) return { ...base2, files: [] };
|
|
2527
|
+
const preview = await previewOf(services, files);
|
|
2528
|
+
return { ...base2, files, ...preview ? { preview } : {} };
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
return { ...base2, files: [], error: toCliError(error).message };
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
const base = { cvId: target.cvId, title: target.title, kind: "cv" };
|
|
2534
|
+
try {
|
|
2535
|
+
if (!services.saveCvExport) return { ...base, files: [] };
|
|
2536
|
+
const file = await services.saveCvExport(target.cvId, { format: target.format, signal });
|
|
2537
|
+
if (signal.aborted) return { ...base, files: [] };
|
|
2538
|
+
return { ...base, files: [file] };
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
return { ...base, files: [], error: toCliError(error).message };
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
function useAutoSave(state, dispatch, services, enabled) {
|
|
2544
|
+
const started = useMemo4(() => /* @__PURE__ */ new Set(), [services]);
|
|
2545
|
+
const controller = useMemo4(() => new AbortController(), [services]);
|
|
2546
|
+
useEffect5(() => () => controller.abort(), [controller]);
|
|
2547
|
+
const targets = enabled && (services.saveArtifact || services.saveCvExport) ? saveTargets(state) : [];
|
|
2548
|
+
const fresh = targets.filter((target) => !started.has(target.key));
|
|
2549
|
+
const key = fresh.map((target) => target.key).join("|");
|
|
2550
|
+
useEffect5(() => {
|
|
2551
|
+
for (const target of fresh) {
|
|
2552
|
+
if (started.has(target.key)) continue;
|
|
2553
|
+
started.add(target.key);
|
|
2554
|
+
void save(services, target, controller.signal).then((saved) => {
|
|
2555
|
+
if (controller.signal.aborted || !saved.files.length && !saved.error) return;
|
|
2556
|
+
dispatch({ type: "files-saved", ...target.toolCallId ? { toolCallId: target.toolCallId } : {}, saved });
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
}, [key, dispatch, services, started, controller]);
|
|
2560
|
+
}
|
|
1656
2561
|
|
|
1657
2562
|
// src/ui/screens/chat/useArtifactFollowUp.ts
|
|
1658
|
-
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
1659
2563
|
var FOLLOW_TIMEOUT_MS = 5 * 6e4;
|
|
1660
2564
|
function liveArtifacts(state) {
|
|
1661
2565
|
const items = state.turn?.items ?? [];
|
|
1662
2566
|
return items.flatMap((item) => item.kind === "tool" && item.call.artifact ? [item.call.artifact] : []);
|
|
1663
2567
|
}
|
|
1664
|
-
function useArtifactFollowUp(state, dispatch, services) {
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
|
|
2568
|
+
function useArtifactFollowUp(state, dispatch, services, opts = {}) {
|
|
2569
|
+
useAutoSave(state, dispatch, services, opts.save !== false && !autoSaveDisabled());
|
|
2570
|
+
const followed = useRef5(/* @__PURE__ */ new Map());
|
|
2571
|
+
const alive = useRef5(true);
|
|
2572
|
+
useEffect6(
|
|
1668
2573
|
() => () => {
|
|
1669
2574
|
alive.current = false;
|
|
1670
2575
|
},
|
|
@@ -1672,7 +2577,7 @@ function useArtifactFollowUp(state, dispatch, services) {
|
|
|
1672
2577
|
);
|
|
1673
2578
|
const cards = liveArtifacts(state);
|
|
1674
2579
|
const key = cards.map((c) => `${c.id}:${c.status}:${c.linkPending ? 1 : 0}:${c.released ? 1 : 0}`).join("|");
|
|
1675
|
-
|
|
2580
|
+
useEffect6(() => {
|
|
1676
2581
|
const send = (action) => {
|
|
1677
2582
|
if (alive.current) dispatch(action);
|
|
1678
2583
|
};
|
|
@@ -1707,6 +2612,7 @@ function useArtifactFollowUp(state, dispatch, services) {
|
|
|
1707
2612
|
artifactId: card.id,
|
|
1708
2613
|
status: artifact.status,
|
|
1709
2614
|
title: artifact.title,
|
|
2615
|
+
...!card.versionId && artifact.currentVersion?.id ? { versionId: artifact.currentVersion.id, versionNumber: artifact.currentVersion.number } : {},
|
|
1710
2616
|
...url ? { previewUrl: url } : {},
|
|
1711
2617
|
...artifact.error ? { failure: artifact.error } : {}
|
|
1712
2618
|
});
|
|
@@ -1725,9 +2631,18 @@ function useArtifactFollowUp(state, dispatch, services) {
|
|
|
1725
2631
|
// src/ui/screens/chat/useChatSession.ts
|
|
1726
2632
|
var FLUSH_INTERVAL_MS = 50;
|
|
1727
2633
|
var HISTORY_LIMIT = 20;
|
|
2634
|
+
function isRecord4(value) {
|
|
2635
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2636
|
+
}
|
|
2637
|
+
function resultFailure(result) {
|
|
2638
|
+
if (!isRecord4(result) || result.ok !== false) return null;
|
|
2639
|
+
const error = isRecord4(result.error) ? result.error : {};
|
|
2640
|
+
const text2 = typeof error.message === "string" ? error.message : typeof result.message === "string" ? result.message : "The tool refused this request";
|
|
2641
|
+
return { tone: "error", text: text2 };
|
|
2642
|
+
}
|
|
1728
2643
|
function useChatSession(opts) {
|
|
1729
2644
|
const { services, companyId } = opts;
|
|
1730
|
-
const [state, dispatch] =
|
|
2645
|
+
const [state, dispatch] = useReducer3(
|
|
1731
2646
|
chatReducer,
|
|
1732
2647
|
opts,
|
|
1733
2648
|
(o) => initialChatState({
|
|
@@ -1735,24 +2650,25 @@ function useChatSession(opts) {
|
|
|
1735
2650
|
threadId: o.threadId,
|
|
1736
2651
|
modelId: o.modelId,
|
|
1737
2652
|
capabilities: o.capabilities ?? o.services.capabilityPrefs ?? DEFAULT_CAPABILITIES,
|
|
1738
|
-
links: Boolean(o.services.hyperlinks)
|
|
2653
|
+
links: Boolean(o.services.hyperlinks),
|
|
2654
|
+
appUrl: o.services.appUrl
|
|
1739
2655
|
})
|
|
1740
2656
|
);
|
|
1741
|
-
const stateRef =
|
|
2657
|
+
const stateRef = useRef6(state);
|
|
1742
2658
|
stateRef.current = state;
|
|
1743
|
-
const buffer =
|
|
1744
|
-
const abortRef =
|
|
1745
|
-
const mounted =
|
|
1746
|
-
const [queue, setQueue] =
|
|
1747
|
-
const [deciding, setDeciding] =
|
|
1748
|
-
const [loading, setLoading] =
|
|
2659
|
+
const buffer = useRef6([]);
|
|
2660
|
+
const abortRef = useRef6(null);
|
|
2661
|
+
const mounted = useRef6(true);
|
|
2662
|
+
const [queue, setQueue] = useState4([]);
|
|
2663
|
+
const [deciding, setDeciding] = useState4(null);
|
|
2664
|
+
const [loading, setLoading] = useState4(
|
|
1749
2665
|
opts.threadId ? { label: "Loading the thread\u2026", since: Date.now() } : null
|
|
1750
2666
|
);
|
|
1751
|
-
const flush =
|
|
2667
|
+
const flush = useCallback4(() => {
|
|
1752
2668
|
const events = buffer.current.splice(0);
|
|
1753
2669
|
for (const event of events) dispatch(event);
|
|
1754
2670
|
}, []);
|
|
1755
|
-
|
|
2671
|
+
useEffect7(() => {
|
|
1756
2672
|
if (!state.streaming) return void 0;
|
|
1757
2673
|
const id = setInterval(() => {
|
|
1758
2674
|
flush();
|
|
@@ -1760,16 +2676,16 @@ function useChatSession(opts) {
|
|
|
1760
2676
|
}, FLUSH_INTERVAL_MS);
|
|
1761
2677
|
return () => clearInterval(id);
|
|
1762
2678
|
}, [state.streaming, flush]);
|
|
1763
|
-
|
|
2679
|
+
useEffect7(
|
|
1764
2680
|
() => () => {
|
|
1765
2681
|
mounted.current = false;
|
|
1766
2682
|
abortRef.current?.abort();
|
|
1767
2683
|
},
|
|
1768
2684
|
[]
|
|
1769
2685
|
);
|
|
1770
|
-
const send =
|
|
1771
|
-
(
|
|
1772
|
-
const message =
|
|
2686
|
+
const send = useCallback4(
|
|
2687
|
+
(text2) => {
|
|
2688
|
+
const message = text2.trim();
|
|
1773
2689
|
if (!message) return;
|
|
1774
2690
|
if (stateRef.current.streaming) {
|
|
1775
2691
|
setQueue((q) => [...q, message]);
|
|
@@ -1806,18 +2722,72 @@ function useChatSession(opts) {
|
|
|
1806
2722
|
},
|
|
1807
2723
|
[services, companyId, flush]
|
|
1808
2724
|
);
|
|
1809
|
-
const
|
|
2725
|
+
const runTool = useCallback4(
|
|
2726
|
+
(spec) => {
|
|
2727
|
+
if (stateRef.current.streaming) return false;
|
|
2728
|
+
const started = Date.now();
|
|
2729
|
+
dispatch({ type: "submit", text: spec.text, at: started });
|
|
2730
|
+
const controller = new AbortController();
|
|
2731
|
+
abortRef.current = controller;
|
|
2732
|
+
const payload = {
|
|
2733
|
+
id: `direct-${started}`,
|
|
2734
|
+
name: spec.name,
|
|
2735
|
+
args: spec.args,
|
|
2736
|
+
display: { title: spec.title },
|
|
2737
|
+
...spec.cardType ? { cardType: spec.cardType } : {}
|
|
2738
|
+
};
|
|
2739
|
+
dispatch({ type: "tool-call", payload: { ...payload, status: "running", stage: "running" }, at: started });
|
|
2740
|
+
const settle = (event, notes) => {
|
|
2741
|
+
if (!mounted.current) return;
|
|
2742
|
+
const at = Date.now();
|
|
2743
|
+
dispatch({ type: "tool-result", payload: event, at });
|
|
2744
|
+
dispatch({ type: "finish", payload: { finishReason: "stop" }, at });
|
|
2745
|
+
for (const note of notes) dispatch({ type: "notice", ...note });
|
|
2746
|
+
};
|
|
2747
|
+
void (async () => {
|
|
2748
|
+
try {
|
|
2749
|
+
const outcome = await spec.run(controller.signal);
|
|
2750
|
+
if (controller.signal.aborted) throw new Error("aborted");
|
|
2751
|
+
if (!outcome.ok) {
|
|
2752
|
+
const failed = { ...payload, status: "failed", stage: "failed", result: { ok: false, error: { code: outcome.error.code, message: outcome.error.message } } };
|
|
2753
|
+
settle(failed, [{ tone: "error", text: outcome.error.message, ...outcome.error.hint ? { hint: outcome.error.hint } : {} }]);
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
if (outcome.approval) {
|
|
2757
|
+
const waiting = { ...payload, status: "failed", stage: "failed", result: { ok: false, error: { code: "APPROVAL_REQUIRED", message: outcome.approval.message } } };
|
|
2758
|
+
const hint = `Approve it with \`tabbio approvals approve ${outcome.approval.approvalId}\`, in /approvals on the home screen, or in the app.`;
|
|
2759
|
+
settle(waiting, [{ tone: "attention", text: outcome.approval.message, hint }]);
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
const refused = resultFailure(outcome.result);
|
|
2763
|
+
const completed = { ...payload, status: refused ? "failed" : "completed", stage: refused ? "failed" : "completed", result: outcome.result };
|
|
2764
|
+
settle(completed, refused ? [refused] : spec.after?.(outcome.result) ?? []);
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
const cancelled = controller.signal.aborted || isCliError(error) && error.exitCode === 130;
|
|
2767
|
+
const cli = toCliError(error);
|
|
2768
|
+
const failed = { ...payload, status: "failed", stage: "failed", result: { ok: false, error: { code: cli.code, message: cancelled ? "Stopped" : cli.message } } };
|
|
2769
|
+
const note = cancelled ? { tone: "attention", text: "Stopped" } : { tone: "error", text: cli.message, ...cli.hint ? { hint: cli.hint } : {} };
|
|
2770
|
+
settle(failed, [note]);
|
|
2771
|
+
} finally {
|
|
2772
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
2773
|
+
}
|
|
2774
|
+
})();
|
|
2775
|
+
return true;
|
|
2776
|
+
},
|
|
2777
|
+
[]
|
|
2778
|
+
);
|
|
2779
|
+
const interrupt = useCallback4(() => {
|
|
1810
2780
|
if (!abortRef.current) return false;
|
|
1811
2781
|
abortRef.current.abort();
|
|
1812
2782
|
return true;
|
|
1813
2783
|
}, []);
|
|
1814
|
-
|
|
2784
|
+
useEffect7(() => {
|
|
1815
2785
|
if (state.streaming || state.approvals.length > 0 || queue.length === 0) return;
|
|
1816
2786
|
const [next, ...rest] = queue;
|
|
1817
2787
|
setQueue(rest);
|
|
1818
2788
|
if (next) send(next);
|
|
1819
2789
|
}, [state.streaming, state.approvals.length, queue, send]);
|
|
1820
|
-
const decide =
|
|
2790
|
+
const decide = useCallback4(
|
|
1821
2791
|
async (approval, decision) => {
|
|
1822
2792
|
if (decision === "leave") {
|
|
1823
2793
|
dispatch({ type: "approval-decided", approvalId: approval.approvalId, decision, at: Date.now() });
|
|
@@ -1844,7 +2814,7 @@ function useChatSession(opts) {
|
|
|
1844
2814
|
},
|
|
1845
2815
|
[services, companyId]
|
|
1846
2816
|
);
|
|
1847
|
-
const resume =
|
|
2817
|
+
const resume = useCallback4(
|
|
1848
2818
|
async (threadId, title) => {
|
|
1849
2819
|
setLoading({ label: "Loading the thread\u2026", since: Date.now() });
|
|
1850
2820
|
try {
|
|
@@ -1863,8 +2833,8 @@ function useChatSession(opts) {
|
|
|
1863
2833
|
},
|
|
1864
2834
|
[services, companyId]
|
|
1865
2835
|
);
|
|
1866
|
-
const booted =
|
|
1867
|
-
|
|
2836
|
+
const booted = useRef6(false);
|
|
2837
|
+
useEffect7(() => {
|
|
1868
2838
|
if (booted.current) return;
|
|
1869
2839
|
booted.current = true;
|
|
1870
2840
|
void (async () => {
|
|
@@ -1872,97 +2842,112 @@ function useChatSession(opts) {
|
|
|
1872
2842
|
if (opts.initialMessage && mounted.current) send(opts.initialMessage);
|
|
1873
2843
|
})();
|
|
1874
2844
|
}, [opts.threadId, opts.initialMessage, resume, send]);
|
|
1875
|
-
|
|
1876
|
-
|
|
2845
|
+
const saveArtifacts = opts.saveArtifacts !== false;
|
|
2846
|
+
useArtifactFollowUp(state, dispatch, services, { save: saveArtifacts });
|
|
2847
|
+
return { state, dispatch, send, runTool, interrupt, decide, deciding, queue, setQueue, resume, loading, saveArtifacts };
|
|
1877
2848
|
}
|
|
1878
2849
|
|
|
1879
2850
|
// src/ui/screens/chat/Chat.tsx
|
|
1880
|
-
import { jsx as
|
|
2851
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1881
2852
|
var DOUBLE_PRESS_MS = 2e3;
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
const hints = props.approval ? [
|
|
1886
|
-
{ key: "y", label: "approve" },
|
|
1887
|
-
{ key: "n", label: "reject" },
|
|
1888
|
-
{ key: "l", label: "later" }
|
|
1889
|
-
] : props.streaming ? [
|
|
1890
|
-
{ key: "enter", label: "queue" },
|
|
1891
|
-
{ key: "esc", label: "interrupt" },
|
|
1892
|
-
{ key: "/", label: "commands" }
|
|
1893
|
-
] : [
|
|
1894
|
-
{ key: "enter", label: "send" },
|
|
1895
|
-
{ key: "/", label: "commands" },
|
|
1896
|
-
{ key: "ctrl+c", label: "quit" }
|
|
1897
|
-
];
|
|
1898
|
-
return /* @__PURE__ */ jsx16(StatusBar, { width: props.width, segments: [...props.segments, elapsed], hints });
|
|
1899
|
-
});
|
|
2853
|
+
function saveHintText(dir = artifactsDir()) {
|
|
2854
|
+
return `Generated files are saved to ${displayPath(dir)} (/help for /save, /show, /open)`;
|
|
2855
|
+
}
|
|
1900
2856
|
function Chat(props) {
|
|
1901
|
-
const { services, width, height, hostedByHome = false, onExit } = props;
|
|
2857
|
+
const { services, width, height, hostedByHome = false, onExit, onAccountChange } = props;
|
|
1902
2858
|
const gutter = pageMargin(useTerminalSize().columns);
|
|
1903
2859
|
const session = useChatSession(props);
|
|
1904
2860
|
const { state } = session;
|
|
1905
|
-
const [editor, dispatchEditor] =
|
|
1906
|
-
const [paletteIndex, setPaletteIndex] =
|
|
1907
|
-
const [
|
|
1908
|
-
const [
|
|
1909
|
-
const [
|
|
1910
|
-
const
|
|
1911
|
-
const
|
|
1912
|
-
const
|
|
1913
|
-
const
|
|
1914
|
-
const
|
|
1915
|
-
const
|
|
2861
|
+
const [editor, dispatchEditor] = useReducer4(editorReducer, emptyEditor);
|
|
2862
|
+
const [paletteIndex, setPaletteIndex] = useState5(0);
|
|
2863
|
+
const [navigated, setNavigated] = useState5(false);
|
|
2864
|
+
const [overlay, setOverlay] = useState5(null);
|
|
2865
|
+
const [expandTools, setExpandTools] = useState5(false);
|
|
2866
|
+
const [hint, setHint] = useState5(null);
|
|
2867
|
+
const lastCtrlC = useRef7(0);
|
|
2868
|
+
const lastEsc = useRef7(0);
|
|
2869
|
+
const commands = useMemo5(() => chatCommands({ hostedByHome }), [hostedByHome]);
|
|
2870
|
+
const skills = useSkills(services, services.account.hasSession && editor.value.startsWith("/"));
|
|
2871
|
+
const view = overlay ? null : paletteView(commands, skills, editor.value, { ellipsis: theme.symbols.ellipsis, separator: theme.symbols.middot });
|
|
2872
|
+
const matches = view?.items ?? [];
|
|
1916
2873
|
const selected = Math.min(paletteIndex, Math.max(0, matches.length - 1));
|
|
1917
2874
|
const approval = !state.streaming ? state.approvals[0] : void 0;
|
|
1918
2875
|
const composerActive = !overlay && !approval;
|
|
1919
|
-
const
|
|
2876
|
+
const firstTurn = !state.entries.some((e) => e.kind === "user") && !state.turn;
|
|
2877
|
+
const saveHint = session.saveArtifacts && firstTurn ? saveHintText() : null;
|
|
2878
|
+
const [header] = useState5(() => ({
|
|
1920
2879
|
kind: "header",
|
|
1921
2880
|
id: "chat-header",
|
|
1922
2881
|
text: `${theme.bold("Chat")} ${theme.dim(`${theme.symbols.middot} ${props.mode}${props.threadId ? ` ${theme.symbols.middot} resumed` : ""}`)}`,
|
|
1923
2882
|
right: `profile ${services.profileName}`
|
|
1924
2883
|
}));
|
|
1925
|
-
const staticItems =
|
|
1926
|
-
|
|
2884
|
+
const staticItems = useMemo5(() => [header, ...state.entries], [header, state.entries]);
|
|
2885
|
+
useEffect8(() => {
|
|
1927
2886
|
if (!hint) return void 0;
|
|
1928
2887
|
const timer = setTimeout(() => setHint(null), DOUBLE_PRESS_MS);
|
|
1929
2888
|
return () => clearTimeout(timer);
|
|
1930
2889
|
}, [hint]);
|
|
1931
|
-
|
|
2890
|
+
useEffect8(() => setNavigated(false), [editor.value]);
|
|
2891
|
+
const openThreads = useCallback5(() => {
|
|
1932
2892
|
setOverlay({ kind: "threads", since: Date.now() });
|
|
1933
|
-
services.listThreads({ mode: state.mode, companyId: props.companyId }).then((threads) => setOverlay((o) => o ? { ...o, threads } : o)).catch((error) => setOverlay((o) => o ? { ...o, error: toCliError(error).message } : o));
|
|
2893
|
+
services.listThreads({ mode: state.mode, companyId: props.companyId }).then((threads) => setOverlay((o) => o?.kind === "threads" ? { ...o, threads } : o)).catch((error) => setOverlay((o) => o?.kind === "threads" ? { ...o, error: toCliError(error).message } : o));
|
|
1934
2894
|
}, [services, state.mode, props.companyId]);
|
|
1935
|
-
const
|
|
1936
|
-
const
|
|
2895
|
+
const openLogin = useCallback5((start) => setOverlay({ kind: "login", start }), []);
|
|
2896
|
+
const onLoginDone = useCallback5(
|
|
2897
|
+
(outcome) => {
|
|
2898
|
+
setOverlay(null);
|
|
2899
|
+
if (outcome.status === "done") {
|
|
2900
|
+
onAccountChange?.(outcome.change);
|
|
2901
|
+
session.dispatch({ type: "notice", tone: "success", text: outcome.change.message });
|
|
2902
|
+
for (const warning of outcome.change.warnings ?? []) session.dispatch({ type: "notice", tone: "attention", text: warning });
|
|
2903
|
+
} else if (outcome.status === "cancelled") session.dispatch({ type: "notice", tone: "info", text: "Sign-in cancelled" });
|
|
2904
|
+
else session.dispatch({ type: "notice", tone: "error", text: outcome.message, hint: outcome.hint });
|
|
2905
|
+
},
|
|
2906
|
+
[onAccountChange, session]
|
|
2907
|
+
);
|
|
2908
|
+
const runCommand = useChatCommands({ session, services, companyId: props.companyId, hostedByHome, onExit, openThreads, openLogin, onAccountChange });
|
|
2909
|
+
const submit2 = useCallback5(
|
|
1937
2910
|
(value) => {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
let arg = exact ? slash.arg : "";
|
|
1951
|
-
const known = exact?.args?.some((a) => a.value === arg.toLowerCase());
|
|
1952
|
-
if (exact?.args && !known && picked) arg = parseSlash(picked)?.arg ?? arg;
|
|
1953
|
-
dispatchEditor({ type: "remember", value: text });
|
|
2911
|
+
const argRows2 = view?.args ? matches : [];
|
|
2912
|
+
const res = resolveSubmit(value, { commands, skills, highlighted: view?.args ? void 0 : matches[selected], navigated });
|
|
2913
|
+
if (res.kind === "empty" || res.kind === "bare") return;
|
|
2914
|
+
if (res.kind === "loading") {
|
|
2915
|
+
setHint("Skills are still loading. Press enter again in a moment.");
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
const text2 = value.trim();
|
|
2919
|
+
const done = () => {
|
|
2920
|
+
dispatchEditor({ type: "remember", value: text2 });
|
|
1954
2921
|
dispatchEditor({ type: "clear" });
|
|
1955
2922
|
setPaletteIndex(0);
|
|
1956
|
-
|
|
2923
|
+
};
|
|
2924
|
+
if (res.kind === "message" || res.kind === "skill") {
|
|
2925
|
+
done();
|
|
2926
|
+
session.send(res.kind === "skill" ? res.message : res.text);
|
|
1957
2927
|
return;
|
|
1958
2928
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2929
|
+
if (res.kind === "unknown") {
|
|
2930
|
+
done();
|
|
2931
|
+
session.dispatch({ type: "notice", tone: "error", text: `Unknown command /${res.name}`, hint: "Type / to see the commands and your skills." });
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
const exact = res.picked ? void 0 : commands.find((c) => c.name === res.name);
|
|
2935
|
+
const choice = exact ?? commands.find((c) => c.name === res.name);
|
|
2936
|
+
const picked = exact?.args ? argRows2[selected]?.completion : void 0;
|
|
2937
|
+
if (choice?.args && !res.arg && !picked) {
|
|
2938
|
+
dispatchEditor({ type: "set", value: `/${choice.name} ` });
|
|
2939
|
+
setPaletteIndex(0);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
let arg = exact ? res.arg : "";
|
|
2943
|
+
const known = exact?.args?.some((a) => a.value === arg.toLowerCase());
|
|
2944
|
+
if (exact?.args && !known && picked) arg = parseSlash(picked)?.arg ?? arg;
|
|
2945
|
+
done();
|
|
2946
|
+
runCommand(res.name, arg);
|
|
1962
2947
|
},
|
|
1963
|
-
[
|
|
2948
|
+
[view, matches, selected, commands, skills, navigated, runCommand, session]
|
|
1964
2949
|
);
|
|
1965
|
-
const onCtrlC =
|
|
2950
|
+
const onCtrlC = useCallback5(() => {
|
|
1966
2951
|
const now = Date.now();
|
|
1967
2952
|
if (now - lastCtrlC.current < DOUBLE_PRESS_MS) {
|
|
1968
2953
|
session.interrupt();
|
|
@@ -1976,21 +2961,22 @@ function Chat(props) {
|
|
|
1976
2961
|
setHint("Press ctrl+c again to quit.");
|
|
1977
2962
|
}
|
|
1978
2963
|
}, [session, onExit, editor.value]);
|
|
1979
|
-
|
|
2964
|
+
useInput7((input, key) => {
|
|
1980
2965
|
if (key.ctrl && input === "c") onCtrlC();
|
|
1981
2966
|
});
|
|
1982
|
-
const onComposerKey =
|
|
2967
|
+
const onComposerKey = useCallback5(
|
|
1983
2968
|
(input, key) => {
|
|
1984
2969
|
if (key.ctrl && input === "c") return true;
|
|
1985
2970
|
if (matches.length > 0) {
|
|
1986
2971
|
if (key.upArrow || key.downArrow) {
|
|
1987
2972
|
setPaletteIndex((i) => key.upArrow ? Math.max(0, i - 1) : Math.min(matches.length - 1, i + 1));
|
|
2973
|
+
setNavigated(true);
|
|
1988
2974
|
return true;
|
|
1989
2975
|
}
|
|
1990
2976
|
if (key.tab) {
|
|
1991
2977
|
const choice = matches[selected];
|
|
1992
2978
|
if (choice?.completion) dispatchEditor({ type: "set", value: choice.completion });
|
|
1993
|
-
else if (choice) dispatchEditor({ type: "set", value: `/${choice.name}${choice.usage ? " " : ""}` });
|
|
2979
|
+
else if (choice) dispatchEditor({ type: "set", value: `/${choice.name}${choice.usage || choice.skillId ? " " : ""}` });
|
|
1994
2980
|
setPaletteIndex(0);
|
|
1995
2981
|
return true;
|
|
1996
2982
|
}
|
|
@@ -2001,7 +2987,7 @@ function Chat(props) {
|
|
|
2001
2987
|
if (!editor.value) {
|
|
2002
2988
|
if (hostedByHome) onExit("home");
|
|
2003
2989
|
else setHint("Type /exit or press ctrl+c twice to quit.");
|
|
2004
|
-
} else if (
|
|
2990
|
+
} else if (view?.open || now - lastEsc.current < 600) dispatchEditor({ type: "clear" });
|
|
2005
2991
|
lastEsc.current = now;
|
|
2006
2992
|
return true;
|
|
2007
2993
|
}
|
|
@@ -2017,10 +3003,12 @@ function Chat(props) {
|
|
|
2017
3003
|
}
|
|
2018
3004
|
return false;
|
|
2019
3005
|
},
|
|
2020
|
-
[matches, selected, session, editor.value, state.streaming, state.mode, state.turn, runCommand, hostedByHome, onExit]
|
|
3006
|
+
[matches, selected, view, session, editor.value, state.streaming, state.mode, state.turn, runCommand, hostedByHome, onExit]
|
|
2021
3007
|
);
|
|
2022
3008
|
const cardRows = approval ? 14 + Math.min(9, Object.keys(approval.args).length) : 0;
|
|
2023
|
-
const
|
|
3009
|
+
const paletteRows = view?.open ? Math.min(6, matches.length) + 2 : 0;
|
|
3010
|
+
const saveHintRows = saveHint ? Math.ceil(saveHint.length / Math.max(20, width)) : 0;
|
|
3011
|
+
const fixedRows = 4 + 1 + (hint ? 1 : 0) + saveHintRows + session.queue.length + paletteRows + (overlay ? 12 : 0) + cardRows + 2;
|
|
2024
3012
|
const liveBudget = Math.max(3, height - fixedRows - 1);
|
|
2025
3013
|
const placeholder = approval ? "Decide the approval above first" : state.streaming ? `Queue a follow-up${theme.symbols.ellipsis} (esc to interrupt)` : "Ask Tabbio anything, or / for commands";
|
|
2026
3014
|
const segments = [
|
|
@@ -2030,11 +3018,11 @@ function Chat(props) {
|
|
|
2030
3018
|
...capabilitySegments(state.mode, state.capabilities),
|
|
2031
3019
|
state.threadId ? shortId(state.threadId, 6, 3) : "new chat"
|
|
2032
3020
|
];
|
|
2033
|
-
return /* @__PURE__ */
|
|
2034
|
-
/* @__PURE__ */
|
|
2035
|
-
state.turn ? /* @__PURE__ */
|
|
2036
|
-
session.loading ? /* @__PURE__ */
|
|
2037
|
-
approval ? /* @__PURE__ */
|
|
3021
|
+
return /* @__PURE__ */ jsxs15(Box17, { flexDirection: "column", width, children: [
|
|
3022
|
+
/* @__PURE__ */ jsx19(Static, { items: staticItems, children: (item) => /* @__PURE__ */ jsx19(Box17, { paddingLeft: gutter, children: /* @__PURE__ */ jsx19(EntryView, { entry: item, width }) }, item.id) }),
|
|
3023
|
+
state.turn ? /* @__PURE__ */ jsx19(LiveTurn, { turn: state.turn, width, expandTools, maxLines: liveBudget, streaming: state.streaming }) : null,
|
|
3024
|
+
session.loading ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(Working, { label: session.loading.label, since: session.loading.since }) }) : null,
|
|
3025
|
+
approval ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(
|
|
2038
3026
|
ApprovalCard,
|
|
2039
3027
|
{
|
|
2040
3028
|
approvalId: approval.approvalId,
|
|
@@ -2048,9 +3036,10 @@ function Chat(props) {
|
|
|
2048
3036
|
onDecision: (decision) => void session.decide(approval, decision)
|
|
2049
3037
|
}
|
|
2050
3038
|
) }) : null,
|
|
2051
|
-
overlay ? /* @__PURE__ */
|
|
2052
|
-
|
|
2053
|
-
|
|
3039
|
+
overlay?.kind === "login" ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(LoginPanel, { services, start: overlay.start, width, onDone: onLoginDone }) }) : null,
|
|
3040
|
+
overlay?.kind === "threads" ? /* @__PURE__ */ jsxs15(Box17, { flexDirection: "column", marginTop: 1, children: [
|
|
3041
|
+
/* @__PURE__ */ jsx19(Text17, { children: theme.bold("Recent chats") }),
|
|
3042
|
+
overlay.error ? /* @__PURE__ */ jsx19(Text17, { children: theme.error(`${theme.symbols.error} ${overlay.error}`) }) : !overlay.threads ? /* @__PURE__ */ jsx19(Working, { label: "Loading chats\u2026", since: overlay.since }) : /* @__PURE__ */ jsx19(
|
|
2054
3043
|
SelectList,
|
|
2055
3044
|
{
|
|
2056
3045
|
items: overlay.threads.map((t) => ({
|
|
@@ -2069,11 +3058,11 @@ function Chat(props) {
|
|
|
2069
3058
|
onCancel: () => setOverlay(null)
|
|
2070
3059
|
}
|
|
2071
3060
|
),
|
|
2072
|
-
overlay.error || !overlay.threads ? /* @__PURE__ */
|
|
3061
|
+
overlay.error || !overlay.threads ? /* @__PURE__ */ jsx19(SelectEscape, { onClose: () => setOverlay(null) }) : null
|
|
2073
3062
|
] }) : null,
|
|
2074
|
-
session.queue.map((queued, i) => /* @__PURE__ */
|
|
2075
|
-
|
|
2076
|
-
/* @__PURE__ */
|
|
3063
|
+
session.queue.map((queued, i) => /* @__PURE__ */ jsx19(Text17, { wrap: "truncate-end", children: theme.dim(`${theme.symbols.arrowRight} queued: ${queued}`) }, `q-${i}`)),
|
|
3064
|
+
view?.open ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(Palette, { commands: matches, selected, width, footer: view.footer }) }) : null,
|
|
3065
|
+
/* @__PURE__ */ jsx19(Box17, { marginTop: view?.open ? 0 : 1, children: /* @__PURE__ */ jsx19(
|
|
2077
3066
|
TextInput,
|
|
2078
3067
|
{
|
|
2079
3068
|
editor,
|
|
@@ -2086,45 +3075,233 @@ function Chat(props) {
|
|
|
2086
3075
|
boxed: true
|
|
2087
3076
|
}
|
|
2088
3077
|
) }),
|
|
2089
|
-
hint ? /* @__PURE__ */
|
|
2090
|
-
/* @__PURE__ */
|
|
3078
|
+
hint ? /* @__PURE__ */ jsx19(Text17, { children: theme.attention(hint) }) : null,
|
|
3079
|
+
saveHint && !hint ? /* @__PURE__ */ jsx19(Text17, { wrap: "wrap", children: theme.dim(saveHint) }) : null,
|
|
3080
|
+
/* @__PURE__ */ jsx19(
|
|
2091
3081
|
ChatStatus,
|
|
2092
3082
|
{
|
|
2093
3083
|
width,
|
|
2094
3084
|
segments,
|
|
2095
3085
|
since: state.streaming && state.turn ? state.turn.startedAt : null,
|
|
2096
3086
|
streaming: state.streaming,
|
|
2097
|
-
approval: Boolean(approval)
|
|
3087
|
+
approval: Boolean(approval),
|
|
3088
|
+
overlay: Boolean(overlay)
|
|
2098
3089
|
}
|
|
2099
3090
|
)
|
|
2100
3091
|
] });
|
|
2101
3092
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
3093
|
+
|
|
3094
|
+
// src/ui/screens/home/Home.tsx
|
|
3095
|
+
import { Box as Box26, Text as Text26, useInput as useInput12 } from "ink";
|
|
3096
|
+
import { useCallback as useCallback8, useEffect as useEffect11, useMemo as useMemo8, useReducer as useReducer5, useRef as useRef10, useState as useState10 } from "react";
|
|
3097
|
+
|
|
3098
|
+
// src/ui/session-controls.ts
|
|
3099
|
+
import open from "open";
|
|
3100
|
+
var CATALOG_RELOAD_MS = 8e3;
|
|
3101
|
+
var created = /* @__PURE__ */ new Set();
|
|
3102
|
+
async function closeUiContexts() {
|
|
3103
|
+
const all = [...created];
|
|
3104
|
+
created.clear();
|
|
3105
|
+
await Promise.all(all.map((ctx) => ctx.close().catch(() => void 0)));
|
|
3106
|
+
}
|
|
3107
|
+
async function openInBrowser(url) {
|
|
3108
|
+
if (!hasDisplay()) return false;
|
|
3109
|
+
try {
|
|
3110
|
+
await open(url);
|
|
3111
|
+
return true;
|
|
3112
|
+
} catch {
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
2105
3115
|
}
|
|
2106
|
-
function
|
|
2107
|
-
|
|
2108
|
-
|
|
3116
|
+
async function defaultReload(ctx) {
|
|
3117
|
+
const load = (async () => (await (await ctx.mcp()).listTools({ refresh: true })).filter((t) => !t.hidden).length)();
|
|
3118
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve(null), CATALOG_RELOAD_MS).unref());
|
|
3119
|
+
return Promise.race([load, timeout]).catch((error) => {
|
|
3120
|
+
debug(`catalog reload after sign-in failed: ${error.message}`);
|
|
3121
|
+
return null;
|
|
2109
3122
|
});
|
|
2110
|
-
|
|
3123
|
+
}
|
|
3124
|
+
function envNote() {
|
|
3125
|
+
return process.env.TABBIO_TOKEN || process.env.TABBIO_ACCESS_TOKEN ? "TABBIO_TOKEN / TABBIO_ACCESS_TOKEN is set and still wins for tool calls." : void 0;
|
|
3126
|
+
}
|
|
3127
|
+
function createSessionControls(deps) {
|
|
3128
|
+
const { ctx } = deps;
|
|
3129
|
+
const authClient = () => new ApiClient(ctx.profile, null, { persist: false, fetch: deps.fetch });
|
|
3130
|
+
const nextContext = () => {
|
|
3131
|
+
const next = deps.newContext ? deps.newContext() : createCommandContext();
|
|
3132
|
+
created.add(next);
|
|
3133
|
+
return next;
|
|
3134
|
+
};
|
|
3135
|
+
const adopt = async (toolCount) => {
|
|
3136
|
+
await ctx.close().catch(() => void 0);
|
|
3137
|
+
const next = nextContext();
|
|
3138
|
+
const count = toolCount !== void 0 ? toolCount : await (deps.reloadCatalog ?? defaultReload)(next);
|
|
3139
|
+
return deps.build(next, count === null ? {} : { toolCount: count });
|
|
3140
|
+
};
|
|
3141
|
+
const signedIn = async (payload) => {
|
|
3142
|
+
const creds = await saveAppSession(ctx, payload, { fetch: deps.fetch });
|
|
3143
|
+
const note = envNote();
|
|
3144
|
+
return {
|
|
3145
|
+
services: await adopt(),
|
|
3146
|
+
message: `Signed in as ${describeUser(creds.user)}`,
|
|
3147
|
+
...note ? { warnings: [note] } : {},
|
|
3148
|
+
signedOut: false
|
|
3149
|
+
};
|
|
3150
|
+
};
|
|
3151
|
+
return {
|
|
3152
|
+
async loginWithBrowser({ signal, onWaiting }) {
|
|
3153
|
+
const payload = await loginWithBrowser(authClient(), {
|
|
3154
|
+
device: deviceLabel(),
|
|
3155
|
+
signal,
|
|
3156
|
+
openUrl: deps.openUrl ?? openInBrowser,
|
|
3157
|
+
onWaiting: (url, opened) => onWaiting(url, opened)
|
|
3158
|
+
});
|
|
3159
|
+
return signedIn(payload);
|
|
3160
|
+
},
|
|
3161
|
+
async loginWithEmail({ email, promptCode, onInvalidCode }) {
|
|
3162
|
+
const payload = await loginWithEmailOtp(authClient(), { email, promptCode, onInvalidCode });
|
|
3163
|
+
return signedIn(payload);
|
|
3164
|
+
},
|
|
3165
|
+
async loginWithToken(token) {
|
|
3166
|
+
const { visibleTools } = await saveTokenSession(ctx, token, { fetch: deps.fetch });
|
|
3167
|
+
const note = envNote();
|
|
3168
|
+
return {
|
|
3169
|
+
services: await adopt(visibleTools),
|
|
3170
|
+
message: `Token accepted ${theme.symbols.middot} ${visibleTools} tools. Chat and approvals need /login with the browser or an email code.`,
|
|
3171
|
+
...note ? { warnings: [note] } : {},
|
|
3172
|
+
signedOut: false
|
|
3173
|
+
};
|
|
3174
|
+
},
|
|
3175
|
+
async logout() {
|
|
3176
|
+
const stored = loadStoredCredentials(ctx.profile.name);
|
|
3177
|
+
const result = stored ? await logoutProfile(ctx.profile, stored, { fetch: deps.fetch }) : { warnings: [], revokedSession: false };
|
|
3178
|
+
const note = envNote();
|
|
3179
|
+
const warnings = [...result.warnings, ...note ? [note] : []];
|
|
3180
|
+
return {
|
|
3181
|
+
services: await adopt(null),
|
|
3182
|
+
message: stored ? `Signed out of profile ${ctx.profile.name}` : `Not signed in (profile ${ctx.profile.name})`,
|
|
3183
|
+
...warnings.length ? { warnings } : {},
|
|
3184
|
+
signedOut: true
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
2111
3188
|
}
|
|
2112
3189
|
|
|
2113
|
-
// src/ui/
|
|
2114
|
-
|
|
2115
|
-
|
|
3190
|
+
// src/ui/services.ts
|
|
3191
|
+
function accountIdentity(services) {
|
|
3192
|
+
const account = services.account;
|
|
3193
|
+
return `${services.profileName}:${account.signedIn ? account.hasSession ? "app" : "token" : "signed-out"}:${account.userId ?? account.email ?? account.name ?? ""}`;
|
|
3194
|
+
}
|
|
3195
|
+
function hostOf2(url) {
|
|
3196
|
+
try {
|
|
3197
|
+
return new URL(url).host;
|
|
3198
|
+
} catch {
|
|
3199
|
+
return url;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
function createUiServices(ctx, opts = {}) {
|
|
3203
|
+
const creds = ctx.api.credentials ?? ctx.credentials;
|
|
3204
|
+
const cached = readCachedCatalog(ctx.profile, creds);
|
|
3205
|
+
const saved = /* @__PURE__ */ new Map();
|
|
3206
|
+
let skills = null;
|
|
3207
|
+
return {
|
|
3208
|
+
version: CLI_VERSION,
|
|
3209
|
+
profileName: ctx.profile.name,
|
|
3210
|
+
apiHost: hostOf2(ctx.profile.apiUrl),
|
|
3211
|
+
account: {
|
|
3212
|
+
name: creds?.user?.name ?? null,
|
|
3213
|
+
email: creds?.user?.email ?? null,
|
|
3214
|
+
signedIn: Boolean(creds?.accessToken || creds?.mcpToken),
|
|
3215
|
+
hasSession: Boolean(creds?.accessToken),
|
|
3216
|
+
userId: creds?.user?.id
|
|
3217
|
+
},
|
|
3218
|
+
cachedToolCount: opts.toolCount ?? (cached ? cached.tools.filter((t) => !t.hidden).length : null),
|
|
3219
|
+
async loadPlan() {
|
|
3220
|
+
if (!creds?.accessToken) return null;
|
|
3221
|
+
const state = await ctx.api.json("/api/subscription/state");
|
|
3222
|
+
return state?.snapshot?.tier ?? null;
|
|
3223
|
+
},
|
|
3224
|
+
async loadPendingApprovals() {
|
|
3225
|
+
const state = await getMcpAccessState(ctx.api);
|
|
3226
|
+
return state?.pendingApprovals ?? [];
|
|
3227
|
+
},
|
|
3228
|
+
listThreads: (scope) => listThreads(ctx, scope),
|
|
3229
|
+
listModels: (scope) => listModels(ctx, scope),
|
|
3230
|
+
getThreadMessages: (threadId, scope) => getThreadMessages(ctx, threadId, scope),
|
|
3231
|
+
streamChat: (request, signal) => streamChat(ctx, request, { signal }),
|
|
3232
|
+
decideApproval: (approvalId, decision, scope) => decision === "approve" ? confirmApproval(ctx, approvalId, scope) : rejectApproval(ctx, approvalId, scope),
|
|
3233
|
+
async listTools(opts2) {
|
|
3234
|
+
const session = await ctx.mcp();
|
|
3235
|
+
return session.listTools(opts2);
|
|
3236
|
+
},
|
|
3237
|
+
async callTool(toolId, input, opts2) {
|
|
3238
|
+
const session = await ctx.mcp();
|
|
3239
|
+
const started = Date.now();
|
|
3240
|
+
const result = await session.callTool(toolId, input, opts2);
|
|
3241
|
+
return { ...result, durationMs: Date.now() - started };
|
|
3242
|
+
},
|
|
3243
|
+
capabilityPrefs: loadCapabilityPrefs(ctx.profile.name),
|
|
3244
|
+
saveCapabilityPrefs: (capabilities) => saveCapabilityPrefs(ctx.profile.name, capabilities),
|
|
3245
|
+
loadCapabilities: () => ctx.api.json("/api/agent/capabilities"),
|
|
3246
|
+
getArtifact: (artifactId) => getArtifact(ctx.api, artifactId),
|
|
3247
|
+
async artifactPreviewUrl(artifactId, kind) {
|
|
3248
|
+
return (await previewUrl(ctx.api, artifactId, { variant: previewVariantFor(kind) })).url;
|
|
3249
|
+
},
|
|
3250
|
+
hyperlinks: supportsHyperlinks(),
|
|
3251
|
+
appUrl: ctx.profile.appUrl,
|
|
3252
|
+
session: createSessionControls({ ctx, build: createUiServices }),
|
|
3253
|
+
loadSkills() {
|
|
3254
|
+
skills ??= (async () => {
|
|
3255
|
+
const session = await ctx.mcp();
|
|
3256
|
+
const result = await session.callTool("skills.list", { limit: 50 });
|
|
3257
|
+
if (!result.ok) throw result.error;
|
|
3258
|
+
return parseSkillsResult(result.result);
|
|
3259
|
+
})().catch((error) => {
|
|
3260
|
+
skills = null;
|
|
3261
|
+
throw error;
|
|
3262
|
+
});
|
|
3263
|
+
return skills;
|
|
3264
|
+
},
|
|
3265
|
+
openUrl: (url) => openInBrowser(url),
|
|
3266
|
+
async saveArtifact(artifactId, saveOpts) {
|
|
3267
|
+
const files = await saveArtifactToDisk(ctx, artifactId, saveOpts ?? {});
|
|
3268
|
+
if (files.length) saved.set(artifactId, files);
|
|
3269
|
+
return files;
|
|
3270
|
+
},
|
|
3271
|
+
savedFiles: (artifactId) => saved.get(artifactId),
|
|
3272
|
+
saveCvExport: (cvId, exportOpts) => saveCvExportToDisk(ctx, cvId, exportOpts ?? {}),
|
|
3273
|
+
readSavedPreview: (path, maxLines) => readSavedPreview(path, maxLines),
|
|
3274
|
+
async loadStatus() {
|
|
3275
|
+
const { collectStatus } = await import("./status-2C7BECKK.js");
|
|
3276
|
+
const report = await collectStatus(ctx);
|
|
3277
|
+
const who = report.user ? report.user.name ? `${report.user.name} <${report.user.email}>` : report.user.email : "not signed in";
|
|
3278
|
+
const rows = [
|
|
3279
|
+
["Profile", `${report.profile.name} (${report.profile.source})`],
|
|
3280
|
+
["API", report.profile.apiUrl],
|
|
3281
|
+
["Account", who],
|
|
3282
|
+
["Plan", report.plan],
|
|
3283
|
+
["Session", report.session ? `${report.session.token}${report.session.refreshable ? " \xB7 auto-refresh" : ""}` : null],
|
|
3284
|
+
["Tools via", report.mcp?.label],
|
|
3285
|
+
["Approvals", report.pendingApprovals === null ? null : `${report.pendingApprovals} pending`],
|
|
3286
|
+
["Catalog", report.catalog ? `${report.catalog.tools} tools${report.catalog.fresh ? "" : " (stale)"}` : null],
|
|
3287
|
+
...report.problems.map((p) => ["Problem", p])
|
|
3288
|
+
];
|
|
3289
|
+
return rows.filter((r) => Boolean(r[1])).map(([label, value]) => ({ label, value }));
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
2116
3293
|
|
|
2117
3294
|
// src/ui/screens/Approvals.tsx
|
|
2118
|
-
import { Box as
|
|
2119
|
-
import { useCallback as
|
|
3295
|
+
import { Box as Box20, Text as Text20, useInput as useInput9 } from "ink";
|
|
3296
|
+
import { useCallback as useCallback6, useEffect as useEffect9, useRef as useRef8, useState as useState7 } from "react";
|
|
2120
3297
|
|
|
2121
3298
|
// src/ui/screens/ResultPanel.tsx
|
|
2122
|
-
import { Box as
|
|
2123
|
-
import { useMemo as
|
|
3299
|
+
import { Box as Box19, Text as Text19, useInput as useInput8 } from "ink";
|
|
3300
|
+
import { useMemo as useMemo6, useState as useState6 } from "react";
|
|
2124
3301
|
|
|
2125
3302
|
// src/ui/components/Panel.tsx
|
|
2126
|
-
import { Box as
|
|
2127
|
-
import { jsx as
|
|
3303
|
+
import { Box as Box18, Text as Text18 } from "ink";
|
|
3304
|
+
import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2128
3305
|
var BORDER = {
|
|
2129
3306
|
round: { tl: "\u256D", tr: "\u256E", h: "\u2500" },
|
|
2130
3307
|
classic: { tl: "+", tr: "+", h: "-" }
|
|
@@ -2136,12 +3313,12 @@ function toneColor(tone) {
|
|
|
2136
3313
|
if (tone === "attention") return "yellow";
|
|
2137
3314
|
return void 0;
|
|
2138
3315
|
}
|
|
2139
|
-
function paintBorder(tone,
|
|
2140
|
-
if (tone === "accent") return theme.accent(
|
|
2141
|
-
if (tone === "success") return theme.success(
|
|
2142
|
-
if (tone === "danger") return theme.danger(
|
|
2143
|
-
if (tone === "attention") return theme.attention(
|
|
2144
|
-
return theme.dim(
|
|
3316
|
+
function paintBorder(tone, text2) {
|
|
3317
|
+
if (tone === "accent") return theme.accent(text2);
|
|
3318
|
+
if (tone === "success") return theme.success(text2);
|
|
3319
|
+
if (tone === "danger") return theme.danger(text2);
|
|
3320
|
+
if (tone === "attention") return theme.attention(text2);
|
|
3321
|
+
return theme.dim(text2);
|
|
2145
3322
|
}
|
|
2146
3323
|
function panelTopLine(width, title = "", right = "", tone = "muted") {
|
|
2147
3324
|
const chars2 = BORDER[theme.boxStyle];
|
|
@@ -2162,10 +3339,10 @@ function Panel({
|
|
|
2162
3339
|
children
|
|
2163
3340
|
}) {
|
|
2164
3341
|
const color = toneColor(tone);
|
|
2165
|
-
return /* @__PURE__ */
|
|
2166
|
-
/* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */
|
|
2168
|
-
|
|
3342
|
+
return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", width, children: [
|
|
3343
|
+
/* @__PURE__ */ jsx20(Text18, { children: panelTopLine(width, title, right, tone) }),
|
|
3344
|
+
/* @__PURE__ */ jsx20(
|
|
3345
|
+
Box18,
|
|
2169
3346
|
{
|
|
2170
3347
|
borderStyle: theme.boxStyle,
|
|
2171
3348
|
borderTop: false,
|
|
@@ -2181,11 +3358,11 @@ function Panel({
|
|
|
2181
3358
|
}
|
|
2182
3359
|
|
|
2183
3360
|
// src/ui/result-model.ts
|
|
2184
|
-
function
|
|
3361
|
+
function isRecord5(value) {
|
|
2185
3362
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2186
3363
|
}
|
|
2187
3364
|
function isRowList(value) {
|
|
2188
|
-
return Array.isArray(value) && value.length > 0 && value.every(
|
|
3365
|
+
return Array.isArray(value) && value.length > 0 && value.every(isRecord5);
|
|
2189
3366
|
}
|
|
2190
3367
|
function tableBody(rows, summary, now) {
|
|
2191
3368
|
const picked = pickColumns(rows);
|
|
@@ -2201,7 +3378,7 @@ function kvValue(value, now) {
|
|
|
2201
3378
|
if (Array.isArray(value) && value.some((v) => typeof v === "object" && v !== null)) {
|
|
2202
3379
|
return `${value.length} item${value.length === 1 ? "" : "s"}`;
|
|
2203
3380
|
}
|
|
2204
|
-
if (
|
|
3381
|
+
if (isRecord5(value)) {
|
|
2205
3382
|
const json = safeJson(value, 0);
|
|
2206
3383
|
return json.length > 100 ? `${json.slice(0, 99)}\u2026` : json;
|
|
2207
3384
|
}
|
|
@@ -2238,7 +3415,7 @@ function bodyLength(body) {
|
|
|
2238
3415
|
}
|
|
2239
3416
|
|
|
2240
3417
|
// src/ui/screens/ResultPanel.tsx
|
|
2241
|
-
import { jsx as
|
|
3418
|
+
import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2242
3419
|
function summaryLine(body) {
|
|
2243
3420
|
switch (body.kind) {
|
|
2244
3421
|
case "table":
|
|
@@ -2252,23 +3429,23 @@ function summaryLine(body) {
|
|
|
2252
3429
|
}
|
|
2253
3430
|
}
|
|
2254
3431
|
function ErrorBody({ error }) {
|
|
2255
|
-
return /* @__PURE__ */
|
|
2256
|
-
error.hint ? /* @__PURE__ */
|
|
2257
|
-
/* @__PURE__ */
|
|
3432
|
+
return /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", children: [
|
|
3433
|
+
error.hint ? /* @__PURE__ */ jsx21(Text19, { wrap: "wrap", children: theme.dim(`${theme.symbols.arrowRight} ${error.hint}`) }) : null,
|
|
3434
|
+
/* @__PURE__ */ jsx21(Text19, { children: theme.dim([error.requestId ? `request id ${error.requestId}` : null, `exit ${error.exitCode}`].filter(Boolean).join(` ${theme.symbols.middot} `)) })
|
|
2258
3435
|
] });
|
|
2259
3436
|
}
|
|
2260
3437
|
function ResultPanel(props) {
|
|
2261
3438
|
const { title, result, error, durationMs, requestId, note, width, height, interactive } = props;
|
|
2262
|
-
const body =
|
|
2263
|
-
const jsonLines =
|
|
2264
|
-
const [view, setView] =
|
|
2265
|
-
const [offset, setOffset] =
|
|
3439
|
+
const body = useMemo6(() => describeResult(result), [result]);
|
|
3440
|
+
const jsonLines = useMemo6(() => safeJson(result ?? null).split("\n"), [result]);
|
|
3441
|
+
const [view, setView] = useState6("human");
|
|
3442
|
+
const [offset, setOffset] = useState6(0);
|
|
2266
3443
|
const total = view === "json" ? jsonLines.length : bodyLength(body);
|
|
2267
3444
|
const chrome = 3 + (note ? 1 : 0) + 3;
|
|
2268
3445
|
const window = interactive ? Math.max(3, height - chrome - (body.kind === "table" ? 1 : 0)) : total;
|
|
2269
3446
|
const maxOffset = Math.max(0, total - window);
|
|
2270
3447
|
const clamped = Math.min(offset, maxOffset);
|
|
2271
|
-
|
|
3448
|
+
useInput8(
|
|
2272
3449
|
(input, key) => {
|
|
2273
3450
|
if (key.escape || input === "q") props.onBack?.();
|
|
2274
3451
|
else if (input === "j") {
|
|
@@ -2296,32 +3473,32 @@ function ResultPanel(props) {
|
|
|
2296
3473
|
...total > window ? [{ key: theme.symbols.upDown, label: "scroll" }] : [],
|
|
2297
3474
|
{ key: "q", label: "back" }
|
|
2298
3475
|
];
|
|
2299
|
-
return /* @__PURE__ */
|
|
2300
|
-
/* @__PURE__ */
|
|
2301
|
-
note ? /* @__PURE__ */
|
|
2302
|
-
/* @__PURE__ */
|
|
2303
|
-
error ? /* @__PURE__ */
|
|
2304
|
-
interactive && total > window ? /* @__PURE__ */
|
|
3476
|
+
return /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", width, children: [
|
|
3477
|
+
/* @__PURE__ */ jsx21(Panel, { title: heading, right, width, tone: ok ? "muted" : "danger", children: /* @__PURE__ */ jsx21(Text19, { wrap: "truncate-end", children: error ? theme.error(error.message) : theme.dim(summaryLine(body)) }) }),
|
|
3478
|
+
note ? /* @__PURE__ */ jsx21(Text19, { wrap: "truncate-end", children: note }) : null,
|
|
3479
|
+
/* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", paddingLeft: 2, width, children: [
|
|
3480
|
+
error ? /* @__PURE__ */ jsx21(ErrorBody, { error }) : view === "json" ? jsonLines.slice(clamped, clamped + window).map((line, i) => /* @__PURE__ */ jsx21(Text19, { wrap: "truncate-end", children: line }, clamped + i)) : body.kind === "table" ? /* @__PURE__ */ jsx21(Table, { columns: body.columns, rows: body.rows, width: inner, offset: clamped, limit: window }) : body.kind === "kv" ? /* @__PURE__ */ jsx21(KeyValue, { rows: body.rows.slice(clamped, clamped + window), width: inner }) : body.kind === "text" ? interactive ? body.text.split("\n").slice(clamped, clamped + window).map((line, i) => /* @__PURE__ */ jsx21(Text19, { wrap: "truncate-end", children: line }, clamped + i)) : /* @__PURE__ */ jsx21(Markdown, { text: body.text, width: inner }) : /* @__PURE__ */ jsx21(Text19, { children: theme.dim("No output") }),
|
|
3481
|
+
interactive && total > window ? /* @__PURE__ */ jsx21(Text19, { children: theme.dim(`${clamped + 1}\u2013${Math.min(total, clamped + window)} of ${total}`) }) : null
|
|
2305
3482
|
] }),
|
|
2306
|
-
interactive ? /* @__PURE__ */
|
|
3483
|
+
interactive ? /* @__PURE__ */ jsx21(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx21(KeyHints, { width, hints }) }) : null
|
|
2307
3484
|
] });
|
|
2308
3485
|
}
|
|
2309
3486
|
|
|
2310
3487
|
// src/ui/screens/Approvals.tsx
|
|
2311
|
-
import { jsx as
|
|
3488
|
+
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2312
3489
|
function argsOf(item) {
|
|
2313
3490
|
return typeof item.args === "object" && item.args !== null && !Array.isArray(item.args) ? item.args : {};
|
|
2314
3491
|
}
|
|
2315
3492
|
function Approvals({ services, width, height, onExit }) {
|
|
2316
|
-
const [phase, setPhase] =
|
|
2317
|
-
const mounted =
|
|
2318
|
-
|
|
3493
|
+
const [phase, setPhase] = useState7({ name: "loading", since: Date.now() });
|
|
3494
|
+
const mounted = useRef8(true);
|
|
3495
|
+
useEffect9(
|
|
2319
3496
|
() => () => {
|
|
2320
3497
|
mounted.current = false;
|
|
2321
3498
|
},
|
|
2322
3499
|
[]
|
|
2323
3500
|
);
|
|
2324
|
-
const load =
|
|
3501
|
+
const load = useCallback6(async () => {
|
|
2325
3502
|
if (!services.account.hasSession) {
|
|
2326
3503
|
setPhase({ name: "failed", message: "Approvals need a full sign-in", hint: "Run `tabbio login`; a personal MCP token cannot approve." });
|
|
2327
3504
|
return;
|
|
@@ -2335,10 +3512,10 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2335
3512
|
if (mounted.current) setPhase({ name: "failed", message: cli.message, hint: cli.hint });
|
|
2336
3513
|
}
|
|
2337
3514
|
}, [services]);
|
|
2338
|
-
|
|
3515
|
+
useEffect9(() => {
|
|
2339
3516
|
void load();
|
|
2340
3517
|
}, [load]);
|
|
2341
|
-
const decide =
|
|
3518
|
+
const decide = useCallback6(
|
|
2342
3519
|
async (decision) => {
|
|
2343
3520
|
if (phase.name !== "card") return;
|
|
2344
3521
|
if (decision === "leave") {
|
|
@@ -2362,7 +3539,7 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2362
3539
|
},
|
|
2363
3540
|
[phase, services]
|
|
2364
3541
|
);
|
|
2365
|
-
|
|
3542
|
+
useInput9(
|
|
2366
3543
|
(input, key) => {
|
|
2367
3544
|
if (phase.name === "done" && phase.decision !== "approve" && (key.return || key.escape || input === "q")) void load();
|
|
2368
3545
|
else if (phase.name === "failed" && (key.escape || input === "q" || key.return)) onExit();
|
|
@@ -2371,27 +3548,27 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2371
3548
|
{ isActive: phase.name === "done" && phase.decision !== "approve" || phase.name === "failed" || phase.name === "loading" }
|
|
2372
3549
|
);
|
|
2373
3550
|
const count = phase.name === "list" || phase.name === "card" ? phase.items.length : null;
|
|
2374
|
-
const header = /* @__PURE__ */
|
|
3551
|
+
const header = /* @__PURE__ */ jsx22(Box20, { marginBottom: 1, children: /* @__PURE__ */ jsxs18(Text20, { children: [
|
|
2375
3552
|
theme.bold("Approvals"),
|
|
2376
3553
|
count !== null ? theme.dim(` ${theme.symbols.middot} ${count} waiting`) : ""
|
|
2377
3554
|
] }) });
|
|
2378
3555
|
switch (phase.name) {
|
|
2379
3556
|
case "loading":
|
|
2380
|
-
return /* @__PURE__ */
|
|
3557
|
+
return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width, children: [
|
|
2381
3558
|
header,
|
|
2382
|
-
/* @__PURE__ */
|
|
3559
|
+
/* @__PURE__ */ jsx22(Working, { label: "Loading approvals\u2026", since: phase.since })
|
|
2383
3560
|
] });
|
|
2384
3561
|
case "failed":
|
|
2385
|
-
return /* @__PURE__ */
|
|
3562
|
+
return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width, children: [
|
|
2386
3563
|
header,
|
|
2387
|
-
/* @__PURE__ */
|
|
2388
|
-
phase.hint ? /* @__PURE__ */
|
|
2389
|
-
/* @__PURE__ */
|
|
3564
|
+
/* @__PURE__ */ jsx22(Text20, { children: theme.error(`${theme.symbols.error} ${phase.message}`) }),
|
|
3565
|
+
phase.hint ? /* @__PURE__ */ jsx22(Text20, { wrap: "wrap", children: theme.dim(` ${theme.symbols.arrowRight} ${phase.hint}`) }) : null,
|
|
3566
|
+
/* @__PURE__ */ jsx22(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx22(KeyHints, { width, hints: [{ key: "q", label: "back" }] }) })
|
|
2390
3567
|
] });
|
|
2391
3568
|
case "list":
|
|
2392
|
-
return /* @__PURE__ */
|
|
3569
|
+
return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width, children: [
|
|
2393
3570
|
header,
|
|
2394
|
-
/* @__PURE__ */
|
|
3571
|
+
/* @__PURE__ */ jsx22(
|
|
2395
3572
|
SelectList,
|
|
2396
3573
|
{
|
|
2397
3574
|
items: phase.items.map((item) => ({
|
|
@@ -2413,9 +3590,9 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2413
3590
|
)
|
|
2414
3591
|
] });
|
|
2415
3592
|
case "card":
|
|
2416
|
-
return /* @__PURE__ */
|
|
3593
|
+
return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width, children: [
|
|
2417
3594
|
header,
|
|
2418
|
-
/* @__PURE__ */
|
|
3595
|
+
/* @__PURE__ */ jsx22(
|
|
2419
3596
|
ApprovalCard,
|
|
2420
3597
|
{
|
|
2421
3598
|
approvalId: phase.item.id,
|
|
@@ -2432,9 +3609,9 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2432
3609
|
)
|
|
2433
3610
|
] });
|
|
2434
3611
|
case "done":
|
|
2435
|
-
return /* @__PURE__ */
|
|
3612
|
+
return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width, children: [
|
|
2436
3613
|
header,
|
|
2437
|
-
phase.decision === "approve" ? /* @__PURE__ */
|
|
3614
|
+
phase.decision === "approve" ? /* @__PURE__ */ jsx22(
|
|
2438
3615
|
ResultPanel,
|
|
2439
3616
|
{
|
|
2440
3617
|
title: sentenceCase(phase.item.toolName),
|
|
@@ -2446,41 +3623,41 @@ function Approvals({ services, width, height, onExit }) {
|
|
|
2446
3623
|
interactive: true,
|
|
2447
3624
|
onBack: () => void load()
|
|
2448
3625
|
}
|
|
2449
|
-
) : /* @__PURE__ */
|
|
2450
|
-
/* @__PURE__ */
|
|
2451
|
-
/* @__PURE__ */
|
|
3626
|
+
) : /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", children: [
|
|
3627
|
+
/* @__PURE__ */ jsx22(Text20, { children: approvalOutcomeText(phase.decision, sentenceCase(phase.item.toolName), phase.item.id, phase.durationMs) }),
|
|
3628
|
+
/* @__PURE__ */ jsx22(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx22(KeyHints, { width, hints: [{ key: "enter", label: "back to approvals" }] }) })
|
|
2452
3629
|
] })
|
|
2453
3630
|
] });
|
|
2454
3631
|
}
|
|
2455
3632
|
}
|
|
2456
3633
|
|
|
2457
3634
|
// src/ui/screens/ToolBrowser.tsx
|
|
2458
|
-
import { Box as
|
|
2459
|
-
import { memo as
|
|
3635
|
+
import { Box as Box22, Text as Text22, useInput as useInput11 } from "ink";
|
|
3636
|
+
import { memo as memo16, useMemo as useMemo7, useState as useState9 } from "react";
|
|
2460
3637
|
|
|
2461
3638
|
// src/ui/screens/ToolRun.tsx
|
|
2462
|
-
import { Box as
|
|
2463
|
-
import { useCallback as
|
|
2464
|
-
import { jsx as
|
|
3639
|
+
import { Box as Box21, Text as Text21, useInput as useInput10 } from "ink";
|
|
3640
|
+
import { useCallback as useCallback7, useEffect as useEffect10, useRef as useRef9, useState as useState8 } from "react";
|
|
3641
|
+
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2465
3642
|
function toolLabel(tool) {
|
|
2466
3643
|
return tool.commandPath.join(" ") || tool.id;
|
|
2467
3644
|
}
|
|
2468
3645
|
function ToolRun({ tool, services, width, height, initialInput = {}, start, onExit }) {
|
|
2469
|
-
const [phase, setPhase] =
|
|
2470
|
-
const mounted =
|
|
2471
|
-
const controller =
|
|
2472
|
-
|
|
3646
|
+
const [phase, setPhase] = useState8({ name: "form", input: initialInput });
|
|
3647
|
+
const mounted = useRef9(true);
|
|
3648
|
+
const controller = useRef9(null);
|
|
3649
|
+
useEffect10(
|
|
2473
3650
|
() => () => {
|
|
2474
3651
|
mounted.current = false;
|
|
2475
3652
|
controller.current?.abort();
|
|
2476
3653
|
},
|
|
2477
3654
|
[]
|
|
2478
3655
|
);
|
|
2479
|
-
const withDefaults =
|
|
3656
|
+
const withDefaults = useCallback7(
|
|
2480
3657
|
(input) => autoFilledProperties(tool, tool.inputSchema).includes("userId") && input.userId === void 0 && services.account.userId ? { ...input, userId: services.account.userId } : input,
|
|
2481
3658
|
[tool, services.account.userId]
|
|
2482
3659
|
);
|
|
2483
|
-
const run =
|
|
3660
|
+
const run = useCallback7(
|
|
2484
3661
|
async (input) => {
|
|
2485
3662
|
const startedAt = Date.now();
|
|
2486
3663
|
controller.current?.abort();
|
|
@@ -2503,13 +3680,13 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2503
3680
|
},
|
|
2504
3681
|
[services, tool.id, withDefaults]
|
|
2505
3682
|
);
|
|
2506
|
-
const started =
|
|
2507
|
-
|
|
3683
|
+
const started = useRef9(false);
|
|
3684
|
+
useEffect10(() => {
|
|
2508
3685
|
if (started.current || start !== "run") return;
|
|
2509
3686
|
started.current = true;
|
|
2510
3687
|
void run(initialInput);
|
|
2511
3688
|
}, [start, run, initialInput]);
|
|
2512
|
-
const decide =
|
|
3689
|
+
const decide = useCallback7(
|
|
2513
3690
|
async (decision) => {
|
|
2514
3691
|
if (phase.name !== "approval") return;
|
|
2515
3692
|
const { approval, input, requestedAt } = phase;
|
|
@@ -2536,7 +3713,7 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2536
3713
|
},
|
|
2537
3714
|
[phase, services, tool.title]
|
|
2538
3715
|
);
|
|
2539
|
-
|
|
3716
|
+
useInput10(
|
|
2540
3717
|
(input, key) => {
|
|
2541
3718
|
if (phase.name === "running" && key.escape) {
|
|
2542
3719
|
controller.current?.abort();
|
|
@@ -2550,19 +3727,19 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2550
3727
|
{ isActive: phase.name === "running" || phase.name === "error" || phase.name === "left" }
|
|
2551
3728
|
);
|
|
2552
3729
|
const label = toolLabel(tool);
|
|
2553
|
-
const header = /* @__PURE__ */
|
|
2554
|
-
/* @__PURE__ */
|
|
3730
|
+
const header = /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", marginBottom: 1, children: [
|
|
3731
|
+
/* @__PURE__ */ jsxs19(Text21, { wrap: "truncate-end", children: [
|
|
2555
3732
|
theme.bold(label),
|
|
2556
3733
|
" ",
|
|
2557
3734
|
theme.dim(`${theme.symbols.middot} ${oneLine(tool.title)}`)
|
|
2558
3735
|
] }),
|
|
2559
|
-
tool.description ? /* @__PURE__ */
|
|
3736
|
+
tool.description ? /* @__PURE__ */ jsx23(Text21, { wrap: "truncate-end", children: theme.dim(oneLine(tool.description)) }) : null
|
|
2560
3737
|
] });
|
|
2561
3738
|
switch (phase.name) {
|
|
2562
3739
|
case "form":
|
|
2563
|
-
return /* @__PURE__ */
|
|
3740
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", width, children: [
|
|
2564
3741
|
header,
|
|
2565
|
-
/* @__PURE__ */
|
|
3742
|
+
/* @__PURE__ */ jsx23(
|
|
2566
3743
|
Form,
|
|
2567
3744
|
{
|
|
2568
3745
|
initial: createFormState(tool.inputSchema, phase.input, { skip: autoFilledProperties(tool, tool.inputSchema) }),
|
|
@@ -2575,15 +3752,15 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2575
3752
|
)
|
|
2576
3753
|
] });
|
|
2577
3754
|
case "running":
|
|
2578
|
-
return /* @__PURE__ */
|
|
3755
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", width, children: [
|
|
2579
3756
|
header,
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
3757
|
+
/* @__PURE__ */ jsx23(Working, { label: `Running ${label}${theme.symbols.ellipsis}`, since: phase.startedAt }),
|
|
3758
|
+
/* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx23(KeyHints, { width, hints: [{ key: "esc", label: "cancel" }] }) })
|
|
2582
3759
|
] });
|
|
2583
3760
|
case "approval":
|
|
2584
|
-
return /* @__PURE__ */
|
|
3761
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", width, children: [
|
|
2585
3762
|
header,
|
|
2586
|
-
/* @__PURE__ */
|
|
3763
|
+
/* @__PURE__ */ jsx23(
|
|
2587
3764
|
ApprovalCard,
|
|
2588
3765
|
{
|
|
2589
3766
|
approvalId: phase.approval.approvalId,
|
|
@@ -2601,7 +3778,7 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2601
3778
|
)
|
|
2602
3779
|
] });
|
|
2603
3780
|
case "result":
|
|
2604
|
-
return /* @__PURE__ */
|
|
3781
|
+
return /* @__PURE__ */ jsx23(
|
|
2605
3782
|
ResultPanel,
|
|
2606
3783
|
{
|
|
2607
3784
|
title: label,
|
|
@@ -2618,18 +3795,18 @@ function ToolRun({ tool, services, width, height, initialInput = {}, start, onEx
|
|
|
2618
3795
|
}
|
|
2619
3796
|
);
|
|
2620
3797
|
case "error":
|
|
2621
|
-
return /* @__PURE__ */
|
|
2622
|
-
/* @__PURE__ */
|
|
2623
|
-
/* @__PURE__ */
|
|
3798
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", width, children: [
|
|
3799
|
+
/* @__PURE__ */ jsx23(ResultPanel, { title: label, error: phase.error, durationMs: phase.durationMs, width, height, interactive: false }),
|
|
3800
|
+
/* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx23(KeyHints, { width, hints: [{ key: "r", label: "retry" }, { key: "e", label: "edit input" }, { key: "q", label: "back" }] }) })
|
|
2624
3801
|
] });
|
|
2625
3802
|
case "left":
|
|
2626
|
-
return /* @__PURE__ */
|
|
3803
|
+
return /* @__PURE__ */ jsxs19(Box21, { flexDirection: "column", width, children: [
|
|
2627
3804
|
header,
|
|
2628
|
-
/* @__PURE__ */
|
|
2629
|
-
/* @__PURE__ */
|
|
3805
|
+
/* @__PURE__ */ jsx23(Text21, { children: approvalOutcomeText("leave", sentenceCase(phase.approval.toolName || tool.title), phase.approval.approvalId) }),
|
|
3806
|
+
/* @__PURE__ */ jsx23(Text21, { wrap: "wrap", children: theme.dim(
|
|
2630
3807
|
services.account.hasSession ? "Decide later in the Tabbio app or with `tabbio approvals`." : `Approve it in the Tabbio app, or run \`tabbio approvals wait ${shortId(phase.approval.approvalId, 12, 4)}\`.`
|
|
2631
3808
|
) }),
|
|
2632
|
-
/* @__PURE__ */
|
|
3809
|
+
/* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx23(KeyHints, { width, hints: [{ key: "r", label: "run again" }, { key: "e", label: "edit input" }, { key: "q", label: "back" }] }) })
|
|
2633
3810
|
] });
|
|
2634
3811
|
}
|
|
2635
3812
|
}
|
|
@@ -2727,7 +3904,7 @@ function browserKeyAction(state, input, key, rows) {
|
|
|
2727
3904
|
}
|
|
2728
3905
|
|
|
2729
3906
|
// src/ui/screens/ToolBrowser.tsx
|
|
2730
|
-
import { jsx as
|
|
3907
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2731
3908
|
var TWO_PANE_MIN = fromTerminal(90);
|
|
2732
3909
|
function badge(tool) {
|
|
2733
3910
|
if (tool.kind === "workflow") return theme.dim("workflow");
|
|
@@ -2735,14 +3912,14 @@ function badge(tool) {
|
|
|
2735
3912
|
if (tool.annotations.destructiveHint === true) return theme.danger("write");
|
|
2736
3913
|
return tool.readOnly ? theme.dim("read") : "write";
|
|
2737
3914
|
}
|
|
2738
|
-
var ListRow =
|
|
3915
|
+
var ListRow = memo16(function ListRow2({ row, selected, width }) {
|
|
2739
3916
|
const pointer = selected ? theme.accent(theme.symbols.pointer) : " ";
|
|
2740
3917
|
if (row.kind === "group") {
|
|
2741
3918
|
const glyph2 = row.expanded ? theme.symbols.expanded : theme.symbols.collapsed;
|
|
2742
3919
|
const count = theme.dim(row.matched === row.total ? String(row.total) : `${row.matched} of ${row.total}`);
|
|
2743
3920
|
const name = truncate(row.group, Math.max(4, width - 12));
|
|
2744
3921
|
const fill = Math.max(1, width - 4 - displayWidth(name) - displayWidth(count));
|
|
2745
|
-
return /* @__PURE__ */
|
|
3922
|
+
return /* @__PURE__ */ jsxs20(Text22, { wrap: "truncate-end", children: [
|
|
2746
3923
|
pointer,
|
|
2747
3924
|
" ",
|
|
2748
3925
|
theme.dim(glyph2),
|
|
@@ -2755,34 +3932,34 @@ var ListRow = memo15(function ListRow2({ row, selected, width }) {
|
|
|
2755
3932
|
const label = row.tool.kind === "workflow" ? row.tool.action : row.tool.commandPath.join(" ");
|
|
2756
3933
|
const tag = badge(row.tool);
|
|
2757
3934
|
const labelWidth = Math.max(4, width - 4 - displayWidth(tag) - 1);
|
|
2758
|
-
const
|
|
2759
|
-
return /* @__PURE__ */
|
|
3935
|
+
const text2 = padEnd(truncate(label, labelWidth), labelWidth);
|
|
3936
|
+
return /* @__PURE__ */ jsxs20(Text22, { wrap: "truncate-end", children: [
|
|
2760
3937
|
pointer,
|
|
2761
3938
|
" ",
|
|
2762
|
-
selected ? theme.accent(
|
|
3939
|
+
selected ? theme.accent(text2) : text2,
|
|
2763
3940
|
" ",
|
|
2764
3941
|
tag
|
|
2765
3942
|
] });
|
|
2766
3943
|
});
|
|
2767
|
-
var ToolDetail =
|
|
2768
|
-
const flags =
|
|
2769
|
-
const example =
|
|
3944
|
+
var ToolDetail = memo16(function ToolDetail2({ tool, width, height }) {
|
|
3945
|
+
const flags = useMemo7(() => describeSchema(tool.inputSchema), [tool]);
|
|
3946
|
+
const example = useMemo7(() => exampleInvocation(tool, tool.inputSchema), [tool]);
|
|
2770
3947
|
const flagWidth = Math.min(28, Math.max(8, ...flags.map((f) => displayWidth(`${f.flag} <${f.type}>`))));
|
|
2771
3948
|
const descLines = Math.max(1, Math.min(4, height - flags.length - 8));
|
|
2772
3949
|
const lines = wrapWords(tool.description, width, descLines);
|
|
2773
3950
|
const shownFlags = flags.slice(0, Math.max(1, height - 7 - lines.length));
|
|
2774
|
-
return /* @__PURE__ */
|
|
2775
|
-
/* @__PURE__ */
|
|
2776
|
-
/* @__PURE__ */
|
|
2777
|
-
/* @__PURE__ */
|
|
3951
|
+
return /* @__PURE__ */ jsxs20(Box22, { flexDirection: "column", width, children: [
|
|
3952
|
+
/* @__PURE__ */ jsxs20(Box22, { width, children: [
|
|
3953
|
+
/* @__PURE__ */ jsx24(Box22, { flexGrow: 1, children: /* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: theme.bold(tool.commandPath.join(" ")) }) }),
|
|
3954
|
+
/* @__PURE__ */ jsx24(Text22, { children: badge(tool) })
|
|
2778
3955
|
] }),
|
|
2779
|
-
/* @__PURE__ */
|
|
2780
|
-
/* @__PURE__ */
|
|
2781
|
-
lines.map((line, i) => /* @__PURE__ */
|
|
2782
|
-
/* @__PURE__ */
|
|
2783
|
-
/* @__PURE__ */
|
|
2784
|
-
flags.length === 0 ? /* @__PURE__ */
|
|
2785
|
-
shownFlags.map((flag) => /* @__PURE__ */
|
|
3956
|
+
/* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: oneLine(tool.title) }),
|
|
3957
|
+
/* @__PURE__ */ jsx24(Text22, { children: " " }),
|
|
3958
|
+
lines.map((line, i) => /* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: theme.dim(line) }, i)),
|
|
3959
|
+
/* @__PURE__ */ jsx24(Text22, { children: " " }),
|
|
3960
|
+
/* @__PURE__ */ jsx24(Text22, { children: theme.dim("FLAGS") }),
|
|
3961
|
+
flags.length === 0 ? /* @__PURE__ */ jsx24(Text22, { children: theme.dim(" none") }) : null,
|
|
3962
|
+
shownFlags.map((flag) => /* @__PURE__ */ jsxs20(Text22, { wrap: "truncate-end", children: [
|
|
2786
3963
|
" ",
|
|
2787
3964
|
padEnd(truncate(`${flag.flag} <${flag.type}>`, flagWidth), flagWidth),
|
|
2788
3965
|
" ",
|
|
@@ -2790,10 +3967,10 @@ var ToolDetail = memo15(function ToolDetail2({ tool, width, height }) {
|
|
|
2790
3967
|
" ",
|
|
2791
3968
|
theme.dim(flag.description)
|
|
2792
3969
|
] }, flag.flag)),
|
|
2793
|
-
flags.length > shownFlags.length ? /* @__PURE__ */
|
|
2794
|
-
/* @__PURE__ */
|
|
2795
|
-
/* @__PURE__ */
|
|
2796
|
-
/* @__PURE__ */
|
|
3970
|
+
flags.length > shownFlags.length ? /* @__PURE__ */ jsx24(Text22, { children: theme.dim(` +${flags.length - shownFlags.length} more`) }) : null,
|
|
3971
|
+
/* @__PURE__ */ jsx24(Text22, { children: " " }),
|
|
3972
|
+
/* @__PURE__ */ jsx24(Text22, { children: theme.dim("EXAMPLE") }),
|
|
3973
|
+
/* @__PURE__ */ jsxs20(Text22, { wrap: "truncate-end", children: [
|
|
2797
3974
|
" ",
|
|
2798
3975
|
example
|
|
2799
3976
|
] })
|
|
@@ -2801,15 +3978,15 @@ var ToolDetail = memo15(function ToolDetail2({ tool, width, height }) {
|
|
|
2801
3978
|
});
|
|
2802
3979
|
function ToolBrowser(props) {
|
|
2803
3980
|
const { tools, services, width, height, onExit } = props;
|
|
2804
|
-
const [state, setState] =
|
|
3981
|
+
const [state, setState] = useState9(() => {
|
|
2805
3982
|
const initial = initialBrowserState({ query: props.initialQuery, group: props.initialGroup });
|
|
2806
3983
|
return initial.query ? applyBrowserAction(tools, { ...initial, query: "" }, { type: "type", text: initial.query }) : initial;
|
|
2807
3984
|
});
|
|
2808
|
-
const [running, setRunning] =
|
|
2809
|
-
const rows =
|
|
3985
|
+
const [running, setRunning] = useState9(null);
|
|
3986
|
+
const rows = useMemo7(() => browserRows(tools, state), [tools, state]);
|
|
2810
3987
|
const cursor = Math.min(state.cursor, Math.max(0, rows.length - 1));
|
|
2811
3988
|
const current = rows[cursor];
|
|
2812
|
-
|
|
3989
|
+
useInput11(
|
|
2813
3990
|
(input, key) => {
|
|
2814
3991
|
const outcome = browserKeyAction({ ...state, cursor }, input, key, rows);
|
|
2815
3992
|
if (!outcome) return;
|
|
@@ -2820,7 +3997,7 @@ function ToolBrowser(props) {
|
|
|
2820
3997
|
{ isActive: !running }
|
|
2821
3998
|
);
|
|
2822
3999
|
if (running) {
|
|
2823
|
-
return /* @__PURE__ */
|
|
4000
|
+
return /* @__PURE__ */ jsx24(ToolRun, { tool: running, services, width, height, start: "form", onExit: () => setRunning(null) });
|
|
2824
4001
|
}
|
|
2825
4002
|
const visibleCount = tools.filter((t) => !t.hidden).length;
|
|
2826
4003
|
const groupCount = new Set(tools.filter((t) => !t.hidden).map((t) => t.group)).size;
|
|
@@ -2835,16 +4012,16 @@ function ToolBrowser(props) {
|
|
|
2835
4012
|
twoPane ? `${theme.symbols.line.repeat(leftWidth + 1)}\u252C${theme.symbols.line.repeat(Math.max(0, rightWidth + 1))}` : theme.symbols.line.repeat(width)
|
|
2836
4013
|
);
|
|
2837
4014
|
const detailTool = current?.kind === "tool" ? current.tool : null;
|
|
2838
|
-
return /* @__PURE__ */
|
|
2839
|
-
/* @__PURE__ */
|
|
2840
|
-
/* @__PURE__ */
|
|
2841
|
-
/* @__PURE__ */
|
|
4015
|
+
return /* @__PURE__ */ jsxs20(Box22, { flexDirection: "column", width, children: [
|
|
4016
|
+
/* @__PURE__ */ jsxs20(Box22, { width, children: [
|
|
4017
|
+
/* @__PURE__ */ jsx24(Box22, { flexGrow: 1, children: /* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: title }) }),
|
|
4018
|
+
/* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: filter })
|
|
2842
4019
|
] }),
|
|
2843
|
-
/* @__PURE__ */
|
|
2844
|
-
/* @__PURE__ */
|
|
2845
|
-
/* @__PURE__ */
|
|
2846
|
-
rows.length === 0 ? /* @__PURE__ */
|
|
2847
|
-
rows.slice(start, end).map((row, i) => /* @__PURE__ */
|
|
4020
|
+
/* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: theme.unicode ? rule : theme.dim("-".repeat(width)) }),
|
|
4021
|
+
/* @__PURE__ */ jsxs20(Box22, { width, children: [
|
|
4022
|
+
/* @__PURE__ */ jsxs20(Box22, { flexDirection: "column", width: leftWidth, children: [
|
|
4023
|
+
rows.length === 0 ? /* @__PURE__ */ jsx24(Text22, { children: theme.dim(` No tools match "${state.query}"`) }) : null,
|
|
4024
|
+
rows.slice(start, end).map((row, i) => /* @__PURE__ */ jsx24(
|
|
2848
4025
|
ListRow,
|
|
2849
4026
|
{
|
|
2850
4027
|
row,
|
|
@@ -2854,11 +4031,11 @@ function ToolBrowser(props) {
|
|
|
2854
4031
|
row.kind === "group" ? `g:${row.group}` : `t:${row.tool.id}`
|
|
2855
4032
|
))
|
|
2856
4033
|
] }),
|
|
2857
|
-
twoPane ? /* @__PURE__ */
|
|
4034
|
+
twoPane ? /* @__PURE__ */ jsx24(Box22, { flexDirection: "column", marginLeft: 1, paddingLeft: 1, borderStyle: theme.unicode ? "single" : "classic", borderTop: false, borderRight: false, borderBottom: false, borderDimColor: true, width: rightWidth + 2, children: detailTool ? /* @__PURE__ */ jsx24(ToolDetail, { tool: detailTool, width: rightWidth, height: listHeight }) : /* @__PURE__ */ jsx24(Text22, { children: theme.dim(current?.kind === "group" ? `${current.group}: enter or \u2192 to open the group` : "Select a tool") }) }) : null
|
|
2858
4035
|
] }),
|
|
2859
|
-
!twoPane && detailTool ? /* @__PURE__ */
|
|
2860
|
-
/* @__PURE__ */
|
|
2861
|
-
/* @__PURE__ */
|
|
4036
|
+
!twoPane && detailTool ? /* @__PURE__ */ jsx24(Box22, { marginTop: 1, children: /* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: theme.dim(oneLine(detailTool.description || detailTool.title)) }) }) : null,
|
|
4037
|
+
/* @__PURE__ */ jsx24(Text22, { wrap: "truncate-end", children: theme.dim(theme.symbols.line.repeat(width)) }),
|
|
4038
|
+
/* @__PURE__ */ jsx24(
|
|
2862
4039
|
KeyHints,
|
|
2863
4040
|
{
|
|
2864
4041
|
width,
|
|
@@ -2876,7 +4053,14 @@ function ToolBrowser(props) {
|
|
|
2876
4053
|
|
|
2877
4054
|
// src/ui/screens/home/home-model.ts
|
|
2878
4055
|
var emptyHomeData = { plan: null, pendingApprovals: null, recent: null };
|
|
2879
|
-
function homeCommands(data, toolCount) {
|
|
4056
|
+
function homeCommands(data, toolCount, access = { signedIn: true, hasSession: true }) {
|
|
4057
|
+
if (!access.signedIn) {
|
|
4058
|
+
return [
|
|
4059
|
+
{ name: "login", usage: "/login [email|token]", description: "Sign in (browser, email code or token)" },
|
|
4060
|
+
{ name: "help", description: "Keys and commands" },
|
|
4061
|
+
{ name: "exit", description: "Quit tabbio" }
|
|
4062
|
+
];
|
|
4063
|
+
}
|
|
2880
4064
|
const waiting = data.pendingApprovals;
|
|
2881
4065
|
return [
|
|
2882
4066
|
{ name: "chat", description: "Chat with Tabbio" },
|
|
@@ -2884,34 +4068,50 @@ function homeCommands(data, toolCount) {
|
|
|
2884
4068
|
{ name: "approvals", description: waiting === null ? "Approve or reject actions" : waiting > 0 ? `${waiting} waiting` : "None waiting" },
|
|
2885
4069
|
{ name: "status", description: "Account, tokens, catalog" },
|
|
2886
4070
|
{ name: "help", description: "Keys and commands" },
|
|
4071
|
+
{ name: "login", usage: "/login [email|token]", description: access.hasSession ? "Switch account" : "Full sign-in for chat and approvals" },
|
|
4072
|
+
{ name: "logout", description: "Sign out of this profile" },
|
|
2887
4073
|
{ name: "exit", description: "Quit tabbio" }
|
|
2888
4074
|
];
|
|
2889
4075
|
}
|
|
2890
|
-
function
|
|
2891
|
-
return
|
|
4076
|
+
function startCommands(commands, signedIn) {
|
|
4077
|
+
return commands.filter((c) => c.name !== "exit" && (!signedIn || c.name !== "login" && c.name !== "logout"));
|
|
4078
|
+
}
|
|
4079
|
+
var HOME_COMMAND_NAMES = ["chat", "tools", "approvals", "status", "login", "logout", "help", "exit", "quit"];
|
|
4080
|
+
var SIGN_IN_FIRST = "Sign in first: /login";
|
|
4081
|
+
function homeGate(name, access) {
|
|
4082
|
+
if (name === "login" || name === "help" || name === "exit" || name === "quit") return null;
|
|
4083
|
+
if (!access.signedIn) return SIGN_IN_FIRST;
|
|
4084
|
+
if ((name === "chat" || name === "approvals" || name === "message" || name === "skill") && !access.hasSession) {
|
|
4085
|
+
return `${name === "approvals" ? "Approvals need" : "Chat needs"} a full sign-in: /login (browser or email code)`;
|
|
4086
|
+
}
|
|
4087
|
+
return null;
|
|
2892
4088
|
}
|
|
2893
4089
|
var HOME_HELP_ROWS = [
|
|
2894
4090
|
["type + enter", "Start a chat with that message"],
|
|
2895
|
-
["/", "Command palette
|
|
4091
|
+
["/", "Command palette: /chat /tools /approvals /status /login /logout /help /exit"],
|
|
4092
|
+
["/login", "Sign in here: browser, /login email you@x.com, or /login token"],
|
|
4093
|
+
["/logout", "Sign out of this profile (ends the session on the server)"],
|
|
4094
|
+
["/<skill>", "Your Tabbio skills are listed in the palette; enter runs one in a chat"],
|
|
2896
4095
|
["tab", "Jump to recent chats"],
|
|
2897
|
-
["esc", "Close, go back, or
|
|
4096
|
+
["esc", "Close, go back, or clear the composer"],
|
|
4097
|
+
["q", "Back from lists and help"],
|
|
2898
4098
|
["ctrl+j", "New line in the composer (or \\ then enter)"],
|
|
2899
4099
|
["\u2191 \u2193", "Composer history, or move in lists"],
|
|
2900
|
-
["ctrl+c
|
|
4100
|
+
["ctrl+c twice", "Quit"]
|
|
2901
4101
|
];
|
|
2902
4102
|
|
|
2903
4103
|
// src/ui/screens/home/HomeView.tsx
|
|
2904
|
-
import { Box as
|
|
2905
|
-
import { memo as
|
|
4104
|
+
import { Box as Box25, Text as Text25 } from "ink";
|
|
4105
|
+
import { memo as memo18 } from "react";
|
|
2906
4106
|
|
|
2907
4107
|
// src/ui/components/Header.tsx
|
|
2908
|
-
import { Box as
|
|
2909
|
-
import { memo as
|
|
4108
|
+
import { Box as Box24, Text as Text24 } from "ink";
|
|
4109
|
+
import { memo as memo17 } from "react";
|
|
2910
4110
|
|
|
2911
4111
|
// src/ui/components/Wordmark.tsx
|
|
2912
|
-
import { Box as
|
|
4112
|
+
import { Box as Box23, Text as Text23 } from "ink";
|
|
2913
4113
|
import Gradient from "ink-gradient";
|
|
2914
|
-
import { jsx as
|
|
4114
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
2915
4115
|
var WORDMARK_ROWS = [
|
|
2916
4116
|
" \u2588 \u2588 \u2588 \u2580",
|
|
2917
4117
|
"\u2580\u2588\u2580 \u2584\u2580\u2580\u2588 \u2588\u2580\u2580\u2584 \u2588\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584",
|
|
@@ -2919,17 +4119,17 @@ var WORDMARK_ROWS = [
|
|
|
2919
4119
|
];
|
|
2920
4120
|
var WORDMARK_WIDTH = 25;
|
|
2921
4121
|
function Wordmark() {
|
|
2922
|
-
if (!theme.unicode) return /* @__PURE__ */
|
|
4122
|
+
if (!theme.unicode) return /* @__PURE__ */ jsx25(Text23, { children: theme.accentBold("tabbio") });
|
|
2923
4123
|
const rows = WORDMARK_ROWS.map((row) => row.padEnd(WORDMARK_WIDTH));
|
|
2924
4124
|
const gradient = theme.wordmarkGradient;
|
|
2925
4125
|
if (gradient) {
|
|
2926
|
-
return /* @__PURE__ */
|
|
4126
|
+
return /* @__PURE__ */ jsx25(Gradient, { colors: [...gradient], children: /* @__PURE__ */ jsx25(Text23, { children: rows.join("\n") }) });
|
|
2927
4127
|
}
|
|
2928
|
-
return /* @__PURE__ */
|
|
4128
|
+
return /* @__PURE__ */ jsx25(Box23, { flexDirection: "column", children: rows.map((row, i) => /* @__PURE__ */ jsx25(Text23, { children: theme.accent(row) }, i)) });
|
|
2929
4129
|
}
|
|
2930
4130
|
|
|
2931
4131
|
// src/ui/components/Header.tsx
|
|
2932
|
-
import { jsx as
|
|
4132
|
+
import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2933
4133
|
var WIDE_HEADER_MIN = fromTerminal(60);
|
|
2934
4134
|
function oneLineHeader({ version, profile, account }) {
|
|
2935
4135
|
const sep = theme.dim(` ${theme.symbols.middot} `);
|
|
@@ -2945,24 +4145,24 @@ function oneLineHeader({ version, profile, account }) {
|
|
|
2945
4145
|
function capitalize(value) {
|
|
2946
4146
|
return value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
|
|
2947
4147
|
}
|
|
2948
|
-
var Header =
|
|
4148
|
+
var Header = memo17(function Header2(props) {
|
|
2949
4149
|
const { version, width, tagline = "Your career, from the terminal" } = props;
|
|
2950
4150
|
if (width < WIDE_HEADER_MIN || !theme.unicode) {
|
|
2951
|
-
return /* @__PURE__ */
|
|
4151
|
+
return /* @__PURE__ */ jsx26(Text24, { wrap: "truncate-end", children: oneLineHeader(props) });
|
|
2952
4152
|
}
|
|
2953
4153
|
const showTagline = width >= WORDMARK_WIDTH + 3 + tagline.length;
|
|
2954
|
-
return /* @__PURE__ */
|
|
2955
|
-
/* @__PURE__ */
|
|
2956
|
-
/* @__PURE__ */
|
|
2957
|
-
/* @__PURE__ */
|
|
2958
|
-
/* @__PURE__ */
|
|
2959
|
-
/* @__PURE__ */
|
|
4154
|
+
return /* @__PURE__ */ jsxs21(Box24, { children: [
|
|
4155
|
+
/* @__PURE__ */ jsx26(Wordmark, {}),
|
|
4156
|
+
/* @__PURE__ */ jsxs21(Box24, { flexDirection: "column", marginLeft: 3, children: [
|
|
4157
|
+
/* @__PURE__ */ jsx26(Text24, { children: " " }),
|
|
4158
|
+
/* @__PURE__ */ jsx26(Text24, { children: theme.bold(`tabbio ${version}`) }),
|
|
4159
|
+
/* @__PURE__ */ jsx26(Text24, { wrap: "truncate-end", children: showTagline ? theme.dim(tagline) : "" })
|
|
2960
4160
|
] })
|
|
2961
4161
|
] });
|
|
2962
4162
|
});
|
|
2963
4163
|
|
|
2964
4164
|
// src/ui/screens/home/HomeView.tsx
|
|
2965
|
-
import { jsx as
|
|
4165
|
+
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2966
4166
|
var TWO_COLUMN_MIN = fromTerminal(100);
|
|
2967
4167
|
function AccountLine({ services, plan, width }) {
|
|
2968
4168
|
const { account } = services;
|
|
@@ -2971,13 +4171,14 @@ function AccountLine({ services, plan, width }) {
|
|
|
2971
4171
|
const left = `${account.signedIn ? theme.success(theme.symbols.dot) : theme.attention(theme.symbols.dot)} ${who}`;
|
|
2972
4172
|
const right = theme.dim(`profile ${services.profileName} ${theme.symbols.middot} ${services.apiHost}`);
|
|
2973
4173
|
const fill = width - displayWidth(left) - displayWidth(right);
|
|
2974
|
-
return /* @__PURE__ */
|
|
4174
|
+
return /* @__PURE__ */ jsx27(Text25, { wrap: "truncate-end", children: fill >= 2 ? `${left}${" ".repeat(fill)}${right}` : left });
|
|
2975
4175
|
}
|
|
2976
|
-
var QuickActions =
|
|
4176
|
+
var QuickActions = memo18(function QuickActions2(props) {
|
|
4177
|
+
const { commands, width, pending, signedIn } = props;
|
|
2977
4178
|
const nameWidth = 13;
|
|
2978
|
-
return /* @__PURE__ */
|
|
2979
|
-
/* @__PURE__ */
|
|
2980
|
-
commands
|
|
4179
|
+
return /* @__PURE__ */ jsxs22(Box25, { flexDirection: "column", width, children: [
|
|
4180
|
+
/* @__PURE__ */ jsx27(Text25, { children: theme.dim("START") }),
|
|
4181
|
+
startCommands(commands, signedIn).map((c) => /* @__PURE__ */ jsxs22(Text25, { wrap: "truncate-end", children: [
|
|
2981
4182
|
theme.accent(padEnd(`/${c.name}`, nameWidth)),
|
|
2982
4183
|
truncate(c.description, Math.max(4, width - nameWidth - 3)),
|
|
2983
4184
|
c.name === "approvals" && pending ? ` ${theme.attention(theme.symbols.dot)}` : ""
|
|
@@ -2986,23 +4187,23 @@ var QuickActions = memo17(function QuickActions2({ commands, width, pending }) {
|
|
|
2986
4187
|
});
|
|
2987
4188
|
function Recent(props) {
|
|
2988
4189
|
const { data, focus, width, rows, services } = props;
|
|
2989
|
-
const title = /* @__PURE__ */
|
|
4190
|
+
const title = /* @__PURE__ */ jsx27(Text25, { children: theme.dim("RECENT CHATS") });
|
|
2990
4191
|
if (!services.account.hasSession) {
|
|
2991
|
-
return /* @__PURE__ */
|
|
4192
|
+
return /* @__PURE__ */ jsxs22(Box25, { flexDirection: "column", width, children: [
|
|
2992
4193
|
title,
|
|
2993
|
-
/* @__PURE__ */
|
|
4194
|
+
/* @__PURE__ */ jsx27(Text25, { wrap: "truncate-end", children: theme.dim(services.account.signedIn ? "Chats need a full sign-in: /login" : "Type /login to see your chats") })
|
|
2994
4195
|
] });
|
|
2995
4196
|
}
|
|
2996
4197
|
if (data.recent === null) {
|
|
2997
|
-
return /* @__PURE__ */
|
|
4198
|
+
return /* @__PURE__ */ jsxs22(Box25, { flexDirection: "column", width, children: [
|
|
2998
4199
|
title,
|
|
2999
|
-
/* @__PURE__ */
|
|
4200
|
+
/* @__PURE__ */ jsx27(Text25, { children: theme.dim(data.error ? `Could not load: ${data.error}` : `Loading${theme.symbols.ellipsis}`) })
|
|
3000
4201
|
] });
|
|
3001
4202
|
}
|
|
3002
|
-
return /* @__PURE__ */
|
|
4203
|
+
return /* @__PURE__ */ jsxs22(Box25, { flexDirection: "column", width, children: [
|
|
3003
4204
|
title,
|
|
3004
|
-
data.recent.length === 0 ? /* @__PURE__ */
|
|
3005
|
-
data.recent.length > 0 ? /* @__PURE__ */
|
|
4205
|
+
data.recent.length === 0 ? /* @__PURE__ */ jsx27(Text25, { children: theme.dim("No chats yet. Type below to start one.") }) : null,
|
|
4206
|
+
data.recent.length > 0 ? /* @__PURE__ */ jsx27(
|
|
3006
4207
|
SelectList,
|
|
3007
4208
|
{
|
|
3008
4209
|
items: data.recent.map((r) => ({ id: r.id, label: r.title, meta: r.when })),
|
|
@@ -3017,24 +4218,37 @@ function Recent(props) {
|
|
|
3017
4218
|
) : null
|
|
3018
4219
|
] });
|
|
3019
4220
|
}
|
|
4221
|
+
function composerPlaceholder(signedOut, width) {
|
|
4222
|
+
if (signedOut) return "Type /login to sign in, or / for commands";
|
|
4223
|
+
return width < WIDE_HEADER_MIN ? "Ask Tabbio anything, or / for commands" : "Ask Tabbio anything, or type / for commands";
|
|
4224
|
+
}
|
|
3020
4225
|
function HomeView(props) {
|
|
3021
4226
|
const { services, data, commands, matches, width, height } = props;
|
|
3022
4227
|
const wide = width >= TWO_COLUMN_MIN;
|
|
3023
4228
|
const tall = height >= 26;
|
|
3024
4229
|
const showWordmark = width >= WIDE_HEADER_MIN && tall;
|
|
3025
4230
|
const showAccount = width >= WIDE_HEADER_MIN;
|
|
3026
|
-
const
|
|
3027
|
-
const
|
|
4231
|
+
const signedOut = !services.account.signedIn;
|
|
4232
|
+
const recentRows = signedOut ? 0 : wide ? 5 : height >= 30 ? 3 : 0;
|
|
4233
|
+
const colWidth = wide && recentRows > 0 ? Math.floor((width - 4) / 2) : width;
|
|
3028
4234
|
const account = { name: services.account.name, email: services.account.email, plan: data.plan };
|
|
3029
|
-
return /* @__PURE__ */
|
|
3030
|
-
showWordmark ? /* @__PURE__ */
|
|
3031
|
-
showAccount ? /* @__PURE__ */
|
|
3032
|
-
|
|
3033
|
-
/* @__PURE__ */
|
|
3034
|
-
|
|
4235
|
+
return /* @__PURE__ */ jsxs22(Box25, { flexDirection: "column", width, children: [
|
|
4236
|
+
showWordmark ? /* @__PURE__ */ jsx27(Header, { version: services.version, profile: services.profileName, width, account }) : /* @__PURE__ */ jsx27(Header, { version: services.version, profile: services.profileName, width: Math.min(width, WIDE_HEADER_MIN - 1), account }),
|
|
4237
|
+
showAccount ? /* @__PURE__ */ jsxs22(Box25, { marginTop: 1, flexDirection: "column", children: [
|
|
4238
|
+
/* @__PURE__ */ jsx27(AccountLine, { services, plan: data.plan, width }),
|
|
4239
|
+
signedOut ? /* @__PURE__ */ jsx27(Text25, { wrap: "truncate-end", children: theme.dim(" Type /login to sign in") }) : null
|
|
4240
|
+
] }) : signedOut ? /* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs22(Text25, { wrap: "truncate-end", children: [
|
|
4241
|
+
theme.attention("Not signed in"),
|
|
4242
|
+
" ",
|
|
4243
|
+
theme.dim("Type /login to sign in")
|
|
4244
|
+
] }) }) : null,
|
|
4245
|
+
/* @__PURE__ */ jsxs22(Box25, { marginTop: 1, flexDirection: wide ? "row" : "column", width, children: [
|
|
4246
|
+
/* @__PURE__ */ jsx27(QuickActions, { commands, width: colWidth, pending: data.pendingApprovals, signedIn: !signedOut }),
|
|
4247
|
+
recentRows > 0 ? /* @__PURE__ */ jsx27(Box25, { marginLeft: wide ? 4 : 0, marginTop: wide ? 0 : 1, children: /* @__PURE__ */ jsx27(Recent, { ...props, width: colWidth, rows: recentRows }) }) : null
|
|
3035
4248
|
] }),
|
|
3036
|
-
|
|
3037
|
-
/* @__PURE__ */
|
|
4249
|
+
props.modal ? /* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: props.modal }) : null,
|
|
4250
|
+
!props.modal && (matches.length > 0 || props.paletteOpen) ? /* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: /* @__PURE__ */ jsx27(Palette, { commands: matches, selected: props.paletteIndex, width, footer: props.paletteFooter }) }) : null,
|
|
4251
|
+
props.modal ? null : /* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: /* @__PURE__ */ jsx27(
|
|
3038
4252
|
TextInput,
|
|
3039
4253
|
{
|
|
3040
4254
|
editor: props.editor,
|
|
@@ -3043,16 +4257,20 @@ function HomeView(props) {
|
|
|
3043
4257
|
onKey: props.onComposerKey,
|
|
3044
4258
|
isActive: props.focus === "composer",
|
|
3045
4259
|
width,
|
|
3046
|
-
placeholder:
|
|
4260
|
+
placeholder: composerPlaceholder(signedOut, width),
|
|
3047
4261
|
boxed: true
|
|
3048
4262
|
}
|
|
3049
4263
|
) }),
|
|
3050
|
-
props.notice ? /* @__PURE__ */
|
|
3051
|
-
/* @__PURE__ */
|
|
4264
|
+
props.notice ? props.notice.split("\n").map((line, i) => /* @__PURE__ */ jsx27(Text25, { wrap: "wrap", children: line }, i)) : null,
|
|
4265
|
+
/* @__PURE__ */ jsx27(
|
|
3052
4266
|
KeyHints,
|
|
3053
4267
|
{
|
|
3054
4268
|
width,
|
|
3055
|
-
hints: [
|
|
4269
|
+
hints: props.modal ? [{ key: "esc", label: "cancel sign-in" }] : signedOut ? [
|
|
4270
|
+
{ key: "/login", label: "sign in" },
|
|
4271
|
+
{ key: "/", label: "commands" },
|
|
4272
|
+
{ key: "ctrl+c", label: "quit" }
|
|
4273
|
+
] : [
|
|
3056
4274
|
{ key: "enter", label: "send" },
|
|
3057
4275
|
{ key: "/", label: "commands" },
|
|
3058
4276
|
...services.account.hasSession && recentRows > 0 ? [{ key: "tab", label: "recent chats" }] : [],
|
|
@@ -3064,32 +4282,41 @@ function HomeView(props) {
|
|
|
3064
4282
|
}
|
|
3065
4283
|
|
|
3066
4284
|
// src/ui/screens/home/Home.tsx
|
|
3067
|
-
import { jsx as
|
|
4285
|
+
import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3068
4286
|
var DOUBLE_PRESS_MS2 = 2e3;
|
|
4287
|
+
var NOTICE_MS = 6e3;
|
|
3069
4288
|
function BackKeys({ onBack }) {
|
|
3070
|
-
|
|
4289
|
+
useInput12((input, key) => {
|
|
3071
4290
|
if (key.escape || input === "q" || key.return) onBack();
|
|
3072
4291
|
});
|
|
3073
4292
|
return null;
|
|
3074
4293
|
}
|
|
4294
|
+
function accountNotice(change) {
|
|
4295
|
+
return [theme.success(`${theme.symbols.success} ${change.message}`), ...(change.warnings ?? []).map((w) => theme.attention(`${theme.symbols.attention} ${w}`))].join("\n");
|
|
4296
|
+
}
|
|
3075
4297
|
function Home(props) {
|
|
3076
|
-
const {
|
|
3077
|
-
const [
|
|
3078
|
-
const [
|
|
3079
|
-
const [
|
|
3080
|
-
const [
|
|
3081
|
-
const [
|
|
3082
|
-
const [
|
|
3083
|
-
const
|
|
3084
|
-
const
|
|
3085
|
-
|
|
4298
|
+
const { width, height, onExit, mode = "seeker", saveArtifacts = true } = props;
|
|
4299
|
+
const [services, setServices] = useState10(props.services);
|
|
4300
|
+
const [route, setRoute] = useState10({ name: "home" });
|
|
4301
|
+
const [data, setData] = useState10(props.initialData ?? emptyHomeData);
|
|
4302
|
+
const [editor, dispatchEditor] = useReducer5(editorReducer, emptyEditor);
|
|
4303
|
+
const [paletteIndex, setPaletteIndex] = useState10(0);
|
|
4304
|
+
const [navigated, setNavigated] = useState10(false);
|
|
4305
|
+
const [focus, setFocus] = useState10("composer");
|
|
4306
|
+
const [notice, setNotice] = useState10(props.initialNotice ?? null);
|
|
4307
|
+
const [login, setLogin] = useState10(null);
|
|
4308
|
+
const lastCtrlC = useRef10(0);
|
|
4309
|
+
const mounted = useRef10(true);
|
|
4310
|
+
useEffect11(
|
|
3086
4311
|
() => () => {
|
|
3087
4312
|
mounted.current = false;
|
|
3088
4313
|
},
|
|
3089
4314
|
[]
|
|
3090
4315
|
);
|
|
3091
|
-
const
|
|
3092
|
-
|
|
4316
|
+
const { signedIn, hasSession } = services.account;
|
|
4317
|
+
const access = useMemo8(() => ({ signedIn, hasSession }), [signedIn, hasSession]);
|
|
4318
|
+
const loadData = useCallback8(async () => {
|
|
4319
|
+
if (props.initialData && services === props.services || !services.account.hasSession) return;
|
|
3093
4320
|
const [plan, approvals, threads] = await Promise.allSettled([
|
|
3094
4321
|
services.loadPlan(),
|
|
3095
4322
|
services.loadPendingApprovals(),
|
|
@@ -3106,51 +4333,89 @@ function Home(props) {
|
|
|
3106
4333
|
})) : null,
|
|
3107
4334
|
error: threads.status === "rejected" ? toCliError(threads.reason).message : void 0
|
|
3108
4335
|
});
|
|
3109
|
-
}, [props.initialData, props.companyId, services, mode]);
|
|
3110
|
-
|
|
4336
|
+
}, [props.initialData, props.services, props.companyId, services, mode]);
|
|
4337
|
+
useEffect11(() => {
|
|
3111
4338
|
if (route.name === "home") void loadData();
|
|
3112
4339
|
}, [route.name, loadData]);
|
|
3113
|
-
|
|
4340
|
+
useEffect11(() => {
|
|
3114
4341
|
if (!notice) return void 0;
|
|
3115
|
-
const timer = setTimeout(() => setNotice(null),
|
|
4342
|
+
const timer = setTimeout(() => setNotice(null), NOTICE_MS);
|
|
3116
4343
|
return () => clearTimeout(timer);
|
|
3117
4344
|
}, [notice]);
|
|
3118
|
-
|
|
3119
|
-
const
|
|
4345
|
+
useEffect11(() => setNavigated(false), [editor.value]);
|
|
4346
|
+
const commands = useMemo8(() => homeCommands(data, services.cachedToolCount, access), [data, services.cachedToolCount, access]);
|
|
4347
|
+
const skills = useSkills(services, hasSession && editor.value.startsWith("/"));
|
|
4348
|
+
const view = paletteView(commands, hasSession ? skills : { status: "idle", skills: [] }, editor.value, { ellipsis: theme.symbols.ellipsis, separator: theme.symbols.middot });
|
|
4349
|
+
const matches = view.items;
|
|
3120
4350
|
const selected = Math.min(paletteIndex, Math.max(0, matches.length - 1));
|
|
3121
|
-
const goHome =
|
|
3122
|
-
const
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
4351
|
+
const goHome = useCallback8(() => setRoute({ name: "home" }), []);
|
|
4352
|
+
const warn = useCallback8((text2) => setNotice(theme.attention(`${theme.symbols.attention} ${text2}`)), []);
|
|
4353
|
+
const applyAccount = useCallback8((change, opts = {}) => {
|
|
4354
|
+
const switched = accountIdentity(services) !== accountIdentity(change.services);
|
|
4355
|
+
setServices(change.services);
|
|
4356
|
+
setData(emptyHomeData);
|
|
4357
|
+
if (opts.stay && switched) setRoute({ name: "chat" });
|
|
4358
|
+
if (!opts.stay) {
|
|
4359
|
+
setRoute({ name: "home" });
|
|
4360
|
+
setNotice(accountNotice(change));
|
|
4361
|
+
}
|
|
4362
|
+
}, [services]);
|
|
4363
|
+
const onLoginDone = useCallback8(
|
|
4364
|
+
(outcome) => {
|
|
4365
|
+
setLogin(null);
|
|
4366
|
+
if (outcome.status === "done") applyAccount(outcome.change);
|
|
4367
|
+
else if (outcome.status === "cancelled") setNotice(theme.dim("Sign-in cancelled"));
|
|
4368
|
+
else setNotice([theme.error(`${theme.symbols.error} ${outcome.message}`), ...outcome.hint ? [theme.dim(` ${outcome.hint}`)] : []].join("\n"));
|
|
3127
4369
|
},
|
|
3128
|
-
[
|
|
4370
|
+
[applyAccount]
|
|
3129
4371
|
);
|
|
3130
|
-
const
|
|
3131
|
-
(name) => {
|
|
4372
|
+
const open2 = useCallback8(
|
|
4373
|
+
(name, arg = "") => {
|
|
4374
|
+
const gate = homeGate(name, access);
|
|
4375
|
+
if (gate) {
|
|
4376
|
+
warn(gate);
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
3132
4379
|
switch (name) {
|
|
3133
4380
|
case "chat":
|
|
3134
|
-
|
|
4381
|
+
setRoute({ name: "chat" });
|
|
3135
4382
|
return;
|
|
3136
4383
|
case "tools": {
|
|
3137
|
-
if (!services.account.signedIn) {
|
|
3138
|
-
setNotice(theme.attention(`${theme.symbols.attention} Sign in first: run \`tabbio login\`.`));
|
|
3139
|
-
return;
|
|
3140
|
-
}
|
|
3141
4384
|
const since = Date.now();
|
|
3142
4385
|
setRoute({ name: "tools", since });
|
|
3143
4386
|
services.listTools().then((tools) => mounted.current && setRoute((r) => r.name === "tools" ? { ...r, tools } : r)).catch((error) => mounted.current && setRoute((r) => r.name === "tools" ? { ...r, error: toCliError(error).message } : r));
|
|
3144
4387
|
return;
|
|
3145
4388
|
}
|
|
3146
4389
|
case "approvals":
|
|
3147
|
-
|
|
4390
|
+
setRoute({ name: "approvals" });
|
|
3148
4391
|
return;
|
|
3149
4392
|
case "status": {
|
|
3150
4393
|
setRoute({ name: "status", since: Date.now() });
|
|
3151
4394
|
services.loadStatus().then((rows) => mounted.current && setRoute((r) => r.name === "status" ? { ...r, rows } : r)).catch((error) => mounted.current && setRoute((r) => r.name === "status" ? { ...r, error: toCliError(error).message } : r));
|
|
3152
4395
|
return;
|
|
3153
4396
|
}
|
|
4397
|
+
case "login": {
|
|
4398
|
+
const start = parseLoginArgs(arg);
|
|
4399
|
+
if ("error" in start) setNotice(theme.error(`${theme.symbols.error} ${start.error}`));
|
|
4400
|
+
else {
|
|
4401
|
+
setNotice(null);
|
|
4402
|
+
setLogin(start);
|
|
4403
|
+
}
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
case "logout": {
|
|
4407
|
+
const session = services.session;
|
|
4408
|
+
if (!session) {
|
|
4409
|
+
warn("Signing out is not available here. Run `tabbio logout`.");
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
setNotice(theme.dim(`Signing out${theme.symbols.ellipsis}`));
|
|
4413
|
+
session.logout().then(
|
|
4414
|
+
(change) => mounted.current && applyAccount(change),
|
|
4415
|
+
(error) => mounted.current && setNotice(theme.error(`${theme.symbols.error} ${toCliError(error).message}`))
|
|
4416
|
+
);
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
3154
4419
|
case "help":
|
|
3155
4420
|
setRoute({ name: "help" });
|
|
3156
4421
|
return;
|
|
@@ -3162,37 +4427,45 @@ function Home(props) {
|
|
|
3162
4427
|
setNotice(theme.error(`${theme.symbols.error} Unknown command /${name}`));
|
|
3163
4428
|
}
|
|
3164
4429
|
},
|
|
3165
|
-
[
|
|
4430
|
+
[access, services, onExit, warn, applyAccount]
|
|
3166
4431
|
);
|
|
3167
|
-
const submit2 =
|
|
4432
|
+
const submit2 = useCallback8(
|
|
3168
4433
|
(value) => {
|
|
3169
|
-
const
|
|
3170
|
-
if (
|
|
3171
|
-
|
|
3172
|
-
|
|
4434
|
+
const res = resolveSubmit(value, { commands, skills, highlighted: matches[selected], navigated, aliases: HOME_COMMAND_NAMES });
|
|
4435
|
+
if (res.kind === "empty" || res.kind === "bare") return;
|
|
4436
|
+
if (res.kind === "loading") {
|
|
4437
|
+
warn(`Skills are still loading. Press enter again in a moment.`);
|
|
4438
|
+
return;
|
|
4439
|
+
}
|
|
4440
|
+
dispatchEditor({ type: "remember", value: value.trim() });
|
|
3173
4441
|
dispatchEditor({ type: "clear" });
|
|
3174
4442
|
setPaletteIndex(0);
|
|
3175
|
-
if (
|
|
3176
|
-
|
|
3177
|
-
open(exact ?? matches[selected]?.name ?? slash.name);
|
|
4443
|
+
if (res.kind === "unknown") {
|
|
4444
|
+
setNotice([theme.error(`${theme.symbols.error} Unknown command /${res.name}`), theme.dim(" Type / to see the commands.")].join("\n"));
|
|
3178
4445
|
return;
|
|
3179
4446
|
}
|
|
3180
|
-
if (
|
|
4447
|
+
if (res.kind === "command") {
|
|
4448
|
+
open2(res.name, res.arg);
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
const gate = homeGate(res.kind, access);
|
|
4452
|
+
if (gate) warn(gate);
|
|
4453
|
+
else setRoute({ name: "chat", initialMessage: res.kind === "skill" ? res.message : res.text });
|
|
3181
4454
|
},
|
|
3182
|
-
[
|
|
4455
|
+
[commands, skills, matches, selected, navigated, open2, access, warn]
|
|
3183
4456
|
);
|
|
3184
|
-
const onComposerKey =
|
|
4457
|
+
const onComposerKey = useCallback8(
|
|
3185
4458
|
(input, key) => {
|
|
3186
4459
|
if (key.ctrl && input === "c") return true;
|
|
3187
4460
|
if (matches.length > 0 && (key.upArrow || key.downArrow)) {
|
|
3188
4461
|
setPaletteIndex((i) => key.upArrow ? Math.max(0, i - 1) : Math.min(matches.length - 1, i + 1));
|
|
4462
|
+
setNavigated(true);
|
|
3189
4463
|
return true;
|
|
3190
4464
|
}
|
|
3191
4465
|
if (key.tab) {
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
} else if (data.recent?.length) setFocus("recent");
|
|
4466
|
+
const choice = matches[selected];
|
|
4467
|
+
if (choice) dispatchEditor({ type: "set", value: `/${choice.name}${choice.usage || choice.skillId ? " " : ""}` });
|
|
4468
|
+
else if (data.recent?.length && hasSession) setFocus("recent");
|
|
3196
4469
|
return true;
|
|
3197
4470
|
}
|
|
3198
4471
|
if (key.escape) {
|
|
@@ -3201,9 +4474,9 @@ function Home(props) {
|
|
|
3201
4474
|
}
|
|
3202
4475
|
return false;
|
|
3203
4476
|
},
|
|
3204
|
-
[matches, selected, data.recent]
|
|
4477
|
+
[matches, selected, data.recent, hasSession]
|
|
3205
4478
|
);
|
|
3206
|
-
|
|
4479
|
+
useInput12(
|
|
3207
4480
|
(input, key) => {
|
|
3208
4481
|
if (!(key.ctrl && input === "c")) return;
|
|
3209
4482
|
const now = Date.now();
|
|
@@ -3219,7 +4492,7 @@ function Home(props) {
|
|
|
3219
4492
|
);
|
|
3220
4493
|
switch (route.name) {
|
|
3221
4494
|
case "chat":
|
|
3222
|
-
return /* @__PURE__ */
|
|
4495
|
+
return /* @__PURE__ */ jsx28(
|
|
3223
4496
|
Chat,
|
|
3224
4497
|
{
|
|
3225
4498
|
services,
|
|
@@ -3227,42 +4500,48 @@ function Home(props) {
|
|
|
3227
4500
|
companyId: props.companyId,
|
|
3228
4501
|
threadId: route.threadId,
|
|
3229
4502
|
initialMessage: route.initialMessage,
|
|
4503
|
+
saveArtifacts,
|
|
3230
4504
|
width,
|
|
3231
4505
|
height,
|
|
3232
4506
|
hostedByHome: true,
|
|
4507
|
+
onAccountChange: (change) => applyAccount(change, { stay: !change.signedOut }),
|
|
3233
4508
|
onExit: (reason) => reason === "home" ? goHome() : onExit()
|
|
3234
|
-
}
|
|
4509
|
+
},
|
|
4510
|
+
accountIdentity(services)
|
|
3235
4511
|
);
|
|
3236
4512
|
case "tools":
|
|
3237
|
-
if (route.tools) return /* @__PURE__ */
|
|
3238
|
-
return /* @__PURE__ */
|
|
3239
|
-
route.error ? /* @__PURE__ */
|
|
3240
|
-
/* @__PURE__ */
|
|
4513
|
+
if (route.tools) return /* @__PURE__ */ jsx28(ToolBrowser, { tools: route.tools, services, width, height, onExit: goHome });
|
|
4514
|
+
return /* @__PURE__ */ jsxs23(Box26, { flexDirection: "column", width, children: [
|
|
4515
|
+
route.error ? /* @__PURE__ */ jsx28(Text26, { children: theme.error(`${theme.symbols.error} ${route.error}`) }) : /* @__PURE__ */ jsx28(Working, { label: "Loading tools\u2026", since: route.since }),
|
|
4516
|
+
/* @__PURE__ */ jsx28(KeyHints, { width, hints: [{ key: "esc", label: "back" }] }),
|
|
4517
|
+
/* @__PURE__ */ jsx28(BackKeys, { onBack: goHome })
|
|
3241
4518
|
] });
|
|
3242
4519
|
case "approvals":
|
|
3243
|
-
return /* @__PURE__ */
|
|
4520
|
+
return /* @__PURE__ */ jsx28(Approvals, { services, width, height, onExit: goHome });
|
|
3244
4521
|
case "status":
|
|
3245
|
-
return /* @__PURE__ */
|
|
3246
|
-
/* @__PURE__ */
|
|
3247
|
-
/* @__PURE__ */
|
|
3248
|
-
/* @__PURE__ */
|
|
3249
|
-
/* @__PURE__ */
|
|
4522
|
+
return /* @__PURE__ */ jsxs23(Box26, { flexDirection: "column", width, children: [
|
|
4523
|
+
/* @__PURE__ */ jsx28(Text26, { children: theme.bold("Status") }),
|
|
4524
|
+
/* @__PURE__ */ jsx28(Box26, { marginTop: 1, flexDirection: "column", children: route.rows ? /* @__PURE__ */ jsx28(KeyValue, { rows: route.rows, width }) : route.error ? /* @__PURE__ */ jsx28(Text26, { children: theme.error(`${theme.symbols.error} ${route.error}`) }) : /* @__PURE__ */ jsx28(Working, { label: "Checking\u2026", since: route.since }) }),
|
|
4525
|
+
/* @__PURE__ */ jsx28(Box26, { marginTop: 1, children: /* @__PURE__ */ jsx28(KeyHints, { width, hints: [{ key: "q", label: "back" }] }) }),
|
|
4526
|
+
/* @__PURE__ */ jsx28(BackKeys, { onBack: goHome })
|
|
3250
4527
|
] });
|
|
3251
4528
|
case "help":
|
|
3252
|
-
return /* @__PURE__ */
|
|
3253
|
-
/* @__PURE__ */
|
|
3254
|
-
/* @__PURE__ */
|
|
3255
|
-
/* @__PURE__ */
|
|
3256
|
-
/* @__PURE__ */
|
|
4529
|
+
return /* @__PURE__ */ jsxs23(Box26, { flexDirection: "column", width, children: [
|
|
4530
|
+
/* @__PURE__ */ jsx28(Text26, { children: theme.bold("Keys and commands") }),
|
|
4531
|
+
/* @__PURE__ */ jsx28(Box26, { marginTop: 1, children: /* @__PURE__ */ jsx28(KeyValue, { rows: HOME_HELP_ROWS.map(([label, value]) => ({ label, value })), width }) }),
|
|
4532
|
+
/* @__PURE__ */ jsx28(Box26, { marginTop: 1, children: /* @__PURE__ */ jsx28(KeyHints, { width, hints: [{ key: "q", label: "back" }] }) }),
|
|
4533
|
+
/* @__PURE__ */ jsx28(BackKeys, { onBack: goHome })
|
|
3257
4534
|
] });
|
|
3258
4535
|
default:
|
|
3259
|
-
return /* @__PURE__ */
|
|
4536
|
+
return /* @__PURE__ */ jsx28(
|
|
3260
4537
|
HomeView,
|
|
3261
4538
|
{
|
|
3262
4539
|
services,
|
|
3263
4540
|
data,
|
|
3264
4541
|
commands,
|
|
3265
4542
|
matches,
|
|
4543
|
+
paletteOpen: view.open,
|
|
4544
|
+
paletteFooter: view.footer,
|
|
3266
4545
|
paletteIndex: selected,
|
|
3267
4546
|
editor,
|
|
3268
4547
|
dispatchEditor,
|
|
@@ -3274,6 +4553,7 @@ function Home(props) {
|
|
|
3274
4553
|
setRoute({ name: "chat", threadId: id });
|
|
3275
4554
|
},
|
|
3276
4555
|
onRecentCancel: () => setFocus("composer"),
|
|
4556
|
+
modal: login ? /* @__PURE__ */ jsx28(LoginPanel, { services, start: login, width, onDone: onLoginDone }) : null,
|
|
3277
4557
|
notice,
|
|
3278
4558
|
width,
|
|
3279
4559
|
height
|
|
@@ -3282,83 +4562,8 @@ function Home(props) {
|
|
|
3282
4562
|
}
|
|
3283
4563
|
}
|
|
3284
4564
|
|
|
3285
|
-
// src/ui/services.ts
|
|
3286
|
-
function hostOf2(url) {
|
|
3287
|
-
try {
|
|
3288
|
-
return new URL(url).host;
|
|
3289
|
-
} catch {
|
|
3290
|
-
return url;
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3293
|
-
function createUiServices(ctx) {
|
|
3294
|
-
const creds = ctx.api.credentials ?? ctx.credentials;
|
|
3295
|
-
const cached = readCachedCatalog(ctx.profile, creds);
|
|
3296
|
-
return {
|
|
3297
|
-
version: CLI_VERSION,
|
|
3298
|
-
profileName: ctx.profile.name,
|
|
3299
|
-
apiHost: hostOf2(ctx.profile.apiUrl),
|
|
3300
|
-
account: {
|
|
3301
|
-
name: creds?.user?.name ?? null,
|
|
3302
|
-
email: creds?.user?.email ?? null,
|
|
3303
|
-
signedIn: Boolean(creds?.accessToken || creds?.mcpToken),
|
|
3304
|
-
hasSession: Boolean(creds?.accessToken),
|
|
3305
|
-
userId: creds?.user?.id
|
|
3306
|
-
},
|
|
3307
|
-
cachedToolCount: cached ? cached.tools.filter((t) => !t.hidden).length : null,
|
|
3308
|
-
async loadPlan() {
|
|
3309
|
-
if (!creds?.accessToken) return null;
|
|
3310
|
-
const state = await ctx.api.json("/api/subscription/state");
|
|
3311
|
-
return state?.snapshot?.tier ?? null;
|
|
3312
|
-
},
|
|
3313
|
-
async loadPendingApprovals() {
|
|
3314
|
-
const state = await getMcpAccessState(ctx.api);
|
|
3315
|
-
return state?.pendingApprovals ?? [];
|
|
3316
|
-
},
|
|
3317
|
-
listThreads: (scope) => listThreads(ctx, scope),
|
|
3318
|
-
listModels: (scope) => listModels(ctx, scope),
|
|
3319
|
-
getThreadMessages: (threadId, scope) => getThreadMessages(ctx, threadId, scope),
|
|
3320
|
-
streamChat: (request, signal) => streamChat(ctx, request, { signal }),
|
|
3321
|
-
decideApproval: (approvalId, decision, scope) => decision === "approve" ? confirmApproval(ctx, approvalId, scope) : rejectApproval(ctx, approvalId, scope),
|
|
3322
|
-
async listTools(opts) {
|
|
3323
|
-
const session = await ctx.mcp();
|
|
3324
|
-
return session.listTools(opts);
|
|
3325
|
-
},
|
|
3326
|
-
async callTool(toolId, input, opts) {
|
|
3327
|
-
const session = await ctx.mcp();
|
|
3328
|
-
const started = Date.now();
|
|
3329
|
-
const result = await session.callTool(toolId, input, opts);
|
|
3330
|
-
return { ...result, durationMs: Date.now() - started };
|
|
3331
|
-
},
|
|
3332
|
-
capabilityPrefs: loadCapabilityPrefs(ctx.profile.name),
|
|
3333
|
-
saveCapabilityPrefs: (capabilities) => saveCapabilityPrefs(ctx.profile.name, capabilities),
|
|
3334
|
-
loadCapabilities: () => ctx.api.json("/api/agent/capabilities"),
|
|
3335
|
-
getArtifact: (artifactId) => getArtifact(ctx.api, artifactId),
|
|
3336
|
-
async artifactPreviewUrl(artifactId, kind) {
|
|
3337
|
-
return (await previewUrl(ctx.api, artifactId, { variant: previewVariantFor(kind) })).url;
|
|
3338
|
-
},
|
|
3339
|
-
hyperlinks: supportsHyperlinks(),
|
|
3340
|
-
async loadStatus() {
|
|
3341
|
-
const { collectStatus } = await import("./status-I5UAQPBY.js");
|
|
3342
|
-
const report = await collectStatus(ctx);
|
|
3343
|
-
const who = report.user ? report.user.name ? `${report.user.name} <${report.user.email}>` : report.user.email : "not signed in";
|
|
3344
|
-
const rows = [
|
|
3345
|
-
["Profile", `${report.profile.name} (${report.profile.source})`],
|
|
3346
|
-
["API", report.profile.apiUrl],
|
|
3347
|
-
["Account", who],
|
|
3348
|
-
["Plan", report.plan],
|
|
3349
|
-
["Session", report.session ? `${report.session.token}${report.session.refreshable ? " \xB7 auto-refresh" : ""}` : null],
|
|
3350
|
-
["Tools via", report.mcp?.label],
|
|
3351
|
-
["Approvals", report.pendingApprovals === null ? null : `${report.pendingApprovals} pending`],
|
|
3352
|
-
["Catalog", report.catalog ? `${report.catalog.tools} tools${report.catalog.fresh ? "" : " (stale)"}` : null],
|
|
3353
|
-
...report.problems.map((p) => ["Problem", p])
|
|
3354
|
-
];
|
|
3355
|
-
return rows.filter((r) => Boolean(r[1])).map(([label, value]) => ({ label, value }));
|
|
3356
|
-
}
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
4565
|
// src/ui/entry.tsx
|
|
3361
|
-
import { jsx as
|
|
4566
|
+
import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
3362
4567
|
function unwrap(outcome) {
|
|
3363
4568
|
if (outcome.status === "interrupted") throw interruptedError();
|
|
3364
4569
|
return outcome.value;
|
|
@@ -3366,7 +4571,7 @@ function unwrap(outcome) {
|
|
|
3366
4571
|
async function runBrowseTools(ctx, tools, options) {
|
|
3367
4572
|
const services = createUiServices(ctx);
|
|
3368
4573
|
unwrap(
|
|
3369
|
-
await renderScreen("The tool browser", ({ finish, width, height }) => /* @__PURE__ */
|
|
4574
|
+
await renderScreen("The tool browser", ({ finish, width, height }) => /* @__PURE__ */ jsx29(
|
|
3370
4575
|
ToolBrowser,
|
|
3371
4576
|
{
|
|
3372
4577
|
tools,
|
|
@@ -3384,23 +4589,23 @@ async function runToolForm(_ctx, tool, initial, options) {
|
|
|
3384
4589
|
const skip = autoFilledProperties(tool, tool.inputSchema);
|
|
3385
4590
|
const state = createFormState(tool.inputSchema, initial, { skip, missingRequired: options.missingRequired });
|
|
3386
4591
|
return unwrap(
|
|
3387
|
-
await renderScreen("The input form", ({ finish, width, height }) => /* @__PURE__ */
|
|
3388
|
-
/* @__PURE__ */
|
|
3389
|
-
/* @__PURE__ */
|
|
4592
|
+
await renderScreen("The input form", ({ finish, width, height }) => /* @__PURE__ */ jsxs24(Box27, { flexDirection: "column", width, children: [
|
|
4593
|
+
/* @__PURE__ */ jsxs24(Box27, { flexDirection: "column", marginBottom: 1, children: [
|
|
4594
|
+
/* @__PURE__ */ jsxs24(Text27, { wrap: "truncate-end", children: [
|
|
3390
4595
|
theme.bold(toolLabel(tool)),
|
|
3391
4596
|
" ",
|
|
3392
4597
|
theme.dim(`${theme.symbols.middot} ${oneLine(tool.title)}`)
|
|
3393
4598
|
] }),
|
|
3394
|
-
options.missingRequired.length ? /* @__PURE__ */
|
|
4599
|
+
options.missingRequired.length ? /* @__PURE__ */ jsx29(Text27, { wrap: "truncate-end", children: theme.dim(`Missing: ${options.missingRequired.join(", ")}`) }) : null
|
|
3395
4600
|
] }),
|
|
3396
|
-
/* @__PURE__ */
|
|
4601
|
+
/* @__PURE__ */ jsx29(
|
|
3397
4602
|
Form,
|
|
3398
4603
|
{
|
|
3399
4604
|
initial: state,
|
|
3400
4605
|
width,
|
|
3401
4606
|
height: Math.max(3, height - 5),
|
|
3402
4607
|
isActive: true,
|
|
3403
|
-
onSubmit: (input) => finish(input, /* @__PURE__ */
|
|
4608
|
+
onSubmit: (input) => finish(input, /* @__PURE__ */ jsx29(Text27, { wrap: "truncate-end", children: theme.dim(`${theme.symbols.pointer} ${equivalentCommand(tool, input, skip)}`) })),
|
|
3404
4609
|
onCancel: () => finish(null)
|
|
3405
4610
|
}
|
|
3406
4611
|
)
|
|
@@ -3411,7 +4616,7 @@ async function runApprovalPrompt(ctx, approval) {
|
|
|
3411
4616
|
const shownAt = Date.now();
|
|
3412
4617
|
const title = sentenceCase(approval.toolName || "this action");
|
|
3413
4618
|
return unwrap(
|
|
3414
|
-
await renderScreen("The approval prompt", ({ finish, width }) => /* @__PURE__ */
|
|
4619
|
+
await renderScreen("The approval prompt", ({ finish, width }) => /* @__PURE__ */ jsx29(
|
|
3415
4620
|
ApprovalCard,
|
|
3416
4621
|
{
|
|
3417
4622
|
approvalId: approval.approvalId,
|
|
@@ -3422,21 +4627,21 @@ async function runApprovalPrompt(ctx, approval) {
|
|
|
3422
4627
|
context: `Writes to your account ${theme.symbols.middot} profile ${ctx.profile.name}`,
|
|
3423
4628
|
isActive: true,
|
|
3424
4629
|
width,
|
|
3425
|
-
onDecision: (decision) => finish(decision, /* @__PURE__ */
|
|
4630
|
+
onDecision: (decision) => finish(decision, /* @__PURE__ */ jsx29(Text27, { children: approvalOutcomeText(decision, title, approval.approvalId, Date.now() - shownAt) }))
|
|
3426
4631
|
}
|
|
3427
4632
|
))
|
|
3428
4633
|
);
|
|
3429
4634
|
}
|
|
3430
4635
|
function PrintOnce({ children, onPrinted }) {
|
|
3431
4636
|
const { columns } = useTerminalSize();
|
|
3432
|
-
|
|
4637
|
+
useEffect12(() => {
|
|
3433
4638
|
onPrinted();
|
|
3434
4639
|
}, [onPrinted]);
|
|
3435
|
-
return /* @__PURE__ */
|
|
4640
|
+
return /* @__PURE__ */ jsx29(Static2, { items: ["once"], children: (key) => /* @__PURE__ */ jsx29(Page, { columns, children }, key) });
|
|
3436
4641
|
}
|
|
3437
4642
|
async function runShowResult(_ctx, tool, result, meta) {
|
|
3438
4643
|
unwrap(
|
|
3439
|
-
await renderScreen("The result panel", ({ finish, width, height }) => /* @__PURE__ */
|
|
4644
|
+
await renderScreen("The result panel", ({ finish, width, height }) => /* @__PURE__ */ jsx29(PrintOnce, { onPrinted: () => finish(void 0), children: /* @__PURE__ */ jsx29(
|
|
3440
4645
|
ResultPanel,
|
|
3441
4646
|
{
|
|
3442
4647
|
title: toolLabel(tool),
|
|
@@ -3452,35 +4657,83 @@ async function runShowResult(_ctx, tool, result, meta) {
|
|
|
3452
4657
|
}
|
|
3453
4658
|
async function runHome(ctx, opts = {}) {
|
|
3454
4659
|
const services = createUiServices(ctx);
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
4660
|
+
try {
|
|
4661
|
+
await renderScreen(
|
|
4662
|
+
"The home screen",
|
|
4663
|
+
({ finish, width, height }) => /* @__PURE__ */ jsx29(
|
|
4664
|
+
Home,
|
|
4665
|
+
{
|
|
4666
|
+
services,
|
|
4667
|
+
width,
|
|
4668
|
+
height,
|
|
4669
|
+
mode: opts.mode,
|
|
4670
|
+
companyId: opts.companyId,
|
|
4671
|
+
saveArtifacts: opts.saveArtifacts,
|
|
4672
|
+
onExit: () => finish(void 0)
|
|
4673
|
+
}
|
|
4674
|
+
),
|
|
4675
|
+
{ ctrlC: "screen" }
|
|
4676
|
+
);
|
|
4677
|
+
} finally {
|
|
4678
|
+
await closeUiContexts();
|
|
4679
|
+
}
|
|
3460
4680
|
}
|
|
3461
|
-
|
|
3462
|
-
const services =
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4681
|
+
function ChatHost(props) {
|
|
4682
|
+
const [services, setServices] = useState11(props.services);
|
|
4683
|
+
const [signedOut, setSignedOut] = useState11(null);
|
|
4684
|
+
const [sessionGeneration, setSessionGeneration] = useState11(0);
|
|
4685
|
+
if (signedOut) {
|
|
4686
|
+
return /* @__PURE__ */ jsx29(
|
|
4687
|
+
Home,
|
|
3467
4688
|
{
|
|
3468
4689
|
services,
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
height,
|
|
3477
|
-
onExit: () => finish(void 0)
|
|
4690
|
+
width: props.width,
|
|
4691
|
+
height: props.height,
|
|
4692
|
+
mode: props.mode,
|
|
4693
|
+
companyId: props.companyId,
|
|
4694
|
+
saveArtifacts: props.saveArtifacts,
|
|
4695
|
+
initialNotice: accountNotice(signedOut),
|
|
4696
|
+
onExit: props.onExit
|
|
3478
4697
|
}
|
|
3479
|
-
)
|
|
3480
|
-
|
|
4698
|
+
);
|
|
4699
|
+
}
|
|
4700
|
+
return /* @__PURE__ */ jsx29(
|
|
4701
|
+
Chat,
|
|
4702
|
+
{
|
|
4703
|
+
services,
|
|
4704
|
+
mode: props.mode,
|
|
4705
|
+
companyId: props.companyId,
|
|
4706
|
+
threadId: sessionGeneration === 0 ? props.threadId : void 0,
|
|
4707
|
+
modelId: props.modelId,
|
|
4708
|
+
initialMessage: sessionGeneration === 0 ? props.message : void 0,
|
|
4709
|
+
capabilities: props.capabilities,
|
|
4710
|
+
saveArtifacts: props.saveArtifacts,
|
|
4711
|
+
width: props.width,
|
|
4712
|
+
height: props.height,
|
|
4713
|
+
onAccountChange: (change) => {
|
|
4714
|
+
if (accountIdentity(services) !== accountIdentity(change.services)) setSessionGeneration((generation) => generation + 1);
|
|
4715
|
+
setServices(change.services);
|
|
4716
|
+
if (change.signedOut) setSignedOut(change);
|
|
4717
|
+
},
|
|
4718
|
+
onExit: () => props.onExit()
|
|
4719
|
+
},
|
|
4720
|
+
sessionGeneration
|
|
3481
4721
|
);
|
|
3482
4722
|
}
|
|
4723
|
+
async function runChat(ctx, opts) {
|
|
4724
|
+
const services = createUiServices(ctx);
|
|
4725
|
+
try {
|
|
4726
|
+
await renderScreen(
|
|
4727
|
+
"Chat",
|
|
4728
|
+
({ finish, width, height }) => /* @__PURE__ */ jsx29(ChatHost, { ...opts, services, width, height, onExit: () => finish(void 0) }),
|
|
4729
|
+
{ ctrlC: "screen" }
|
|
4730
|
+
);
|
|
4731
|
+
} finally {
|
|
4732
|
+
await closeUiContexts();
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
3483
4735
|
export {
|
|
4736
|
+
ChatHost,
|
|
3484
4737
|
PrintOnce,
|
|
3485
4738
|
runApprovalPrompt,
|
|
3486
4739
|
runBrowseTools,
|
|
@@ -3489,4 +4742,4 @@ export {
|
|
|
3489
4742
|
runShowResult,
|
|
3490
4743
|
runToolForm
|
|
3491
4744
|
};
|
|
3492
|
-
//# sourceMappingURL=entry-
|
|
4745
|
+
//# sourceMappingURL=entry-ZAXWZSEE.js.map
|