@vellumai/cli 0.4.2 → 0.4.4
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/package.json +1 -1
- package/src/adapters/install.sh +38 -3
- package/src/commands/hatch.ts +21 -6
- package/src/components/DefaultMainScreen.tsx +84 -49
- package/src/lib/assistant-config.ts +1 -1
- package/src/lib/local.ts +28 -1
package/package.json
CHANGED
package/src/adapters/install.sh
CHANGED
|
@@ -14,13 +14,23 @@ success() { printf "${GREEN}${BOLD}==>${RESET} ${BOLD}%s${RESET}\n" "$1"; }
|
|
|
14
14
|
error() { printf "${RED}error:${RESET} %s\n" "$1" >&2; }
|
|
15
15
|
|
|
16
16
|
ensure_git() {
|
|
17
|
-
|
|
17
|
+
# On macOS, /usr/bin/git is a shim that triggers an "Install Command Line
|
|
18
|
+
# Developer Tools" popup instead of running git. Check that git actually
|
|
19
|
+
# works, not just that the binary exists.
|
|
20
|
+
if command -v git >/dev/null 2>&1 && git --version >/dev/null 2>&1; then
|
|
18
21
|
success "git already installed ($(git --version))"
|
|
19
22
|
return
|
|
20
23
|
fi
|
|
21
24
|
|
|
22
25
|
info "Installing git..."
|
|
23
|
-
if
|
|
26
|
+
if [ "$(uname -s)" = "Darwin" ]; then
|
|
27
|
+
if command -v brew >/dev/null 2>&1; then
|
|
28
|
+
brew install git
|
|
29
|
+
else
|
|
30
|
+
error "git is required. Install Homebrew (https://brew.sh) then run: brew install git"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
elif command -v apt-get >/dev/null 2>&1; then
|
|
24
34
|
sudo apt-get update -qq && sudo apt-get install -y -qq git
|
|
25
35
|
elif command -v yum >/dev/null 2>&1; then
|
|
26
36
|
sudo yum install -y git
|
|
@@ -31,7 +41,11 @@ ensure_git() {
|
|
|
31
41
|
exit 1
|
|
32
42
|
fi
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
# Clear bash's command hash so it finds the newly installed git binary
|
|
45
|
+
# instead of the cached path to the macOS /usr/bin/git shim.
|
|
46
|
+
hash -r 2>/dev/null || true
|
|
47
|
+
|
|
48
|
+
if ! git --version >/dev/null 2>&1; then
|
|
35
49
|
error "git installation failed. Please install manually."
|
|
36
50
|
exit 1
|
|
37
51
|
fi
|
|
@@ -155,6 +169,22 @@ symlink_vellum() {
|
|
|
155
169
|
return 0
|
|
156
170
|
}
|
|
157
171
|
|
|
172
|
+
# Write a small sourceable env file to ~/.config/vellum/env so callers can
|
|
173
|
+
# pick up PATH changes without restarting their shell:
|
|
174
|
+
# curl -fsSL https://assistant.vellum.ai/install.sh | bash && . ~/.config/vellum/env
|
|
175
|
+
write_env_file() {
|
|
176
|
+
local env_dir="${XDG_CONFIG_HOME:-$HOME/.config}/vellum"
|
|
177
|
+
local env_file="$env_dir/env"
|
|
178
|
+
mkdir -p "$env_dir"
|
|
179
|
+
cat > "$env_file" <<'ENVEOF'
|
|
180
|
+
export BUN_INSTALL="$HOME/.bun"
|
|
181
|
+
case ":$PATH:" in
|
|
182
|
+
*":$BUN_INSTALL/bin:"*) ;;
|
|
183
|
+
*) export PATH="$BUN_INSTALL/bin:$PATH" ;;
|
|
184
|
+
esac
|
|
185
|
+
ENVEOF
|
|
186
|
+
}
|
|
187
|
+
|
|
158
188
|
install_vellum() {
|
|
159
189
|
if command -v vellum >/dev/null 2>&1; then
|
|
160
190
|
info "Updating vellum to latest..."
|
|
@@ -183,6 +213,11 @@ main() {
|
|
|
183
213
|
install_vellum
|
|
184
214
|
symlink_vellum
|
|
185
215
|
|
|
216
|
+
# Write a sourceable env file so the quickstart one-liner can pick up
|
|
217
|
+
# PATH changes in the caller's shell:
|
|
218
|
+
# curl ... | bash && . ~/.config/vellum/env
|
|
219
|
+
write_env_file
|
|
220
|
+
|
|
186
221
|
# Source the shell profile so vellum hatch runs with the correct PATH
|
|
187
222
|
# in this session (the profile changes only take effect in new shells
|
|
188
223
|
# otherwise).
|
package/src/commands/hatch.ts
CHANGED
|
@@ -145,6 +145,7 @@ interface HatchArgs {
|
|
|
145
145
|
name: string | null;
|
|
146
146
|
remote: RemoteHost;
|
|
147
147
|
daemonOnly: boolean;
|
|
148
|
+
restart: boolean;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
function parseArgs(): HatchArgs {
|
|
@@ -154,6 +155,7 @@ function parseArgs(): HatchArgs {
|
|
|
154
155
|
let name: string | null = null;
|
|
155
156
|
let remote: RemoteHost = DEFAULT_REMOTE;
|
|
156
157
|
let daemonOnly = false;
|
|
158
|
+
let restart = false;
|
|
157
159
|
|
|
158
160
|
for (let i = 0; i < args.length; i++) {
|
|
159
161
|
const arg = args[i];
|
|
@@ -171,11 +173,14 @@ function parseArgs(): HatchArgs {
|
|
|
171
173
|
console.log(" --name <name> Custom instance name");
|
|
172
174
|
console.log(" --remote <host> Remote host (local, gcp, aws, custom)");
|
|
173
175
|
console.log(" --daemon-only Start daemon only, skip gateway");
|
|
176
|
+
console.log(" --restart Restart processes without onboarding side effects");
|
|
174
177
|
process.exit(0);
|
|
175
178
|
} else if (arg === "-d") {
|
|
176
179
|
detached = true;
|
|
177
180
|
} else if (arg === "--daemon-only") {
|
|
178
181
|
daemonOnly = true;
|
|
182
|
+
} else if (arg === "--restart") {
|
|
183
|
+
restart = true;
|
|
179
184
|
} else if (arg === "--name") {
|
|
180
185
|
const next = args[i + 1];
|
|
181
186
|
if (!next || next.startsWith("-")) {
|
|
@@ -204,13 +209,13 @@ function parseArgs(): HatchArgs {
|
|
|
204
209
|
species = arg as Species;
|
|
205
210
|
} else {
|
|
206
211
|
console.error(
|
|
207
|
-
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --daemon-only, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>`,
|
|
212
|
+
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --daemon-only, --restart, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>`,
|
|
208
213
|
);
|
|
209
214
|
process.exit(1);
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
|
|
213
|
-
return { species, detached, name, remote, daemonOnly };
|
|
218
|
+
return { species, detached, name, remote, daemonOnly, restart };
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
function formatElapsed(ms: number): string {
|
|
@@ -545,7 +550,12 @@ async function displayPairingQRCode(runtimeUrl: string, bearerToken: string | un
|
|
|
545
550
|
}
|
|
546
551
|
}
|
|
547
552
|
|
|
548
|
-
async function hatchLocal(species: Species, name: string | null, daemonOnly: boolean = false): Promise<void> {
|
|
553
|
+
async function hatchLocal(species: Species, name: string | null, daemonOnly: boolean = false, restart: boolean = false): Promise<void> {
|
|
554
|
+
if (restart && !name && !process.env.VELLUM_ASSISTANT_NAME) {
|
|
555
|
+
console.error("Error: Cannot restart without a known assistant ID. Provide --name or ensure VELLUM_ASSISTANT_NAME is set.");
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
|
|
549
559
|
const instanceName =
|
|
550
560
|
name ?? process.env.VELLUM_ASSISTANT_NAME ?? `${species}-${generateRandomSuffix()}`;
|
|
551
561
|
|
|
@@ -627,7 +637,7 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
|
|
|
627
637
|
species,
|
|
628
638
|
hatchedAt: new Date().toISOString(),
|
|
629
639
|
};
|
|
630
|
-
if (!daemonOnly) {
|
|
640
|
+
if (!daemonOnly && !restart) {
|
|
631
641
|
saveAssistantEntry(localEntry);
|
|
632
642
|
syncConfigToLockfile();
|
|
633
643
|
|
|
@@ -656,10 +666,15 @@ export async function hatch(): Promise<void> {
|
|
|
656
666
|
const cliVersion = getCliVersion();
|
|
657
667
|
console.log(`@vellumai/cli v${cliVersion}`);
|
|
658
668
|
|
|
659
|
-
const { species, detached, name, remote, daemonOnly } = parseArgs();
|
|
669
|
+
const { species, detached, name, remote, daemonOnly, restart } = parseArgs();
|
|
670
|
+
|
|
671
|
+
if (restart && remote !== "local") {
|
|
672
|
+
console.error("Error: --restart is only supported for local hatch targets.");
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
660
675
|
|
|
661
676
|
if (remote === "local") {
|
|
662
|
-
await hatchLocal(species, name, daemonOnly);
|
|
677
|
+
await hatchLocal(species, name, daemonOnly, restart);
|
|
663
678
|
return;
|
|
664
679
|
}
|
|
665
680
|
|
|
@@ -32,6 +32,51 @@ const POLL_INTERVAL_MS = 3000;
|
|
|
32
32
|
const SEND_TIMEOUT_MS = 5000;
|
|
33
33
|
const RESPONSE_POLL_INTERVAL_MS = 1000;
|
|
34
34
|
|
|
35
|
+
// ── Layout constants ──────────────────────────────────────
|
|
36
|
+
const MAX_TOTAL_WIDTH = 72;
|
|
37
|
+
const DEFAULT_TERMINAL_COLUMNS = 80;
|
|
38
|
+
const DEFAULT_TERMINAL_ROWS = 24;
|
|
39
|
+
const LEFT_PANEL_WIDTH = 36;
|
|
40
|
+
|
|
41
|
+
const HEADER_PREFIX = "── Vellum ";
|
|
42
|
+
|
|
43
|
+
// Left panel structure: HEADER lines + art + FOOTER lines
|
|
44
|
+
const LEFT_HEADER_LINES = 3; // spacer + heading + spacer
|
|
45
|
+
const LEFT_FOOTER_LINES = 3; // spacer + runtimeUrl + dirName
|
|
46
|
+
|
|
47
|
+
// Right panel structure
|
|
48
|
+
const TIPS = ["Send a message to start chatting", "Use /help to see available commands"];
|
|
49
|
+
const RIGHT_PANEL_INFO_SECTIONS = 3; // Assistant, Species, Status — each with heading + value
|
|
50
|
+
const RIGHT_PANEL_SPACERS = 2; // top spacer + spacer between tips and info
|
|
51
|
+
const RIGHT_PANEL_TIPS_HEADING = 1;
|
|
52
|
+
const RIGHT_PANEL_LINE_COUNT =
|
|
53
|
+
RIGHT_PANEL_SPACERS + RIGHT_PANEL_TIPS_HEADING + TIPS.length + RIGHT_PANEL_INFO_SECTIONS * 2;
|
|
54
|
+
|
|
55
|
+
// Header chrome (borders around panel content)
|
|
56
|
+
const HEADER_TOP_BORDER_LINES = 1; // "── Vellum ───..." line
|
|
57
|
+
const HEADER_BOTTOM_BORDER_LINES = 2; // bottom rule + blank line
|
|
58
|
+
const HEADER_CHROME_LINES = HEADER_TOP_BORDER_LINES + HEADER_BOTTOM_BORDER_LINES;
|
|
59
|
+
|
|
60
|
+
// Selection / Secret windows
|
|
61
|
+
const DIALOG_WINDOW_WIDTH = 60;
|
|
62
|
+
const DIALOG_TITLE_CHROME = 5; // "┌─ " (3) + " " (1) + "┐" (1)
|
|
63
|
+
const DIALOG_BORDER_CORNERS = 2; // └ and ┘
|
|
64
|
+
const SELECTION_OPTION_CHROME = 6; // "│ " (2) + marker (1) + " " (1) + padding‐adjust + "│" (1)
|
|
65
|
+
const SECRET_CONTENT_CHROME = 4; // "│ " (2) + padding + "│" (1) + adjustment
|
|
66
|
+
|
|
67
|
+
// Chat area heights
|
|
68
|
+
const TOOLTIP_HEIGHT = 3;
|
|
69
|
+
const INPUT_AREA_HEIGHT = 4; // separator + input row + separator + hint
|
|
70
|
+
const SELECTION_CHROME_LINES = 3; // title bar + bottom border + spacing
|
|
71
|
+
const SECRET_INPUT_HEIGHT = 5; // title bar + content row + bottom border + tooltip chrome
|
|
72
|
+
const SPINNER_HEIGHT = 1;
|
|
73
|
+
const MIN_FEED_ROWS = 3;
|
|
74
|
+
|
|
75
|
+
// Feed item height estimation
|
|
76
|
+
const TOOL_CALL_CHROME_LINES = 2; // header (┌) + footer (└)
|
|
77
|
+
const MESSAGE_SPACING = 1;
|
|
78
|
+
const HELP_DISPLAY_HEIGHT = 6;
|
|
79
|
+
|
|
35
80
|
interface ListMessagesResponse {
|
|
36
81
|
messages: RuntimeMessage[];
|
|
37
82
|
nextCursor?: string;
|
|
@@ -598,12 +643,10 @@ function DefaultMainScreen({
|
|
|
598
643
|
const accentColor = species === "openclaw" ? "red" : "magenta";
|
|
599
644
|
|
|
600
645
|
const { stdout } = useStdout();
|
|
601
|
-
const terminalColumns = stdout.columns ||
|
|
602
|
-
const totalWidth = Math.min(
|
|
646
|
+
const terminalColumns = stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
647
|
+
const totalWidth = Math.min(MAX_TOTAL_WIDTH, terminalColumns);
|
|
603
648
|
const rightPanelWidth = Math.max(1, totalWidth - LEFT_PANEL_WIDTH);
|
|
604
649
|
|
|
605
|
-
const tips = ["Send a message to start chatting", "Use /help to see available commands"];
|
|
606
|
-
|
|
607
650
|
const leftLines = [
|
|
608
651
|
" ",
|
|
609
652
|
" Meet your Assistant!",
|
|
@@ -617,7 +660,7 @@ function DefaultMainScreen({
|
|
|
617
660
|
const rightLines: StyledLine[] = [
|
|
618
661
|
{ text: " ", style: "normal" },
|
|
619
662
|
{ text: "Tips for getting started", style: "heading" },
|
|
620
|
-
...
|
|
663
|
+
...TIPS.map((t) => ({ text: t, style: "normal" as const })),
|
|
621
664
|
{ text: " ", style: "normal" },
|
|
622
665
|
{ text: "Assistant", style: "heading" },
|
|
623
666
|
{ text: assistantId, style: "dim" },
|
|
@@ -631,7 +674,7 @@ function DefaultMainScreen({
|
|
|
631
674
|
|
|
632
675
|
return (
|
|
633
676
|
<Box flexDirection="column" width={totalWidth}>
|
|
634
|
-
<Text dimColor>{
|
|
677
|
+
<Text dimColor>{HEADER_PREFIX + "─".repeat(Math.max(0, totalWidth - HEADER_PREFIX.length))}</Text>
|
|
635
678
|
<Box flexDirection="row">
|
|
636
679
|
<Box flexDirection="column" width={LEFT_PANEL_WIDTH}>
|
|
637
680
|
{Array.from({ length: maxLines }, (_, i) => {
|
|
@@ -684,15 +727,10 @@ function DefaultMainScreen({
|
|
|
684
727
|
</Box>
|
|
685
728
|
<Text dimColor>{"─".repeat(totalWidth)}</Text>
|
|
686
729
|
<Text> </Text>
|
|
687
|
-
<Tooltip text="Type ? or /help for available commands" delay={1000}>
|
|
688
|
-
<Text dimColor> ? for shortcuts</Text>
|
|
689
|
-
</Tooltip>
|
|
690
|
-
<Text> </Text>
|
|
691
730
|
</Box>
|
|
692
731
|
);
|
|
693
732
|
}
|
|
694
733
|
|
|
695
|
-
const LEFT_PANEL_WIDTH = 36;
|
|
696
734
|
|
|
697
735
|
export interface SelectionRequest {
|
|
698
736
|
title: string;
|
|
@@ -737,13 +775,21 @@ function estimateItemHeight(item: FeedItem, terminalColumns: number): number {
|
|
|
737
775
|
for (const tc of item.toolCalls) {
|
|
738
776
|
const paramCount =
|
|
739
777
|
typeof tc.input === "object" && tc.input ? Object.keys(tc.input).length : 0;
|
|
740
|
-
lines +=
|
|
778
|
+
lines += TOOL_CALL_CHROME_LINES + paramCount + (tc.result !== undefined ? 1 : 0);
|
|
741
779
|
}
|
|
742
780
|
}
|
|
743
|
-
return lines +
|
|
781
|
+
return lines + MESSAGE_SPACING;
|
|
744
782
|
}
|
|
745
783
|
if (item.type === "help") {
|
|
746
|
-
return
|
|
784
|
+
return HELP_DISPLAY_HEIGHT;
|
|
785
|
+
}
|
|
786
|
+
if (item.type === "status" || item.type === "error") {
|
|
787
|
+
const cols = Math.max(1, terminalColumns);
|
|
788
|
+
let lines = 0;
|
|
789
|
+
for (const line of item.text.split("\n")) {
|
|
790
|
+
lines += Math.max(1, Math.ceil(line.length / cols));
|
|
791
|
+
}
|
|
792
|
+
return lines;
|
|
747
793
|
}
|
|
748
794
|
return 1;
|
|
749
795
|
}
|
|
@@ -751,10 +797,9 @@ function estimateItemHeight(item: FeedItem, terminalColumns: number): number {
|
|
|
751
797
|
function calculateHeaderHeight(species: Species): number {
|
|
752
798
|
const config = SPECIES_CONFIG[species];
|
|
753
799
|
const artLength = config.art.length;
|
|
754
|
-
const leftLineCount =
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
return maxLines + 5;
|
|
800
|
+
const leftLineCount = LEFT_HEADER_LINES + artLength + LEFT_FOOTER_LINES;
|
|
801
|
+
const maxLines = Math.max(leftLineCount, RIGHT_PANEL_LINE_COUNT);
|
|
802
|
+
return maxLines + HEADER_CHROME_LINES;
|
|
758
803
|
}
|
|
759
804
|
|
|
760
805
|
const SCROLL_STEP = 5;
|
|
@@ -763,9 +808,8 @@ export function render(runtimeUrl: string, assistantId: string, species: Species
|
|
|
763
808
|
const config = SPECIES_CONFIG[species];
|
|
764
809
|
const art = config.art;
|
|
765
810
|
|
|
766
|
-
const leftLineCount =
|
|
767
|
-
const
|
|
768
|
-
const maxLines = Math.max(leftLineCount, rightLineCount);
|
|
811
|
+
const leftLineCount = LEFT_HEADER_LINES + art.length + LEFT_FOOTER_LINES;
|
|
812
|
+
const maxLines = Math.max(leftLineCount, RIGHT_PANEL_LINE_COUNT);
|
|
769
813
|
|
|
770
814
|
const { unmount } = inkRender(
|
|
771
815
|
<DefaultMainScreen runtimeUrl={runtimeUrl} assistantId={assistantId} species={species} />,
|
|
@@ -773,7 +817,7 @@ export function render(runtimeUrl: string, assistantId: string, species: Species
|
|
|
773
817
|
);
|
|
774
818
|
unmount();
|
|
775
819
|
|
|
776
|
-
const statusCanvasLine =
|
|
820
|
+
const statusCanvasLine = RIGHT_PANEL_LINE_COUNT + HEADER_TOP_BORDER_LINES;
|
|
777
821
|
const statusCol = LEFT_PANEL_WIDTH + 1;
|
|
778
822
|
checkHealth(runtimeUrl)
|
|
779
823
|
.then((health) => {
|
|
@@ -784,7 +828,7 @@ export function render(runtimeUrl: string, assistantId: string, species: Species
|
|
|
784
828
|
})
|
|
785
829
|
.catch(() => {});
|
|
786
830
|
|
|
787
|
-
return
|
|
831
|
+
return maxLines + HEADER_CHROME_LINES;
|
|
788
832
|
}
|
|
789
833
|
|
|
790
834
|
interface SelectionWindowProps {
|
|
@@ -825,15 +869,14 @@ function SelectionWindow({
|
|
|
825
869
|
},
|
|
826
870
|
);
|
|
827
871
|
|
|
828
|
-
const
|
|
829
|
-
const borderH = "\u2500".repeat(Math.max(0, windowWidth - title.length - 5));
|
|
872
|
+
const borderH = "\u2500".repeat(Math.max(0, DIALOG_WINDOW_WIDTH - title.length - DIALOG_TITLE_CHROME));
|
|
830
873
|
|
|
831
874
|
return (
|
|
832
|
-
<Box flexDirection="column" width={
|
|
875
|
+
<Box flexDirection="column" width={DIALOG_WINDOW_WIDTH}>
|
|
833
876
|
<Text>{"\u250C\u2500 " + title + " " + borderH + "\u2510"}</Text>
|
|
834
877
|
{options.map((option, i) => {
|
|
835
878
|
const marker = i === selectedIndex ? "\u276F" : " ";
|
|
836
|
-
const padding = " ".repeat(Math.max(0,
|
|
879
|
+
const padding = " ".repeat(Math.max(0, DIALOG_WINDOW_WIDTH - option.length - SELECTION_OPTION_CHROME));
|
|
837
880
|
return (
|
|
838
881
|
<Text key={i}>
|
|
839
882
|
{"\u2502 "}
|
|
@@ -844,7 +887,7 @@ function SelectionWindow({
|
|
|
844
887
|
</Text>
|
|
845
888
|
);
|
|
846
889
|
})}
|
|
847
|
-
<Text>{"\u2514" + "\u2500".repeat(
|
|
890
|
+
<Text>{"\u2514" + "\u2500".repeat(DIALOG_WINDOW_WIDTH - DIALOG_BORDER_CORNERS) + "\u2518"}</Text>
|
|
848
891
|
<Tooltip text="\u2191/\u2193 navigate Enter select Esc cancel" delay={1000} />
|
|
849
892
|
</Box>
|
|
850
893
|
);
|
|
@@ -888,15 +931,14 @@ function SecretInputWindow({
|
|
|
888
931
|
},
|
|
889
932
|
);
|
|
890
933
|
|
|
891
|
-
const
|
|
892
|
-
const borderH = "\u2500".repeat(Math.max(0, windowWidth - label.length - 5));
|
|
934
|
+
const borderH = "\u2500".repeat(Math.max(0, DIALOG_WINDOW_WIDTH - label.length - DIALOG_TITLE_CHROME));
|
|
893
935
|
const masked = "\u2022".repeat(value.length);
|
|
894
936
|
const displayText = value.length > 0 ? masked : (placeholder ?? "Enter secret...");
|
|
895
937
|
const displayColor = value.length > 0 ? undefined : "gray";
|
|
896
|
-
const contentPad = " ".repeat(Math.max(0,
|
|
938
|
+
const contentPad = " ".repeat(Math.max(0, DIALOG_WINDOW_WIDTH - displayText.length - SECRET_CONTENT_CHROME));
|
|
897
939
|
|
|
898
940
|
return (
|
|
899
|
-
<Box flexDirection="column" width={
|
|
941
|
+
<Box flexDirection="column" width={DIALOG_WINDOW_WIDTH}>
|
|
900
942
|
<Text>{"\u250C\u2500 " + label + " " + borderH + "\u2510"}</Text>
|
|
901
943
|
<Text>
|
|
902
944
|
{"\u2502 "}
|
|
@@ -904,7 +946,7 @@ function SecretInputWindow({
|
|
|
904
946
|
{contentPad}
|
|
905
947
|
{"\u2502"}
|
|
906
948
|
</Text>
|
|
907
|
-
<Text>{"\u2514" + "\u2500".repeat(
|
|
949
|
+
<Text>{"\u2514" + "\u2500".repeat(DIALOG_WINDOW_WIDTH - DIALOG_BORDER_CORNERS) + "\u2518"}</Text>
|
|
908
950
|
<Tooltip text="Enter submit Esc cancel" delay={1000} />
|
|
909
951
|
</Box>
|
|
910
952
|
);
|
|
@@ -985,20 +1027,18 @@ function ChatApp({
|
|
|
985
1027
|
const handleRef_ = useRef<ChatAppHandle | null>(null);
|
|
986
1028
|
|
|
987
1029
|
const { stdout } = useStdout();
|
|
988
|
-
const terminalRows = stdout.rows ||
|
|
989
|
-
const terminalColumns = stdout.columns ||
|
|
1030
|
+
const terminalRows = stdout.rows || DEFAULT_TERMINAL_ROWS;
|
|
1031
|
+
const terminalColumns = stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
990
1032
|
const headerHeight = calculateHeaderHeight(species);
|
|
991
|
-
|
|
992
|
-
const showTooltip = inputFocused && inputValue.length === 0;
|
|
993
|
-
const tooltipHeight = showTooltip ? tooltipBubbleHeight : 0;
|
|
1033
|
+
|
|
994
1034
|
const bottomHeight = selection
|
|
995
|
-
? selection.options.length +
|
|
1035
|
+
? selection.options.length + SELECTION_CHROME_LINES + TOOLTIP_HEIGHT
|
|
996
1036
|
: secretInput
|
|
997
|
-
?
|
|
1037
|
+
? SECRET_INPUT_HEIGHT + TOOLTIP_HEIGHT
|
|
998
1038
|
: spinnerText
|
|
999
|
-
?
|
|
1000
|
-
:
|
|
1001
|
-
const availableRows = Math.max(
|
|
1039
|
+
? SPINNER_HEIGHT + INPUT_AREA_HEIGHT
|
|
1040
|
+
: INPUT_AREA_HEIGHT;
|
|
1041
|
+
const availableRows = Math.max(MIN_FEED_ROWS, terminalRows - headerHeight - bottomHeight);
|
|
1002
1042
|
|
|
1003
1043
|
const addMessage = useCallback((msg: RuntimeMessage) => {
|
|
1004
1044
|
setFeed((prev) => [...prev, msg]);
|
|
@@ -1823,12 +1863,6 @@ function ChatApp({
|
|
|
1823
1863
|
|
|
1824
1864
|
{!selection && !secretInput ? (
|
|
1825
1865
|
<Box flexDirection="column">
|
|
1826
|
-
<Tooltip
|
|
1827
|
-
text="Type a message or /help for commands"
|
|
1828
|
-
visible={inputFocused && inputValue.length === 0}
|
|
1829
|
-
position="above"
|
|
1830
|
-
delay={1000}
|
|
1831
|
-
/>
|
|
1832
1866
|
<Text dimColor>{"\u2500".repeat(terminalColumns)}</Text>
|
|
1833
1867
|
<Box paddingLeft={1}>
|
|
1834
1868
|
<Text color="green" bold>
|
|
@@ -1843,6 +1877,7 @@ function ChatApp({
|
|
|
1843
1877
|
/>
|
|
1844
1878
|
</Box>
|
|
1845
1879
|
<Text dimColor>{"\u2500".repeat(terminalColumns)}</Text>
|
|
1880
|
+
<Text dimColor> ? for shortcuts</Text>
|
|
1846
1881
|
</Box>
|
|
1847
1882
|
) : null}
|
|
1848
1883
|
</Box>
|
|
@@ -99,7 +99,7 @@ export function loadAllAssistants(): AssistantEntry[] {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export function saveAssistantEntry(entry: AssistantEntry): void {
|
|
102
|
-
const entries = readAssistants();
|
|
102
|
+
const entries = readAssistants().filter((e) => e.assistantId !== entry.assistantId);
|
|
103
103
|
entries.unshift(entry);
|
|
104
104
|
writeAssistants(entries);
|
|
105
105
|
}
|
package/src/lib/local.ts
CHANGED
|
@@ -557,8 +557,35 @@ export async function startGateway(assistantId?: string): Promise<string> {
|
|
|
557
557
|
writeFileSync(join(vellumDir, "gateway.pid"), String(gateway.pid), "utf-8");
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
const gatewayUrl = publicUrl || `http://localhost:${GATEWAY_PORT}`;
|
|
561
|
+
|
|
562
|
+
// Wait for the gateway to be responsive before returning. Without this,
|
|
563
|
+
// callers (e.g. displayPairingQRCode) may try to connect before the HTTP
|
|
564
|
+
// server is listening and get connection-refused errors.
|
|
565
|
+
const start = Date.now();
|
|
566
|
+
const timeoutMs = 30000;
|
|
567
|
+
let ready = false;
|
|
568
|
+
while (Date.now() - start < timeoutMs) {
|
|
569
|
+
try {
|
|
570
|
+
const res = await fetch(`http://localhost:${GATEWAY_PORT}/healthz`, {
|
|
571
|
+
signal: AbortSignal.timeout(2000),
|
|
572
|
+
});
|
|
573
|
+
if (res.ok) {
|
|
574
|
+
ready = true;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
// Gateway not ready yet
|
|
579
|
+
}
|
|
580
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (!ready) {
|
|
584
|
+
console.warn("⚠ Gateway started but health check did not respond within 30s");
|
|
585
|
+
}
|
|
586
|
+
|
|
560
587
|
console.log("✅ Gateway started\n");
|
|
561
|
-
return
|
|
588
|
+
return gatewayUrl;
|
|
562
589
|
}
|
|
563
590
|
|
|
564
591
|
/**
|