@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,48 @@
|
|
|
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 "../primitives/PromptLabel.js";
|
|
7
|
+
const SAMPLE_LOGS = [
|
|
8
|
+
{
|
|
9
|
+
time: "16:42:13.221",
|
|
10
|
+
level: "INFO",
|
|
11
|
+
source: "feature.access",
|
|
12
|
+
message: "customer=acme_corp has_access=true plan=growth feature=ai_credits",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
time: "16:42:14.034",
|
|
16
|
+
level: "INFO",
|
|
17
|
+
source: "usage.report",
|
|
18
|
+
message: "customer=acme_corp feature=api_calls value=1 source=mcp",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
time: "16:42:18.512",
|
|
22
|
+
level: "WARN",
|
|
23
|
+
source: "entitlement.exceeded",
|
|
24
|
+
message: "customer=acme_corp feature=api_calls usage=1001 limit=1000 reset_at=2026-07-01",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
time: "16:42:21.118",
|
|
28
|
+
level: "INFO",
|
|
29
|
+
source: "subscription.updated",
|
|
30
|
+
message: "customer=acme_corp from=growth to=growth_v2 status=active",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
const LEVEL_COLOR = {
|
|
34
|
+
INFO: "green",
|
|
35
|
+
WARN: "yellow",
|
|
36
|
+
ERROR: "red",
|
|
37
|
+
};
|
|
38
|
+
export function DebugScreen({ onDone }) {
|
|
39
|
+
useKeyBindings("debug", [
|
|
40
|
+
{
|
|
41
|
+
match: KeyMatch.AnyChar,
|
|
42
|
+
label: "any key",
|
|
43
|
+
action: "menu",
|
|
44
|
+
handler: onDone,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "debug \u00B7 audit + usage logs" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Streaming audit + usage logs (placeholder, sample output)\u2026" }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: SAMPLE_LOGS.map((log, i) => (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: log.time }), " ", _jsx(Text, { color: LEVEL_COLOR[log.level], children: log.level.padEnd(5) }), " ", _jsx(Text, { color: Colors.accent, children: log.source.padEnd(22) }), " ", log.message] }, i))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "(placeholder \u2014 the real command will live-tail audit + usage events with `--since`, `--follow`, `--filter` flags.)" }) })] }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { listApiKeys } from '../../../api/client.js';
|
|
5
|
+
import { formatApiKeyShort } from '../../../api/format-key.js';
|
|
6
|
+
import { loadSession } from '../../../auth/storage.js';
|
|
7
|
+
import { getClientById } from '../../../mcp/clients/index.js';
|
|
8
|
+
import { loadSetup, saveSetup } from '../../../setup/storage.js';
|
|
9
|
+
import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
|
|
10
|
+
import { Colors } from '../../theme.js';
|
|
11
|
+
import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
|
|
12
|
+
import { useKeyBindings } from '../hooks/useKeyBindings.js';
|
|
13
|
+
import { PickerMenu } from '../primitives/PickerMenu.js';
|
|
14
|
+
import { PromptLabel } from '../primitives/PromptLabel.js';
|
|
15
|
+
import { Spinner } from '../primitives/Spinner.js';
|
|
16
|
+
function buildCredentialLabel(key) {
|
|
17
|
+
return `API key ${key.name} (${formatApiKeyShort(key)})`;
|
|
18
|
+
}
|
|
19
|
+
function timeAgo(iso) {
|
|
20
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
21
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
22
|
+
if (days < 1)
|
|
23
|
+
return 'today';
|
|
24
|
+
if (days < 7)
|
|
25
|
+
return `${days}d ago`;
|
|
26
|
+
if (days < 30)
|
|
27
|
+
return `${Math.floor(days / 7)}w ago`;
|
|
28
|
+
if (days < 365)
|
|
29
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
30
|
+
return `${Math.floor(days / 365)}y ago`;
|
|
31
|
+
}
|
|
32
|
+
async function rewriteMcpForScoped(credential, clientIds) {
|
|
33
|
+
const results = [];
|
|
34
|
+
for (const id of clientIds) {
|
|
35
|
+
const client = getClientById(id);
|
|
36
|
+
if (!client)
|
|
37
|
+
continue;
|
|
38
|
+
if (!(await client.hasExistingEntry()))
|
|
39
|
+
continue;
|
|
40
|
+
const r = await client.addServer(credential);
|
|
41
|
+
if (r.ok)
|
|
42
|
+
results.push(client.toWriteResult(r));
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
export function EnvScreen({ onDone }) {
|
|
47
|
+
const [phase, setPhase] = useState({ kind: 'loading' });
|
|
48
|
+
useAsyncEffect(async (signal) => {
|
|
49
|
+
const setup = await loadSetup();
|
|
50
|
+
if (signal.cancelled)
|
|
51
|
+
return;
|
|
52
|
+
if (!setup) {
|
|
53
|
+
setPhase({ kind: 'missing' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!setup.environments || setup.environments.length === 0) {
|
|
57
|
+
setPhase({ kind: 'no-snapshot' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setPhase({ kind: 'env-picker', setup, envs: setup.environments });
|
|
61
|
+
}, [], (err) => setPhase({
|
|
62
|
+
kind: 'error',
|
|
63
|
+
message: err instanceof Error ? err.message : String(err),
|
|
64
|
+
}));
|
|
65
|
+
const backBinding = [
|
|
66
|
+
{
|
|
67
|
+
match: [KeyMatch.Escape, KeyMatch.Return],
|
|
68
|
+
label: 'enter',
|
|
69
|
+
action: 'back',
|
|
70
|
+
priority: 30,
|
|
71
|
+
handler: onDone,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
useKeyBindings('env-back', backBinding, {
|
|
75
|
+
isActive: phase.kind === 'missing' ||
|
|
76
|
+
phase.kind === 'no-snapshot' ||
|
|
77
|
+
phase.kind === 'no-keys' ||
|
|
78
|
+
phase.kind === 'saved' ||
|
|
79
|
+
phase.kind === 'error',
|
|
80
|
+
});
|
|
81
|
+
if (phase.kind === 'loading') {
|
|
82
|
+
return _jsx(Spinner, { label: "Loading environments\u2026" });
|
|
83
|
+
}
|
|
84
|
+
if (phase.kind === 'keys-loading') {
|
|
85
|
+
return _jsx(Spinner, { label: `Loading API keys for ${phase.env.name}…` });
|
|
86
|
+
}
|
|
87
|
+
if (phase.kind === 'rewriting') {
|
|
88
|
+
return _jsx(Spinner, { label: `Switching to ${phase.env.name}…` });
|
|
89
|
+
}
|
|
90
|
+
if (phase.kind === 'missing') {
|
|
91
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "No active Stigg setup" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Run ", _jsx(Text, { color: Colors.accent, children: "terminal init" }), " first to sign in and pick an environment."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "press enter to go back" }) })] }));
|
|
92
|
+
}
|
|
93
|
+
if (phase.kind === 'no-snapshot') {
|
|
94
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "No environment snapshot" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Your saved setup is from an older version of terminal. Run", ' ', _jsx(Text, { color: Colors.accent, children: "terminal init" }), " once to refresh the environment list."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "press enter to go back" }) })] }));
|
|
95
|
+
}
|
|
96
|
+
if (phase.kind === 'error') {
|
|
97
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: Colors.error, children: "\u2717 Could not switch environment" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: phase.message }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "press enter to go back" }) })] }));
|
|
98
|
+
}
|
|
99
|
+
if (phase.kind === 'no-keys') {
|
|
100
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(PromptLabel, { children: ["No API keys in ", phase.env.name] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Create an API key for this environment in the Stigg dashboard (settings \u2192 API keys), then come back and run ", _jsx(Text, { color: Colors.accent, children: "terminal env" }), " again."] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "press enter to go back" }) })] }));
|
|
101
|
+
}
|
|
102
|
+
if (phase.kind === 'saved') {
|
|
103
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "Environment switched" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Active environment is now ", _jsx(Text, { color: Colors.accent, children: phase.env.name }), "."] }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Active key is now ", _jsx(Text, { color: Colors.accent, children: phase.key.name }), " (", formatApiKeyShort(phase.key), ")."] }) }), phase.writes.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: phase.writes.map((w, i) => (_jsxs(Text, { color: Colors.success, children: [' ✓ ', w.path, w.backedUpTo ? (_jsxs(Text, { dimColor: true, children: [' (backup: ', w.backedUpTo, ')'] })) : null] }, i))) })) : (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No MCP entries needed an update." }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "press enter to go back" }) })] }));
|
|
104
|
+
}
|
|
105
|
+
if (phase.kind === 'env-picker') {
|
|
106
|
+
const options = phase.envs.map((e) => ({
|
|
107
|
+
label: e.id === phase.setup.environmentId ? `${e.name} (current)` : e.name,
|
|
108
|
+
value: e.id,
|
|
109
|
+
}));
|
|
110
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(PickerMenu, { id: "env-picker", message: "Switch active environment", options: options, defaultValue: phase.setup.environmentId, onSelect: (v) => {
|
|
111
|
+
const env = phase.envs.find((e) => e.id === v);
|
|
112
|
+
if (!env)
|
|
113
|
+
return;
|
|
114
|
+
void (async () => {
|
|
115
|
+
setPhase({ kind: 'keys-loading', setup: phase.setup, env });
|
|
116
|
+
try {
|
|
117
|
+
const session = await loadSession();
|
|
118
|
+
if (!session) {
|
|
119
|
+
setPhase({
|
|
120
|
+
kind: 'error',
|
|
121
|
+
message: 'Session missing — run reset & init from the menu.',
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const keys = await listApiKeys(session.access_token, phase.setup.accountId, env.id);
|
|
126
|
+
if (keys.length === 0) {
|
|
127
|
+
setPhase({ kind: 'no-keys', setup: phase.setup, env });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
setPhase({
|
|
131
|
+
kind: 'keys-picker',
|
|
132
|
+
setup: phase.setup,
|
|
133
|
+
env,
|
|
134
|
+
keys,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
setPhase({
|
|
139
|
+
kind: 'error',
|
|
140
|
+
message: err instanceof Error ? err.message : String(err),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
} }) }));
|
|
145
|
+
}
|
|
146
|
+
// phase.kind === "keys-picker"
|
|
147
|
+
const keyOptions = phase.keys.map((k) => ({
|
|
148
|
+
label: `${k.name.padEnd(20)} ${formatApiKeyShort(k).padEnd(15)} ${k.keyType.padEnd(7)} ${timeAgo(k.createdAt)}`,
|
|
149
|
+
value: k.id,
|
|
150
|
+
}));
|
|
151
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(PickerMenu, { id: "env-key-picker", message: `Pick an API key for ${phase.env.name}`, options: keyOptions, onSelect: (v) => {
|
|
152
|
+
const key = phase.keys.find((k) => k.id === v);
|
|
153
|
+
if (!key || !key.value)
|
|
154
|
+
return;
|
|
155
|
+
void (async () => {
|
|
156
|
+
setPhase({
|
|
157
|
+
kind: 'rewriting',
|
|
158
|
+
setup: phase.setup,
|
|
159
|
+
env: phase.env,
|
|
160
|
+
key,
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
const credential = {
|
|
164
|
+
value: key.value,
|
|
165
|
+
accountId: phase.setup.accountId,
|
|
166
|
+
environmentId: phase.env.id,
|
|
167
|
+
environmentSlug: phase.env.slug,
|
|
168
|
+
};
|
|
169
|
+
const writes = await rewriteMcpForScoped(credential, phase.setup.clients ?? ['claude-code', 'cursor']);
|
|
170
|
+
await saveSetup({
|
|
171
|
+
...phase.setup,
|
|
172
|
+
environment: phase.env.name,
|
|
173
|
+
environmentId: phase.env.id,
|
|
174
|
+
credentialLabel: buildCredentialLabel(key),
|
|
175
|
+
});
|
|
176
|
+
setPhase({
|
|
177
|
+
kind: 'saved',
|
|
178
|
+
setup: phase.setup,
|
|
179
|
+
env: phase.env,
|
|
180
|
+
key,
|
|
181
|
+
writes,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
setPhase({
|
|
186
|
+
kind: 'error',
|
|
187
|
+
message: err instanceof Error ? err.message : String(err),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
})();
|
|
191
|
+
} }) }));
|
|
192
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { LaunchRequest } from '../../../launch/agent.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
onDone: () => void;
|
|
5
|
+
onLaunch: (req: LaunchRequest) => void;
|
|
6
|
+
callbackPort?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function InitScreen({ onDone, onLaunch, callbackPort }: Props): React.ReactElement;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useMemo, useReducer } from 'react';
|
|
4
|
+
import { getAllClients } from '../../../mcp/clients/index.js';
|
|
5
|
+
import { INITIAL_STATE, wizardReducer } from '../../state.js';
|
|
6
|
+
import { Colors, Icons } from '../../theme.js';
|
|
7
|
+
import { ContextStrip } from '../components/ContextStrip.js';
|
|
8
|
+
import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
|
|
9
|
+
import { useKeyBindings } from '../hooks/useKeyBindings.js';
|
|
10
|
+
import { AccountStep } from '../steps/AccountStep.js';
|
|
11
|
+
import { ApiKeyStep } from '../steps/ApiKeyStep.js';
|
|
12
|
+
import { ClientsStep } from '../steps/ClientsStep.js';
|
|
13
|
+
import { EnvironmentStep } from '../steps/EnvironmentStep.js';
|
|
14
|
+
import { LoginStep } from '../steps/LoginStep.js';
|
|
15
|
+
import { SkillsStep } from '../steps/SkillsStep.js';
|
|
16
|
+
import { SummaryStep } from '../steps/SummaryStep.js';
|
|
17
|
+
import { WritingStep } from '../steps/WritingStep.js';
|
|
18
|
+
function stepPath(state) {
|
|
19
|
+
const path = ['login'];
|
|
20
|
+
const hideAccount = state.accounts != null && state.accounts.length === 1;
|
|
21
|
+
if (!hideAccount)
|
|
22
|
+
path.push('account');
|
|
23
|
+
path.push('environment', 'apiKey', 'clients');
|
|
24
|
+
if ((state.selectedClients?.length ?? 0) > 0)
|
|
25
|
+
path.push('skills');
|
|
26
|
+
path.push('writing');
|
|
27
|
+
path.push('summary');
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
30
|
+
function stepNumber(state) {
|
|
31
|
+
const path = stepPath(state);
|
|
32
|
+
const idx = path.indexOf(state.step);
|
|
33
|
+
const total = path.length;
|
|
34
|
+
return { current: idx >= 0 ? idx + 1 : 1, total };
|
|
35
|
+
}
|
|
36
|
+
function contextEntries(state) {
|
|
37
|
+
const entries = [];
|
|
38
|
+
if (state.session) {
|
|
39
|
+
entries.push({ label: 'user', value: state.session.email });
|
|
40
|
+
}
|
|
41
|
+
if (state.account) {
|
|
42
|
+
entries.push({ label: 'account', value: state.account.displayName });
|
|
43
|
+
}
|
|
44
|
+
if (state.environment) {
|
|
45
|
+
entries.push({ label: 'environment', value: state.environment.name });
|
|
46
|
+
}
|
|
47
|
+
if (state.selectedKey) {
|
|
48
|
+
entries.push({
|
|
49
|
+
label: 'key',
|
|
50
|
+
value: `${state.selectedKey.name} (sk_…${state.selectedKey.lastFour})`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
function renderStep(state, dispatch, clients, onDone, onLaunch, callbackPort) {
|
|
56
|
+
switch (state.step) {
|
|
57
|
+
case 'login':
|
|
58
|
+
return _jsx(LoginStep, { dispatch: dispatch, callbackPort: callbackPort });
|
|
59
|
+
case 'account':
|
|
60
|
+
return _jsx(AccountStep, { state: state, dispatch: dispatch });
|
|
61
|
+
case 'environment':
|
|
62
|
+
return _jsx(EnvironmentStep, { state: state, dispatch: dispatch });
|
|
63
|
+
case 'apiKey':
|
|
64
|
+
return _jsx(ApiKeyStep, { state: state, dispatch: dispatch });
|
|
65
|
+
case 'clients':
|
|
66
|
+
return _jsx(ClientsStep, { state: state, dispatch: dispatch, clients: clients });
|
|
67
|
+
case 'skills':
|
|
68
|
+
return _jsx(SkillsStep, { dispatch: dispatch });
|
|
69
|
+
case 'writing':
|
|
70
|
+
return _jsx(WritingStep, { state: state, dispatch: dispatch, clients: clients });
|
|
71
|
+
case 'summary':
|
|
72
|
+
return _jsx(SummaryStep, { state: state, onDone: onDone, onLaunch: onLaunch });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function InitScreen({ onDone, onLaunch, callbackPort }) {
|
|
76
|
+
const [state, dispatch] = useReducer(wizardReducer, INITIAL_STATE);
|
|
77
|
+
const clients = useMemo(() => getAllClients(), []);
|
|
78
|
+
const canGoBack = state.step !== 'summary' && state.history.length > 0;
|
|
79
|
+
const escBinding = [
|
|
80
|
+
{
|
|
81
|
+
match: KeyMatch.Escape,
|
|
82
|
+
label: 'esc',
|
|
83
|
+
action: canGoBack ? 'back' : 'menu',
|
|
84
|
+
priority: 30,
|
|
85
|
+
handler: () => {
|
|
86
|
+
if (state.step === 'summary')
|
|
87
|
+
return;
|
|
88
|
+
if (state.history.length > 0) {
|
|
89
|
+
dispatch({ type: 'BACK' });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
onDone();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
useKeyBindings('init-host', escBinding);
|
|
98
|
+
const isSummary = state.step === 'summary';
|
|
99
|
+
const { current, total } = stepNumber(state);
|
|
100
|
+
const contextRows = contextEntries(state);
|
|
101
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [!isSummary && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: Colors.accent, children: "Set up Stigg" }), _jsx(Text, { dimColor: true, children: ` · step ${current} of ${total}` })] }), _jsx(Box, { height: 1 }), _jsx(ContextStrip, { entries: contextRows }), contextRows.length > 0 && _jsx(Box, { height: 1 })] })), renderStep(state, dispatch, clients, onDone, onLaunch, callbackPort), state.error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.error, children: [Icons.cross, " ", state.error] }) }))] }));
|
|
102
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useApp } from 'ink';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { clearSession } from '../../../auth/storage.js';
|
|
5
|
+
import { clearSetup, loadSetup } from '../../../setup/storage.js';
|
|
6
|
+
import { ContextStrip } from '../components/ContextStrip.js';
|
|
7
|
+
import { PickerMenu } from '../primitives/PickerMenu.js';
|
|
8
|
+
function setupEntries(setup) {
|
|
9
|
+
return [
|
|
10
|
+
{ label: 'user', value: setup.user },
|
|
11
|
+
{
|
|
12
|
+
label: 'account',
|
|
13
|
+
value: setup.account,
|
|
14
|
+
hint: setup.accountId || undefined,
|
|
15
|
+
},
|
|
16
|
+
{ label: 'credential', value: setup.credentialLabel },
|
|
17
|
+
{
|
|
18
|
+
label: 'environment',
|
|
19
|
+
value: setup.environment,
|
|
20
|
+
hint: setup.environmentId || undefined,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
export function MenuScreen({ onPick }) {
|
|
25
|
+
const { exit } = useApp();
|
|
26
|
+
const [setup, setSetup] = useState(null);
|
|
27
|
+
const [loadedSetup, setLoadedSetup] = useState(false);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
void loadSetup().then((s) => {
|
|
31
|
+
if (cancelled)
|
|
32
|
+
return;
|
|
33
|
+
setSetup(s);
|
|
34
|
+
setLoadedSetup(true);
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const initOption = setup
|
|
41
|
+
? {
|
|
42
|
+
value: 'init',
|
|
43
|
+
label: 'reset & init Clear saved Stigg setup, then re-run init',
|
|
44
|
+
}
|
|
45
|
+
: {
|
|
46
|
+
value: 'init',
|
|
47
|
+
label: 'init Set up Stigg — auth, MCP, skills',
|
|
48
|
+
};
|
|
49
|
+
const options = [initOption];
|
|
50
|
+
if (setup) {
|
|
51
|
+
options.push({
|
|
52
|
+
value: 'env',
|
|
53
|
+
label: 'env Switch the active Stigg environment',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
options.push({
|
|
57
|
+
value: 'dash',
|
|
58
|
+
label: 'dash Open the Stigg dashboard in your browser',
|
|
59
|
+
}, {
|
|
60
|
+
value: 'exit',
|
|
61
|
+
label: 'exit Quit terminal',
|
|
62
|
+
});
|
|
63
|
+
const handleSelect = async (value) => {
|
|
64
|
+
if (value === 'exit') {
|
|
65
|
+
exit();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (value === 'init' && setup) {
|
|
69
|
+
// "reset & init": wipe local credentials + setup before re-entering wizard.
|
|
70
|
+
try {
|
|
71
|
+
await clearSession();
|
|
72
|
+
await clearSetup();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* best-effort cleanup */
|
|
76
|
+
}
|
|
77
|
+
setSetup(null);
|
|
78
|
+
}
|
|
79
|
+
onPick(value);
|
|
80
|
+
};
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", flexGrow: 1, children: [loadedSetup && setup && (_jsx(Box, { marginTop: 1, children: _jsx(ContextStrip, { entries: setupEntries(setup) }) })), _jsx(Box, { marginTop: 3, children: _jsx(Text, { children: "What do you want to do?" }) }), _jsx(Box, { marginTop: 2, children: _jsx(PickerMenu, { id: "menu", numbered: true, options: options, onSelect: (value) => {
|
|
82
|
+
void handleSelect(value);
|
|
83
|
+
} }) })] }));
|
|
84
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LaunchRequest } from '../../launch/agent.js';
|
|
2
|
+
import { type AppScreen } from './App.js';
|
|
3
|
+
export interface StartTuiOptions {
|
|
4
|
+
initialScreen?: AppScreen;
|
|
5
|
+
callbackPort?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface StartTuiResult {
|
|
8
|
+
/** Set when the user chose to hand off into an agent from the summary screen. */
|
|
9
|
+
launch?: LaunchRequest;
|
|
10
|
+
}
|
|
11
|
+
export declare function startTUI(opts?: StartTuiOptions): Promise<StartTuiResult>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render } from 'ink';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { SHELL_BG_RGB } from '../theme.js';
|
|
4
|
+
import { App } from './App.js';
|
|
5
|
+
const ENTER_ALT = '\x1b[?1049h';
|
|
6
|
+
const LEAVE_ALT = '\x1b[?1049l';
|
|
7
|
+
const CLEAR = '\x1b[2J';
|
|
8
|
+
const CURSOR_HOME = '\x1b[H';
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
10
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
11
|
+
const HIDE_CURSOR = '\x1b[?25l';
|
|
12
|
+
const BG = `\x1b[48;2;${SHELL_BG_RGB[0]};${SHELL_BG_RGB[1]};${SHELL_BG_RGB[2]}m`;
|
|
13
|
+
export async function startTUI(opts = {}) {
|
|
14
|
+
if (!process.stdin.isTTY) {
|
|
15
|
+
throw new Error('terminal HUD requires a TTY');
|
|
16
|
+
}
|
|
17
|
+
process.stdout.write(ENTER_ALT + BG + CLEAR + CURSOR_HOME + HIDE_CURSOR);
|
|
18
|
+
let launch;
|
|
19
|
+
const { waitUntilExit, unmount } = render(createElement(App, {
|
|
20
|
+
initialScreen: opts.initialScreen ?? 'menu',
|
|
21
|
+
callbackPort: opts.callbackPort,
|
|
22
|
+
onLaunchAgent: (req) => {
|
|
23
|
+
launch = req;
|
|
24
|
+
},
|
|
25
|
+
}), { exitOnCtrlC: true });
|
|
26
|
+
let cleaned = false;
|
|
27
|
+
// Named handlers so we can detach them on teardown. Detaching is essential
|
|
28
|
+
// before handing the TTY to a spawned agent: with stdio:'inherit' a Ctrl-C in
|
|
29
|
+
// the agent is delivered to this process group too, and a lingering SIGINT /
|
|
30
|
+
// uncaughtException handler would tear the parent down out from under the child.
|
|
31
|
+
const onExit = () => cleanup();
|
|
32
|
+
const onSigint = () => {
|
|
33
|
+
cleanup();
|
|
34
|
+
process.exit(130);
|
|
35
|
+
};
|
|
36
|
+
const onSigterm = () => {
|
|
37
|
+
cleanup();
|
|
38
|
+
process.exit(143);
|
|
39
|
+
};
|
|
40
|
+
const onUncaught = (err) => {
|
|
41
|
+
cleanup();
|
|
42
|
+
console.error(err);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
};
|
|
45
|
+
function cleanup() {
|
|
46
|
+
if (cleaned)
|
|
47
|
+
return;
|
|
48
|
+
cleaned = true;
|
|
49
|
+
process.off('exit', onExit);
|
|
50
|
+
process.off('SIGINT', onSigint);
|
|
51
|
+
process.off('SIGTERM', onSigterm);
|
|
52
|
+
process.off('uncaughtException', onUncaught);
|
|
53
|
+
try {
|
|
54
|
+
unmount();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* ignore */
|
|
58
|
+
}
|
|
59
|
+
process.stdout.write(RESET + SHOW_CURSOR + LEAVE_ALT);
|
|
60
|
+
}
|
|
61
|
+
process.on('exit', onExit);
|
|
62
|
+
process.on('SIGINT', onSigint);
|
|
63
|
+
process.on('SIGTERM', onSigterm);
|
|
64
|
+
process.on('uncaughtException', onUncaught);
|
|
65
|
+
try {
|
|
66
|
+
await waitUntilExit();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
cleanup();
|
|
70
|
+
}
|
|
71
|
+
return { launch };
|
|
72
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Action, WizardState } from '../../state.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
state: WizardState;
|
|
5
|
+
dispatch: React.Dispatch<Action>;
|
|
6
|
+
}
|
|
7
|
+
export declare function AccountStep({ state, dispatch }: Props): React.ReactElement;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { listAccounts } from '../../../api/client.js';
|
|
5
|
+
import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
|
|
6
|
+
import { PickerMenu } from '../primitives/PickerMenu.js';
|
|
7
|
+
import { Spinner } from '../primitives/Spinner.js';
|
|
8
|
+
export function AccountStep({ state, dispatch }) {
|
|
9
|
+
useAsyncEffect(async (signal) => {
|
|
10
|
+
if (state.accounts || !state.session)
|
|
11
|
+
return;
|
|
12
|
+
const accounts = await listAccounts(state.session.accessToken);
|
|
13
|
+
if (signal.cancelled)
|
|
14
|
+
return;
|
|
15
|
+
dispatch({ type: 'ACCOUNTS_LOADED', accounts });
|
|
16
|
+
}, [state.session?.accessToken], (err) => dispatch({
|
|
17
|
+
type: 'ERROR',
|
|
18
|
+
message: err instanceof Error ? err.message : String(err),
|
|
19
|
+
}));
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (state.accounts &&
|
|
22
|
+
state.accounts.length === 1 &&
|
|
23
|
+
!state.account &&
|
|
24
|
+
state.step === 'account') {
|
|
25
|
+
dispatch({ type: 'PICK_ACCOUNT', account: state.accounts[0] });
|
|
26
|
+
}
|
|
27
|
+
}, [state.accounts, state.account, state.step, dispatch]);
|
|
28
|
+
if (!state.accounts) {
|
|
29
|
+
return _jsx(Spinner, { label: "Loading accounts\u2026" });
|
|
30
|
+
}
|
|
31
|
+
if (state.accounts.length === 1) {
|
|
32
|
+
return _jsx(Spinner, { label: `Using ${state.accounts[0].displayName}…` });
|
|
33
|
+
}
|
|
34
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(PickerMenu, { id: "account", message: "Which account?", options: state.accounts.map((a) => ({
|
|
35
|
+
label: a.displayName,
|
|
36
|
+
value: a.id,
|
|
37
|
+
})), onSelect: (v) => {
|
|
38
|
+
const account = state.accounts?.find((a) => a.id === v);
|
|
39
|
+
if (account)
|
|
40
|
+
dispatch({ type: 'PICK_ACCOUNT', account });
|
|
41
|
+
} }) }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Action, WizardState } from '../../state.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
state: WizardState;
|
|
5
|
+
dispatch: React.Dispatch<Action>;
|
|
6
|
+
}
|
|
7
|
+
export declare function ApiKeyStep({ state, dispatch }: Props): React.ReactElement;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { listApiKeys } from '../../../api/client.js';
|
|
4
|
+
import { formatApiKeyShort } from '../../../api/format-key.js';
|
|
5
|
+
import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
|
|
6
|
+
import { Colors } from '../../theme.js';
|
|
7
|
+
import { PickerMenu } from '../primitives/PickerMenu.js';
|
|
8
|
+
import { PromptLabel } from '../primitives/PromptLabel.js';
|
|
9
|
+
import { Spinner } from '../primitives/Spinner.js';
|
|
10
|
+
function timeAgo(iso) {
|
|
11
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
12
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
13
|
+
if (days < 1)
|
|
14
|
+
return 'today';
|
|
15
|
+
if (days < 7)
|
|
16
|
+
return `${days}d ago`;
|
|
17
|
+
if (days < 30)
|
|
18
|
+
return `${Math.floor(days / 7)}w ago`;
|
|
19
|
+
if (days < 365)
|
|
20
|
+
return `${Math.floor(days / 30)}mo ago`;
|
|
21
|
+
return `${Math.floor(days / 365)}y ago`;
|
|
22
|
+
}
|
|
23
|
+
export function ApiKeyStep({ state, dispatch }) {
|
|
24
|
+
useAsyncEffect(async (signal) => {
|
|
25
|
+
if (state.apiKeys || !state.session || !state.environment || !state.account) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const keys = await listApiKeys(state.session.accessToken, state.account.id, state.environment.id);
|
|
29
|
+
if (signal.cancelled)
|
|
30
|
+
return;
|
|
31
|
+
dispatch({ type: 'KEYS_LOADED', apiKeys: keys });
|
|
32
|
+
}, [state.session?.accessToken, state.account?.id, state.environment?.id], (err) => dispatch({
|
|
33
|
+
type: 'ERROR',
|
|
34
|
+
message: err instanceof Error ? err.message : String(err),
|
|
35
|
+
}));
|
|
36
|
+
if (!state.apiKeys) {
|
|
37
|
+
return _jsx(Spinner, { label: `Loading API keys for ${state.environment?.name}…` });
|
|
38
|
+
}
|
|
39
|
+
if (state.apiKeys.length === 0) {
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(PromptLabel, { children: ["No API keys in ", state.environment?.name] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Create an API key for this environment in the Stigg dashboard (Settings \u2192 API keys), then press ", _jsx(Text, { color: Colors.accent, children: "esc" }), " to back up and try again."] }) })] }));
|
|
41
|
+
}
|
|
42
|
+
const handleSelect = (value) => {
|
|
43
|
+
const key = state.apiKeys?.find((k) => k.id === value);
|
|
44
|
+
if (!key || !key.value)
|
|
45
|
+
return;
|
|
46
|
+
dispatch({ type: 'PICK_KEY', key, credentialValue: key.value });
|
|
47
|
+
};
|
|
48
|
+
const options = state.apiKeys.map((k) => ({
|
|
49
|
+
label: `${k.name.padEnd(20)} ${formatApiKeyShort(k).padEnd(15)} ${k.keyType.padEnd(7)} ${timeAgo(k.createdAt)}`,
|
|
50
|
+
value: k.id,
|
|
51
|
+
}));
|
|
52
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(PickerMenu, { id: "apikey", message: `Pick an API key for ${state.environment?.name}`, options: options, onSelect: handleSelect }) }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type McpClient } from '../../../mcp/clients/index.js';
|
|
3
|
+
import type { Action, WizardState } from '../../state.js';
|
|
4
|
+
interface Props {
|
|
5
|
+
state: WizardState;
|
|
6
|
+
dispatch: React.Dispatch<Action>;
|
|
7
|
+
clients: readonly McpClient[];
|
|
8
|
+
}
|
|
9
|
+
export declare function ClientsStep({ state, dispatch, clients }: Props): React.ReactElement;
|
|
10
|
+
export {};
|