@stigg/terminal 0.0.1-alpha
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/LICENSE +15 -0
- package/README.md +47 -0
- package/dist/api/client.d.ts +6 -0
- package/dist/api/client.js +48 -0
- package/dist/api/format-key.d.ts +7 -0
- package/dist/api/format-key.js +12 -0
- package/dist/api/graphql-client.d.ts +5 -0
- package/dist/api/graphql-client.js +44 -0
- package/dist/api/operations.d.ts +65 -0
- package/dist/api/operations.js +77 -0
- package/dist/api/types.d.ts +18 -0
- package/dist/api/types.js +1 -0
- package/dist/auth/callback-server.d.ts +14 -0
- package/dist/auth/callback-server.js +145 -0
- package/dist/auth/config.d.ts +2 -0
- package/dist/auth/config.js +24 -0
- package/dist/auth/oauth.d.ts +17 -0
- package/dist/auth/oauth.js +94 -0
- package/dist/auth/storage.d.ts +6 -0
- package/dist/auth/storage.js +34 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +79 -0
- package/dist/commands/dash.d.ts +1 -0
- package/dist/commands/dash.js +24 -0
- package/dist/commands/debug.d.ts +1 -0
- package/dist/commands/debug.js +53 -0
- package/dist/commands/env.d.ts +1 -0
- package/dist/commands/env.js +15 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +59 -0
- package/dist/commands/mcp.d.ts +7 -0
- package/dist/commands/mcp.js +37 -0
- package/dist/commands/skills.d.ts +6 -0
- package/dist/commands/skills.js +48 -0
- package/dist/headless/host-agent.d.ts +19 -0
- package/dist/headless/host-agent.js +64 -0
- package/dist/headless/init-phase1.d.ts +9 -0
- package/dist/headless/init-phase1.js +173 -0
- package/dist/headless/init-phase2.d.ts +36 -0
- package/dist/headless/init-phase2.js +150 -0
- package/dist/headless/next-step-prompt.d.ts +7 -0
- package/dist/headless/next-step-prompt.js +25 -0
- package/dist/headless/options.d.ts +30 -0
- package/dist/headless/options.js +77 -0
- package/dist/headless/reporter.d.ts +16 -0
- package/dist/headless/reporter.js +41 -0
- package/dist/headless/setup.d.ts +29 -0
- package/dist/headless/setup.js +80 -0
- package/dist/launch/agent.d.ts +55 -0
- package/dist/launch/agent.js +134 -0
- package/dist/mcp/clients/base.d.ts +49 -0
- package/dist/mcp/clients/base.js +66 -0
- package/dist/mcp/clients/claude-code.d.ts +22 -0
- package/dist/mcp/clients/claude-code.js +120 -0
- package/dist/mcp/clients/claude-desktop.d.ts +9 -0
- package/dist/mcp/clients/claude-desktop.js +35 -0
- package/dist/mcp/clients/codex.d.ts +13 -0
- package/dist/mcp/clients/codex.js +113 -0
- package/dist/mcp/clients/cursor.d.ts +9 -0
- package/dist/mcp/clients/cursor.js +26 -0
- package/dist/mcp/clients/index.d.ts +7 -0
- package/dist/mcp/clients/index.js +27 -0
- package/dist/mcp/clients/mcp-remote.d.ts +11 -0
- package/dist/mcp/clients/mcp-remote.js +13 -0
- package/dist/mcp/clients/vscode.d.ts +17 -0
- package/dist/mcp/clients/vscode.js +84 -0
- package/dist/mcp/clients.d.ts +4 -0
- package/dist/mcp/clients.js +50 -0
- package/dist/mcp/config-merge.d.ts +9 -0
- package/dist/mcp/config-merge.js +51 -0
- package/dist/mcp/writer.d.ts +6 -0
- package/dist/mcp/writer.js +65 -0
- package/dist/setup/storage.d.ts +21 -0
- package/dist/setup/storage.js +34 -0
- package/dist/skills/install.d.ts +19 -0
- package/dist/skills/install.js +64 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.js +1 -0
- package/dist/ui/components/Card.d.ts +11 -0
- package/dist/ui/components/Card.js +19 -0
- package/dist/ui/components/ContextRow.d.ts +11 -0
- package/dist/ui/components/ContextRow.js +8 -0
- package/dist/ui/components/Footer.d.ts +10 -0
- package/dist/ui/components/Footer.js +9 -0
- package/dist/ui/components/Header.d.ts +11 -0
- package/dist/ui/components/Header.js +6 -0
- package/dist/ui/components/JsonPreview.d.ts +13 -0
- package/dist/ui/components/JsonPreview.js +25 -0
- package/dist/ui/components/SectionTitle.d.ts +6 -0
- package/dist/ui/components/SectionTitle.js +5 -0
- package/dist/ui/hooks/useAsyncEffect.d.ts +3 -0
- package/dist/ui/hooks/useAsyncEffect.js +20 -0
- package/dist/ui/hooks/useResize.d.ts +4 -0
- package/dist/ui/hooks/useResize.js +20 -0
- package/dist/ui/hud.d.ts +15 -0
- package/dist/ui/hud.js +30 -0
- package/dist/ui/ink-theme.d.ts +2 -0
- package/dist/ui/ink-theme.js +34 -0
- package/dist/ui/intro/LogoView.d.ts +12 -0
- package/dist/ui/intro/LogoView.js +226 -0
- package/dist/ui/intro/MatrixIntro.d.ts +6 -0
- package/dist/ui/intro/MatrixIntro.js +80 -0
- package/dist/ui/intro/logo.d.ts +6 -0
- package/dist/ui/intro/logo.js +21 -0
- package/dist/ui/messages.d.ts +5 -0
- package/dist/ui/messages.js +5 -0
- package/dist/ui/screens/DashScreen.d.ts +6 -0
- package/dist/ui/screens/DashScreen.js +27 -0
- package/dist/ui/screens/DebugScreen.d.ts +6 -0
- package/dist/ui/screens/DebugScreen.js +39 -0
- package/dist/ui/screens/InitScreen.d.ts +6 -0
- package/dist/ui/screens/InitScreen.js +138 -0
- package/dist/ui/screens/MenuScreen.d.ts +7 -0
- package/dist/ui/screens/MenuScreen.js +38 -0
- package/dist/ui/state.d.ts +72 -0
- package/dist/ui/state.js +107 -0
- package/dist/ui/steps/AccountStep.d.ts +8 -0
- package/dist/ui/steps/AccountStep.js +42 -0
- package/dist/ui/steps/ApiKeyStep.d.ts +8 -0
- package/dist/ui/steps/ApiKeyStep.js +91 -0
- package/dist/ui/steps/ClientsStep.d.ts +10 -0
- package/dist/ui/steps/ClientsStep.js +69 -0
- package/dist/ui/steps/CredentialKindStep.d.ts +7 -0
- package/dist/ui/steps/CredentialKindStep.js +18 -0
- package/dist/ui/steps/EnvironmentStep.d.ts +8 -0
- package/dist/ui/steps/EnvironmentStep.js +37 -0
- package/dist/ui/steps/LoginStep.d.ts +7 -0
- package/dist/ui/steps/LoginStep.js +56 -0
- package/dist/ui/steps/SkillsStep.d.ts +7 -0
- package/dist/ui/steps/SkillsStep.js +7 -0
- package/dist/ui/steps/SummaryStep.d.ts +8 -0
- package/dist/ui/steps/SummaryStep.js +41 -0
- package/dist/ui/steps/WritingStep.d.ts +10 -0
- package/dist/ui/steps/WritingStep.js +96 -0
- package/dist/ui/theme.d.ts +53 -0
- package/dist/ui/theme.js +66 -0
- package/dist/ui/tui/App.d.ts +10 -0
- package/dist/ui/tui/App.js +51 -0
- package/dist/ui/tui/components/ContextStrip.d.ts +11 -0
- package/dist/ui/tui/components/ContextStrip.js +8 -0
- package/dist/ui/tui/components/TitleBar.d.ts +6 -0
- package/dist/ui/tui/components/TitleBar.js +18 -0
- package/dist/ui/tui/components/WizardChecklist.d.ts +7 -0
- package/dist/ui/tui/components/WizardChecklist.js +69 -0
- package/dist/ui/tui/hooks/keyboard-hints-utils.d.ts +26 -0
- package/dist/ui/tui/hooks/keyboard-hints-utils.js +69 -0
- package/dist/ui/tui/hooks/useKeyBindings.d.ts +14 -0
- package/dist/ui/tui/hooks/useKeyBindings.js +44 -0
- package/dist/ui/tui/hooks/useKeyboardHints.d.ts +13 -0
- package/dist/ui/tui/hooks/useKeyboardHints.js +38 -0
- package/dist/ui/tui/hooks/useStdoutDimensions.d.ts +8 -0
- package/dist/ui/tui/hooks/useStdoutDimensions.js +28 -0
- package/dist/ui/tui/primitives/BlinkingLabel.d.ts +19 -0
- package/dist/ui/tui/primitives/BlinkingLabel.js +25 -0
- package/dist/ui/tui/primitives/ConfirmPrompt.d.ts +16 -0
- package/dist/ui/tui/primitives/ConfirmPrompt.js +36 -0
- package/dist/ui/tui/primitives/KeyboardHintsBar.d.ts +2 -0
- package/dist/ui/tui/primitives/KeyboardHintsBar.js +8 -0
- package/dist/ui/tui/primitives/PickerMenu.d.ts +38 -0
- package/dist/ui/tui/primitives/PickerMenu.js +162 -0
- package/dist/ui/tui/primitives/PromptLabel.d.ts +6 -0
- package/dist/ui/tui/primitives/PromptLabel.js +6 -0
- package/dist/ui/tui/primitives/ScreenContainer.d.ts +39 -0
- package/dist/ui/tui/primitives/ScreenContainer.js +39 -0
- package/dist/ui/tui/primitives/Spinner.d.ts +7 -0
- package/dist/ui/tui/primitives/Spinner.js +18 -0
- package/dist/ui/tui/screens/DashScreen.d.ts +6 -0
- package/dist/ui/tui/screens/DashScreen.js +37 -0
- package/dist/ui/tui/screens/DebugScreen.d.ts +6 -0
- package/dist/ui/tui/screens/DebugScreen.js +48 -0
- package/dist/ui/tui/screens/EnvScreen.d.ts +6 -0
- package/dist/ui/tui/screens/EnvScreen.js +192 -0
- package/dist/ui/tui/screens/InitScreen.d.ts +9 -0
- package/dist/ui/tui/screens/InitScreen.js +102 -0
- package/dist/ui/tui/screens/MenuScreen.d.ts +7 -0
- package/dist/ui/tui/screens/MenuScreen.js +84 -0
- package/dist/ui/tui/start-tui.d.ts +11 -0
- package/dist/ui/tui/start-tui.js +72 -0
- package/dist/ui/tui/steps/AccountStep.d.ts +8 -0
- package/dist/ui/tui/steps/AccountStep.js +42 -0
- package/dist/ui/tui/steps/ApiKeyStep.d.ts +8 -0
- package/dist/ui/tui/steps/ApiKeyStep.js +53 -0
- package/dist/ui/tui/steps/ClientsStep.d.ts +10 -0
- package/dist/ui/tui/steps/ClientsStep.js +52 -0
- package/dist/ui/tui/steps/CredentialKindStep.d.ts +7 -0
- package/dist/ui/tui/steps/CredentialKindStep.js +18 -0
- package/dist/ui/tui/steps/EnvironmentStep.d.ts +8 -0
- package/dist/ui/tui/steps/EnvironmentStep.js +38 -0
- package/dist/ui/tui/steps/LoginStep.d.ts +8 -0
- package/dist/ui/tui/steps/LoginStep.js +79 -0
- package/dist/ui/tui/steps/SkillsStep.d.ts +7 -0
- package/dist/ui/tui/steps/SkillsStep.js +7 -0
- package/dist/ui/tui/steps/SummaryStep.d.ts +10 -0
- package/dist/ui/tui/steps/SummaryStep.js +133 -0
- package/dist/ui/tui/steps/WritingStep.d.ts +10 -0
- package/dist/ui/tui/steps/WritingStep.js +101 -0
- package/package.json +62 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type KeyBinding } from './keyboard-hints-utils.js';
|
|
2
|
+
interface UseKeyBindingsOptions {
|
|
3
|
+
isActive?: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Declarative key bindings + automatic hint registration.
|
|
7
|
+
*
|
|
8
|
+
* Each binding declares `{match, label, action, handler}`. We wire `useInput`
|
|
9
|
+
* to dispatch the matching binding's handler, AND register `{label, action,
|
|
10
|
+
* priority}` with the KeyboardHintsProvider so the bottom hints bar updates
|
|
11
|
+
* automatically when this component mounts/unmounts.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useKeyBindings(id: string, bindings: KeyBinding[], opts?: UseKeyBindingsOptions): void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useInput } from 'ink';
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
|
+
import { defaultPriorityFor, matchesKey, } from './keyboard-hints-utils.js';
|
|
4
|
+
import { useKeyboardHintsContext } from './useKeyboardHints.js';
|
|
5
|
+
/**
|
|
6
|
+
* Declarative key bindings + automatic hint registration.
|
|
7
|
+
*
|
|
8
|
+
* Each binding declares `{match, label, action, handler}`. We wire `useInput`
|
|
9
|
+
* to dispatch the matching binding's handler, AND register `{label, action,
|
|
10
|
+
* priority}` with the KeyboardHintsProvider so the bottom hints bar updates
|
|
11
|
+
* automatically when this component mounts/unmounts.
|
|
12
|
+
*/
|
|
13
|
+
export function useKeyBindings(id, bindings, opts = {}) {
|
|
14
|
+
const { register, unregister } = useKeyboardHintsContext();
|
|
15
|
+
const isActive = opts.isActive ?? true;
|
|
16
|
+
const hintsKey = JSON.stringify(bindings.map((b) => [b.label, b.action, b.priority]));
|
|
17
|
+
const hints = useMemo(() => bindings.map((b) => {
|
|
18
|
+
const firstMatch = Array.isArray(b.match) ? b.match[0] : b.match;
|
|
19
|
+
const priority = b.priority ?? (firstMatch !== undefined ? defaultPriorityFor(firstMatch) : undefined);
|
|
20
|
+
return { label: b.label, action: b.action, priority };
|
|
21
|
+
}),
|
|
22
|
+
// hintsKey captures the relevant shape of bindings — stable across renders
|
|
23
|
+
// when callers pass equivalent arrays.
|
|
24
|
+
[hintsKey]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!isActive) {
|
|
27
|
+
unregister(id);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
register(id, hints);
|
|
31
|
+
return () => {
|
|
32
|
+
unregister(id);
|
|
33
|
+
};
|
|
34
|
+
}, [id, hints, isActive, register, unregister]);
|
|
35
|
+
useInput((input, key) => {
|
|
36
|
+
for (const b of bindings) {
|
|
37
|
+
const matches = Array.isArray(b.match) ? b.match : [b.match];
|
|
38
|
+
if (matches.some((m) => matchesKey(m, input, key))) {
|
|
39
|
+
b.handler(input, key);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, { isActive });
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type KeyHint } from './keyboard-hints-utils.js';
|
|
3
|
+
interface KeyboardHintsContextValue {
|
|
4
|
+
register: (id: string, hints: KeyHint[]) => void;
|
|
5
|
+
unregister: (id: string) => void;
|
|
6
|
+
hints: KeyHint[];
|
|
7
|
+
}
|
|
8
|
+
interface ProviderProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export declare function KeyboardHintsProvider({ children }: ProviderProps): React.ReactElement;
|
|
12
|
+
export declare function useKeyboardHintsContext(): KeyboardHintsContextValue;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
3
|
+
import { dedupeAndSortHints } from './keyboard-hints-utils.js';
|
|
4
|
+
const Ctx = createContext(null);
|
|
5
|
+
export function KeyboardHintsProvider({ children }) {
|
|
6
|
+
const [bag, setBag] = useState(() => new Map());
|
|
7
|
+
const register = useCallback((id, hints) => {
|
|
8
|
+
setBag((prev) => {
|
|
9
|
+
const next = new Map(prev);
|
|
10
|
+
next.set(id, hints);
|
|
11
|
+
return next;
|
|
12
|
+
});
|
|
13
|
+
}, []);
|
|
14
|
+
const unregister = useCallback((id) => {
|
|
15
|
+
setBag((prev) => {
|
|
16
|
+
if (!prev.has(id))
|
|
17
|
+
return prev;
|
|
18
|
+
const next = new Map(prev);
|
|
19
|
+
next.delete(id);
|
|
20
|
+
return next;
|
|
21
|
+
});
|
|
22
|
+
}, []);
|
|
23
|
+
const hints = useMemo(() => dedupeAndSortHints(bag), [bag]);
|
|
24
|
+
const value = useMemo(() => ({ register, unregister, hints }), [register, unregister, hints]);
|
|
25
|
+
return _jsx(Ctx.Provider, { value: value, children: children });
|
|
26
|
+
}
|
|
27
|
+
export function useKeyboardHintsContext() {
|
|
28
|
+
const ctx = useContext(Ctx);
|
|
29
|
+
if (!ctx) {
|
|
30
|
+
// Allow use outside provider for testing / non-TUI rendering.
|
|
31
|
+
return {
|
|
32
|
+
register: () => { },
|
|
33
|
+
unregister: () => { },
|
|
34
|
+
hints: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return ctx;
|
|
38
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the terminal's [columns, rows] and re-renders on resize.
|
|
3
|
+
*
|
|
4
|
+
* Ink's built-in `useStdout` doesn't trigger re-renders on resize — this hook
|
|
5
|
+
* subscribes to the `resize` event on the underlying stream and forces a state
|
|
6
|
+
* update so the entire layout reflows.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useStdoutDimensions(): readonly [number, number];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useStdout } from 'ink';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the terminal's [columns, rows] and re-renders on resize.
|
|
5
|
+
*
|
|
6
|
+
* Ink's built-in `useStdout` doesn't trigger re-renders on resize — this hook
|
|
7
|
+
* subscribes to the `resize` event on the underlying stream and forces a state
|
|
8
|
+
* update so the entire layout reflows.
|
|
9
|
+
*/
|
|
10
|
+
export function useStdoutDimensions() {
|
|
11
|
+
const { stdout } = useStdout();
|
|
12
|
+
const [size, setSize] = useState(() => [
|
|
13
|
+
stdout?.columns ?? 80,
|
|
14
|
+
stdout?.rows ?? 24,
|
|
15
|
+
]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!stdout)
|
|
18
|
+
return undefined;
|
|
19
|
+
const onResize = () => {
|
|
20
|
+
setSize([stdout.columns ?? 80, stdout.rows ?? 24]);
|
|
21
|
+
};
|
|
22
|
+
stdout.on('resize', onResize);
|
|
23
|
+
return () => {
|
|
24
|
+
stdout.off('resize', onResize);
|
|
25
|
+
};
|
|
26
|
+
}, [stdout]);
|
|
27
|
+
return size;
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface BlinkingLabelProps {
|
|
3
|
+
text: string;
|
|
4
|
+
intervalMs?: number;
|
|
5
|
+
color?: string;
|
|
6
|
+
dimColor?: boolean;
|
|
7
|
+
bold?: boolean;
|
|
8
|
+
/** Visual cell width to reserve. Lets callers pad past JS string length when
|
|
9
|
+
* the label contains emoji that render as 2 cells. */
|
|
10
|
+
padWidth?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hard blink — text alternates between fully visible and fully hidden.
|
|
14
|
+
* Width is preserved during the off-frame via space padding so neighbours
|
|
15
|
+
* don't reflow. Stays steady (no interval) when animations are disabled
|
|
16
|
+
* (CI / NO_COLOR / dumb terminal / non-TTY).
|
|
17
|
+
*/
|
|
18
|
+
export declare function BlinkingLabel({ text, intervalMs, color, dimColor, bold, padWidth, }: BlinkingLabelProps): React.ReactElement;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Colors, shouldRenderAnimation } from '../../theme.js';
|
|
5
|
+
/**
|
|
6
|
+
* Hard blink — text alternates between fully visible and fully hidden.
|
|
7
|
+
* Width is preserved during the off-frame via space padding so neighbours
|
|
8
|
+
* don't reflow. Stays steady (no interval) when animations are disabled
|
|
9
|
+
* (CI / NO_COLOR / dumb terminal / non-TTY).
|
|
10
|
+
*/
|
|
11
|
+
export function BlinkingLabel({ text, intervalMs = 600, color = Colors.accent, dimColor, bold, padWidth, }) {
|
|
12
|
+
const [visible, setVisible] = useState(true);
|
|
13
|
+
const animate = shouldRenderAnimation();
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!animate)
|
|
16
|
+
return undefined;
|
|
17
|
+
const id = setInterval(() => {
|
|
18
|
+
setVisible((v) => !v);
|
|
19
|
+
}, intervalMs);
|
|
20
|
+
return () => clearInterval(id);
|
|
21
|
+
}, [animate, intervalMs]);
|
|
22
|
+
const blankWidth = padWidth ?? text.length;
|
|
23
|
+
const display = visible ? text : ' '.repeat(blankWidth);
|
|
24
|
+
return (_jsx(Text, { color: color, dimColor: dimColor, bold: bold, children: display }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ConfirmPromptProps {
|
|
3
|
+
id: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
defaultAnswer?: 'yes' | 'no';
|
|
6
|
+
onConfirm: () => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* y/n prompt. Enter triggers the default answer (yes by default). Esc is
|
|
11
|
+
* intentionally not bound here — it bubbles up to the wizard host so the
|
|
12
|
+
* standard "esc → back" navigation works on confirm-style steps too.
|
|
13
|
+
* Registers hints: `y yes`, `n no`, `enter <default>`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ConfirmPrompt({ id, message, defaultAnswer, onConfirm, onCancel, }: ConfirmPromptProps): React.ReactElement;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Colors } from '../../theme.js';
|
|
4
|
+
import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
|
|
5
|
+
import { useKeyBindings } from '../hooks/useKeyBindings.js';
|
|
6
|
+
import { PromptLabel } from './PromptLabel.js';
|
|
7
|
+
/**
|
|
8
|
+
* y/n prompt. Enter triggers the default answer (yes by default). Esc is
|
|
9
|
+
* intentionally not bound here — it bubbles up to the wizard host so the
|
|
10
|
+
* standard "esc → back" navigation works on confirm-style steps too.
|
|
11
|
+
* Registers hints: `y yes`, `n no`, `enter <default>`.
|
|
12
|
+
*/
|
|
13
|
+
export function ConfirmPrompt({ id, message, defaultAnswer = 'yes', onConfirm, onCancel, }) {
|
|
14
|
+
const bindings = [
|
|
15
|
+
{
|
|
16
|
+
match: 'y',
|
|
17
|
+
label: 'y',
|
|
18
|
+
action: 'yes',
|
|
19
|
+
handler: onConfirm,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
match: 'n',
|
|
23
|
+
label: 'n',
|
|
24
|
+
action: 'no',
|
|
25
|
+
handler: onCancel,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
match: KeyMatch.Return,
|
|
29
|
+
label: 'enter',
|
|
30
|
+
action: defaultAnswer,
|
|
31
|
+
handler: defaultAnswer === 'yes' ? onConfirm : onCancel,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
useKeyBindings(`confirm:${id}`, bindings);
|
|
35
|
+
return (_jsxs(Box, { flexDirection: "column", children: [message && _jsx(PromptLabel, { children: message }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: Colors.muted, children: `[${defaultAnswer === 'yes' ? 'Y/n' : 'y/N'}]` }), _jsx(Text, { dimColor: true, children: " \u2014 press y/n or enter" })] }) })] }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Colors } from '../../theme.js';
|
|
4
|
+
import { useKeyboardHintsContext } from '../hooks/useKeyboardHints.js';
|
|
5
|
+
export function KeyboardHintsBar() {
|
|
6
|
+
const { hints } = useKeyboardHintsContext();
|
|
7
|
+
return (_jsx(Box, { height: 1, paddingX: 1, children: hints.map((hint, i) => (_jsxs(Box, { marginRight: i < hints.length - 1 ? 2 : 0, children: [_jsx(Text, { bold: true, color: Colors.muted, children: hint.label }), _jsxs(Text, { dimColor: true, children: [" ", hint.action] })] }, `${hint.label}-${hint.action}-${i}`))) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PickerOption<T> {
|
|
3
|
+
label: string;
|
|
4
|
+
value: T;
|
|
5
|
+
/** Short inline trailing text (rendered same-line, dimmed). */
|
|
6
|
+
hint?: string;
|
|
7
|
+
/** Dimmed secondary line rendered under the label (e.g. a long path). */
|
|
8
|
+
description?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface BasePickerMenuProps<T> {
|
|
12
|
+
id: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
options: PickerOption<T>[];
|
|
15
|
+
centered?: boolean;
|
|
16
|
+
optionMarginBottom?: number;
|
|
17
|
+
/** When set, render at most this many options at a time and scroll the
|
|
18
|
+
* viewport as focus moves out of view. */
|
|
19
|
+
visibleCount?: number;
|
|
20
|
+
}
|
|
21
|
+
interface SinglePickerMenuProps<T> extends BasePickerMenuProps<T> {
|
|
22
|
+
mode?: 'single';
|
|
23
|
+
defaultValue?: T;
|
|
24
|
+
/** When true, prefix each label with `[N] ` and bind number keys 1..9 to
|
|
25
|
+
* jump-select the matching option. */
|
|
26
|
+
numbered?: boolean;
|
|
27
|
+
onSelect: (value: T) => void;
|
|
28
|
+
onChange?: (value: T) => void;
|
|
29
|
+
}
|
|
30
|
+
interface MultiPickerMenuProps<T> extends BasePickerMenuProps<T> {
|
|
31
|
+
mode: 'multi';
|
|
32
|
+
defaultValue?: T[];
|
|
33
|
+
onSelect: (values: T[]) => void;
|
|
34
|
+
onChange?: (values: T[]) => void;
|
|
35
|
+
}
|
|
36
|
+
export type PickerMenuProps<T> = SinglePickerMenuProps<T> | MultiPickerMenuProps<T>;
|
|
37
|
+
export declare function PickerMenu<T>(props: PickerMenuProps<T>): React.ReactElement;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Colors, Icons } from '../../theme.js';
|
|
5
|
+
import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
|
|
6
|
+
import { useKeyBindings } from '../hooks/useKeyBindings.js';
|
|
7
|
+
import { PromptLabel } from './PromptLabel.js';
|
|
8
|
+
function findInitialIndex(options, defaultValue) {
|
|
9
|
+
if (defaultValue === undefined)
|
|
10
|
+
return 0;
|
|
11
|
+
const i = options.findIndex((o) => o.value === defaultValue);
|
|
12
|
+
return i >= 0 ? i : 0;
|
|
13
|
+
}
|
|
14
|
+
export function PickerMenu(props) {
|
|
15
|
+
const { id, message, options, centered, optionMarginBottom = 0, visibleCount } = props;
|
|
16
|
+
const mode = props.mode ?? 'single';
|
|
17
|
+
const [focused, setFocused] = useState(() => mode === 'single'
|
|
18
|
+
? findInitialIndex(options, props.defaultValue)
|
|
19
|
+
: 0);
|
|
20
|
+
const windowSize = visibleCount && visibleCount > 0 ? Math.min(visibleCount, options.length) : options.length;
|
|
21
|
+
const [windowStart, setWindowStart] = useState(() => clampWindowStart(focused, windowSize, options.length));
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setWindowStart((start) => {
|
|
24
|
+
if (focused < start)
|
|
25
|
+
return focused;
|
|
26
|
+
if (focused >= start + windowSize)
|
|
27
|
+
return focused - windowSize + 1;
|
|
28
|
+
return clampWindowStart(start, windowSize, options.length);
|
|
29
|
+
});
|
|
30
|
+
}, [focused, windowSize, options.length]);
|
|
31
|
+
const [selected, setSelected] = useState(() => {
|
|
32
|
+
if (mode === 'multi') {
|
|
33
|
+
const dv = props.defaultValue;
|
|
34
|
+
return new Set(Array.isArray(dv) ? dv : []);
|
|
35
|
+
}
|
|
36
|
+
return new Set();
|
|
37
|
+
});
|
|
38
|
+
const move = (delta) => {
|
|
39
|
+
if (options.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
setFocused((f) => {
|
|
42
|
+
let next = f;
|
|
43
|
+
for (let i = 0; i < options.length; i++) {
|
|
44
|
+
next = (next + delta + options.length) % options.length;
|
|
45
|
+
if (!options[next].disabled)
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
const focusedOpt = options[next];
|
|
49
|
+
if (focusedOpt && mode === 'single') {
|
|
50
|
+
props.onChange?.(focusedOpt.value);
|
|
51
|
+
}
|
|
52
|
+
return next;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const toggleCurrent = () => {
|
|
56
|
+
const opt = options[focused];
|
|
57
|
+
if (!opt || opt.disabled)
|
|
58
|
+
return;
|
|
59
|
+
setSelected((prev) => {
|
|
60
|
+
const next = new Set(prev);
|
|
61
|
+
if (next.has(opt.value)) {
|
|
62
|
+
next.delete(opt.value);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
next.add(opt.value);
|
|
66
|
+
}
|
|
67
|
+
props.onChange?.(Array.from(next));
|
|
68
|
+
return next;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const submit = () => {
|
|
72
|
+
const opt = options[focused];
|
|
73
|
+
if (!opt || opt.disabled)
|
|
74
|
+
return;
|
|
75
|
+
if (mode === 'multi') {
|
|
76
|
+
props.onSelect(Array.from(selected));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
props.onSelect(opt.value);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const bindings = [
|
|
83
|
+
{
|
|
84
|
+
match: [KeyMatch.UpArrow, KeyMatch.DownArrow],
|
|
85
|
+
label: '↑↓',
|
|
86
|
+
action: 'navigate',
|
|
87
|
+
handler: (_input, key) => {
|
|
88
|
+
if (key.upArrow)
|
|
89
|
+
move(-1);
|
|
90
|
+
else if (key.downArrow)
|
|
91
|
+
move(1);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
if (mode === 'multi') {
|
|
96
|
+
bindings.push({
|
|
97
|
+
match: KeyMatch.Space,
|
|
98
|
+
label: 'space',
|
|
99
|
+
action: 'toggle',
|
|
100
|
+
handler: toggleCurrent,
|
|
101
|
+
});
|
|
102
|
+
bindings.push({
|
|
103
|
+
match: KeyMatch.Return,
|
|
104
|
+
label: 'enter',
|
|
105
|
+
action: 'confirm',
|
|
106
|
+
handler: submit,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
bindings.push({
|
|
111
|
+
match: KeyMatch.Return,
|
|
112
|
+
label: 'enter',
|
|
113
|
+
action: 'select',
|
|
114
|
+
handler: submit,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const numbered = mode === 'single' && props.numbered === true;
|
|
118
|
+
if (numbered && options.length > 0) {
|
|
119
|
+
const max = Math.min(options.length, 9);
|
|
120
|
+
const keys = Array.from({ length: max }, (_, i) => String(i + 1));
|
|
121
|
+
bindings.push({
|
|
122
|
+
match: keys,
|
|
123
|
+
label: max === 1 ? '1' : `1-${max}`,
|
|
124
|
+
action: 'select',
|
|
125
|
+
priority: 40,
|
|
126
|
+
handler: (input) => {
|
|
127
|
+
const idx = parseInt(input, 10) - 1;
|
|
128
|
+
const opt = options[idx];
|
|
129
|
+
if (!opt || opt.disabled)
|
|
130
|
+
return;
|
|
131
|
+
setFocused(idx);
|
|
132
|
+
props.onSelect(opt.value);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
useKeyBindings(`picker:${id}`, bindings);
|
|
137
|
+
const windowEnd = Math.min(options.length, windowStart + windowSize);
|
|
138
|
+
const hiddenAbove = windowStart;
|
|
139
|
+
const hiddenBelow = options.length - windowEnd;
|
|
140
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: centered ? 'center' : undefined, children: [message && _jsx(PromptLabel, { children: message }), _jsxs(Box, { flexDirection: "column", marginTop: message ? 1 : 0, children: [hiddenAbove > 0 && (_jsx(Text, { dimColor: true, children: ` ▲ ${hiddenAbove} more above` })), options.slice(windowStart, windowEnd).map((opt, j) => {
|
|
141
|
+
const i = windowStart + j;
|
|
142
|
+
const isFocused = i === focused;
|
|
143
|
+
const isChecked = mode === 'multi' && selected.has(opt.value);
|
|
144
|
+
const indicatorGlyph = mode === 'multi'
|
|
145
|
+
? isChecked
|
|
146
|
+
? Icons.squareFilled
|
|
147
|
+
: Icons.squareOpen
|
|
148
|
+
: isFocused
|
|
149
|
+
? Icons.triangleSmallRight
|
|
150
|
+
: ' ';
|
|
151
|
+
const isDisabled = opt.disabled === true;
|
|
152
|
+
const numberTag = numbered && i < 9 ? `[${i + 1}] ` : '';
|
|
153
|
+
const fullLabel = `${numberTag}${opt.label}`;
|
|
154
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: optionMarginBottom, children: [_jsxs(Box, { children: [_jsx(Text, { color: isFocused && !isDisabled ? Colors.accent : undefined, dimColor: !isFocused || isDisabled, children: indicatorGlyph }), _jsx(Text, { children: " " }), _jsx(Text, { color: isFocused && !isDisabled ? Colors.accent : undefined, bold: isFocused && !isDisabled, dimColor: !isFocused || isDisabled, children: fullLabel }), opt.hint && _jsx(Text, { dimColor: true, children: ` ${opt.hint}` })] }), opt.description && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: opt.description }) }))] }, `${id}-opt-${i}`));
|
|
155
|
+
}), hiddenBelow > 0 && (_jsx(Text, { dimColor: true, children: ` ▼ ${hiddenBelow} more below` }))] })] }));
|
|
156
|
+
}
|
|
157
|
+
function clampWindowStart(start, size, total) {
|
|
158
|
+
if (total === 0 || size === 0)
|
|
159
|
+
return 0;
|
|
160
|
+
const max = Math.max(0, total - size);
|
|
161
|
+
return Math.min(Math.max(0, start), max);
|
|
162
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ScreenContainerProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* Optional full-terminal-width content rendered between the TitleBar and
|
|
6
|
+
* the centered body. Receives the live terminal column count so chrome
|
|
7
|
+
* (e.g. the menu's Logo + particle tail) can size to the screen edge.
|
|
8
|
+
*/
|
|
9
|
+
chrome?: (cols: number) => React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Always-on shell that wraps every screen.
|
|
13
|
+
*
|
|
14
|
+
* outer column (full terminal width × full terminal height)
|
|
15
|
+
* → KeyboardHintsProvider
|
|
16
|
+
* → TitleBar (full terminal width — lime strip bleeds edge to edge)
|
|
17
|
+
* → spacer (1 row — keeps the chrome / body off the title bar)
|
|
18
|
+
* → chrome?(cols) (full-width, currently only the menu Logo)
|
|
19
|
+
* → body wrapper (column-flex, full width, alignItems="center")
|
|
20
|
+
* → inner column (width = clamped 80..120, paddingX=1)
|
|
21
|
+
* → children (overflow hidden)
|
|
22
|
+
* → spacer (1 row gap between content and the hints bar)
|
|
23
|
+
* → KeyboardHintsBar
|
|
24
|
+
*
|
|
25
|
+
* Two flex details are load-bearing here:
|
|
26
|
+
* 1. The outer body wrapper sets `flexDirection="column"` explicitly. Without
|
|
27
|
+
* it, Yoga defaults to row, and `alignItems="center"` would center the
|
|
28
|
+
* inner column vertically instead of horizontally — pinning the body to
|
|
29
|
+
* the left edge and pushing it to the vertical middle of the screen.
|
|
30
|
+
* 2. Clamping the inner column while leaving the outer wrapper full-width is
|
|
31
|
+
* what produces the "body centered in the middle of the terminal" effect
|
|
32
|
+
* — empty gutters appear on both sides in wide terminals.
|
|
33
|
+
*
|
|
34
|
+
* No `flexGrow` on the body so it stays at its natural height and the
|
|
35
|
+
* KeyboardHintsBar tucks directly under the content. Leftover vertical space
|
|
36
|
+
* falls below the hints bar at the bottom of the terminal.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ScreenContainer({ children, chrome }: ScreenContainerProps): React.ReactElement;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { MAX_SHELL_WIDTH, MIN_SHELL_WIDTH } from '../../theme.js';
|
|
4
|
+
import { TitleBar } from '../components/TitleBar.js';
|
|
5
|
+
import { useStdoutDimensions } from '../hooks/useStdoutDimensions.js';
|
|
6
|
+
import { KeyboardHintsProvider } from '../hooks/useKeyboardHints.js';
|
|
7
|
+
import { KeyboardHintsBar } from './KeyboardHintsBar.js';
|
|
8
|
+
/**
|
|
9
|
+
* Always-on shell that wraps every screen.
|
|
10
|
+
*
|
|
11
|
+
* outer column (full terminal width × full terminal height)
|
|
12
|
+
* → KeyboardHintsProvider
|
|
13
|
+
* → TitleBar (full terminal width — lime strip bleeds edge to edge)
|
|
14
|
+
* → spacer (1 row — keeps the chrome / body off the title bar)
|
|
15
|
+
* → chrome?(cols) (full-width, currently only the menu Logo)
|
|
16
|
+
* → body wrapper (column-flex, full width, alignItems="center")
|
|
17
|
+
* → inner column (width = clamped 80..120, paddingX=1)
|
|
18
|
+
* → children (overflow hidden)
|
|
19
|
+
* → spacer (1 row gap between content and the hints bar)
|
|
20
|
+
* → KeyboardHintsBar
|
|
21
|
+
*
|
|
22
|
+
* Two flex details are load-bearing here:
|
|
23
|
+
* 1. The outer body wrapper sets `flexDirection="column"` explicitly. Without
|
|
24
|
+
* it, Yoga defaults to row, and `alignItems="center"` would center the
|
|
25
|
+
* inner column vertically instead of horizontally — pinning the body to
|
|
26
|
+
* the left edge and pushing it to the vertical middle of the screen.
|
|
27
|
+
* 2. Clamping the inner column while leaving the outer wrapper full-width is
|
|
28
|
+
* what produces the "body centered in the middle of the terminal" effect
|
|
29
|
+
* — empty gutters appear on both sides in wide terminals.
|
|
30
|
+
*
|
|
31
|
+
* No `flexGrow` on the body so it stays at its natural height and the
|
|
32
|
+
* KeyboardHintsBar tucks directly under the content. Leftover vertical space
|
|
33
|
+
* falls below the hints bar at the bottom of the terminal.
|
|
34
|
+
*/
|
|
35
|
+
export function ScreenContainer({ children, chrome }) {
|
|
36
|
+
const [cols, rows] = useStdoutDimensions();
|
|
37
|
+
const clampedWidth = Math.min(MAX_SHELL_WIDTH, Math.max(MIN_SHELL_WIDTH, cols));
|
|
38
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, width: cols, children: _jsxs(KeyboardHintsProvider, { children: [_jsx(TitleBar, { width: cols }), _jsx(Box, { height: 1 }), chrome && (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, overflow: "hidden", children: chrome(cols) }), _jsx(Box, { height: 1 })] })), _jsx(Box, { width: cols, flexDirection: "column", alignItems: "center", flexShrink: 1, children: _jsxs(Box, { flexDirection: "column", width: clampedWidth, paddingX: 1, children: [_jsx(Box, { flexDirection: "column", overflow: "hidden", children: children }), _jsx(Box, { height: 2 }), _jsx(KeyboardHintsBar, {})] }) })] }) }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Colors, shouldRenderAnimation } from '../../theme.js';
|
|
5
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
6
|
+
export function Spinner({ label, intervalMs = 80 }) {
|
|
7
|
+
const [i, setI] = useState(0);
|
|
8
|
+
const animate = shouldRenderAnimation();
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!animate)
|
|
11
|
+
return undefined;
|
|
12
|
+
const id = setInterval(() => {
|
|
13
|
+
setI((n) => (n + 1) % FRAMES.length);
|
|
14
|
+
}, intervalMs);
|
|
15
|
+
return () => clearInterval(id);
|
|
16
|
+
}, [animate, intervalMs]);
|
|
17
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: Colors.accent, children: FRAMES[i] }), label ? ` ${label}` : ''] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { Colors } from '../../theme.js';
|
|
6
|
+
import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
|
|
7
|
+
import { useKeyBindings } from '../hooks/useKeyBindings.js';
|
|
8
|
+
import { PromptLabel } from '../primitives/PromptLabel.js';
|
|
9
|
+
const DASHBOARD_URL = 'https://app.stigg.io';
|
|
10
|
+
export function DashScreen({ onDone }) {
|
|
11
|
+
const [phase, setPhase] = useState('opening');
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
open(DASHBOARD_URL)
|
|
15
|
+
.then(() => {
|
|
16
|
+
if (!cancelled)
|
|
17
|
+
setPhase('launched');
|
|
18
|
+
})
|
|
19
|
+
.catch(() => {
|
|
20
|
+
if (!cancelled)
|
|
21
|
+
setPhase('failed');
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
cancelled = true;
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
const bindings = [
|
|
28
|
+
{
|
|
29
|
+
match: [KeyMatch.Return, KeyMatch.Escape],
|
|
30
|
+
label: 'enter',
|
|
31
|
+
action: 'menu',
|
|
32
|
+
handler: onDone,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
useKeyBindings('dash', bindings);
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "dash \u00B7 open Stigg dashboard" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Opening ", _jsx(Text, { color: Colors.accent, children: DASHBOARD_URL }), " in your browser\u2026"] }) }), _jsxs(Box, { marginTop: 1, children: [phase === 'opening' && _jsx(Text, { dimColor: true, children: "(launching)" }), phase === 'launched' && _jsx(Text, { color: Colors.success, children: "\u2713 Browser launched." }), phase === 'failed' && (_jsx(Text, { color: Colors.error, children: "Couldn't open a browser automatically \u2014 open the URL above manually." }))] })] }));
|
|
37
|
+
}
|