@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.
Files changed (199) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +47 -0
  3. package/dist/api/client.d.ts +6 -0
  4. package/dist/api/client.js +48 -0
  5. package/dist/api/format-key.d.ts +7 -0
  6. package/dist/api/format-key.js +12 -0
  7. package/dist/api/graphql-client.d.ts +5 -0
  8. package/dist/api/graphql-client.js +44 -0
  9. package/dist/api/operations.d.ts +65 -0
  10. package/dist/api/operations.js +77 -0
  11. package/dist/api/types.d.ts +18 -0
  12. package/dist/api/types.js +1 -0
  13. package/dist/auth/callback-server.d.ts +14 -0
  14. package/dist/auth/callback-server.js +145 -0
  15. package/dist/auth/config.d.ts +2 -0
  16. package/dist/auth/config.js +24 -0
  17. package/dist/auth/oauth.d.ts +17 -0
  18. package/dist/auth/oauth.js +94 -0
  19. package/dist/auth/storage.d.ts +6 -0
  20. package/dist/auth/storage.js +34 -0
  21. package/dist/bin.d.ts +2 -0
  22. package/dist/bin.js +3 -0
  23. package/dist/cli.d.ts +1 -0
  24. package/dist/cli.js +79 -0
  25. package/dist/commands/dash.d.ts +1 -0
  26. package/dist/commands/dash.js +24 -0
  27. package/dist/commands/debug.d.ts +1 -0
  28. package/dist/commands/debug.js +53 -0
  29. package/dist/commands/env.d.ts +1 -0
  30. package/dist/commands/env.js +15 -0
  31. package/dist/commands/init.d.ts +13 -0
  32. package/dist/commands/init.js +59 -0
  33. package/dist/commands/mcp.d.ts +7 -0
  34. package/dist/commands/mcp.js +37 -0
  35. package/dist/commands/skills.d.ts +6 -0
  36. package/dist/commands/skills.js +48 -0
  37. package/dist/headless/host-agent.d.ts +19 -0
  38. package/dist/headless/host-agent.js +64 -0
  39. package/dist/headless/init-phase1.d.ts +9 -0
  40. package/dist/headless/init-phase1.js +173 -0
  41. package/dist/headless/init-phase2.d.ts +36 -0
  42. package/dist/headless/init-phase2.js +150 -0
  43. package/dist/headless/next-step-prompt.d.ts +7 -0
  44. package/dist/headless/next-step-prompt.js +25 -0
  45. package/dist/headless/options.d.ts +30 -0
  46. package/dist/headless/options.js +77 -0
  47. package/dist/headless/reporter.d.ts +16 -0
  48. package/dist/headless/reporter.js +41 -0
  49. package/dist/headless/setup.d.ts +29 -0
  50. package/dist/headless/setup.js +80 -0
  51. package/dist/launch/agent.d.ts +55 -0
  52. package/dist/launch/agent.js +134 -0
  53. package/dist/mcp/clients/base.d.ts +49 -0
  54. package/dist/mcp/clients/base.js +66 -0
  55. package/dist/mcp/clients/claude-code.d.ts +22 -0
  56. package/dist/mcp/clients/claude-code.js +120 -0
  57. package/dist/mcp/clients/claude-desktop.d.ts +9 -0
  58. package/dist/mcp/clients/claude-desktop.js +35 -0
  59. package/dist/mcp/clients/codex.d.ts +13 -0
  60. package/dist/mcp/clients/codex.js +113 -0
  61. package/dist/mcp/clients/cursor.d.ts +9 -0
  62. package/dist/mcp/clients/cursor.js +26 -0
  63. package/dist/mcp/clients/index.d.ts +7 -0
  64. package/dist/mcp/clients/index.js +27 -0
  65. package/dist/mcp/clients/mcp-remote.d.ts +11 -0
  66. package/dist/mcp/clients/mcp-remote.js +13 -0
  67. package/dist/mcp/clients/vscode.d.ts +17 -0
  68. package/dist/mcp/clients/vscode.js +84 -0
  69. package/dist/mcp/clients.d.ts +4 -0
  70. package/dist/mcp/clients.js +50 -0
  71. package/dist/mcp/config-merge.d.ts +9 -0
  72. package/dist/mcp/config-merge.js +51 -0
  73. package/dist/mcp/writer.d.ts +6 -0
  74. package/dist/mcp/writer.js +65 -0
  75. package/dist/setup/storage.d.ts +21 -0
  76. package/dist/setup/storage.js +34 -0
  77. package/dist/skills/install.d.ts +19 -0
  78. package/dist/skills/install.js +64 -0
  79. package/dist/types.d.ts +35 -0
  80. package/dist/types.js +1 -0
  81. package/dist/ui/components/Card.d.ts +11 -0
  82. package/dist/ui/components/Card.js +19 -0
  83. package/dist/ui/components/ContextRow.d.ts +11 -0
  84. package/dist/ui/components/ContextRow.js +8 -0
  85. package/dist/ui/components/Footer.d.ts +10 -0
  86. package/dist/ui/components/Footer.js +9 -0
  87. package/dist/ui/components/Header.d.ts +11 -0
  88. package/dist/ui/components/Header.js +6 -0
  89. package/dist/ui/components/JsonPreview.d.ts +13 -0
  90. package/dist/ui/components/JsonPreview.js +25 -0
  91. package/dist/ui/components/SectionTitle.d.ts +6 -0
  92. package/dist/ui/components/SectionTitle.js +5 -0
  93. package/dist/ui/hooks/useAsyncEffect.d.ts +3 -0
  94. package/dist/ui/hooks/useAsyncEffect.js +20 -0
  95. package/dist/ui/hooks/useResize.d.ts +4 -0
  96. package/dist/ui/hooks/useResize.js +20 -0
  97. package/dist/ui/hud.d.ts +15 -0
  98. package/dist/ui/hud.js +30 -0
  99. package/dist/ui/ink-theme.d.ts +2 -0
  100. package/dist/ui/ink-theme.js +34 -0
  101. package/dist/ui/intro/LogoView.d.ts +12 -0
  102. package/dist/ui/intro/LogoView.js +226 -0
  103. package/dist/ui/intro/MatrixIntro.d.ts +6 -0
  104. package/dist/ui/intro/MatrixIntro.js +80 -0
  105. package/dist/ui/intro/logo.d.ts +6 -0
  106. package/dist/ui/intro/logo.js +21 -0
  107. package/dist/ui/messages.d.ts +5 -0
  108. package/dist/ui/messages.js +5 -0
  109. package/dist/ui/screens/DashScreen.d.ts +6 -0
  110. package/dist/ui/screens/DashScreen.js +27 -0
  111. package/dist/ui/screens/DebugScreen.d.ts +6 -0
  112. package/dist/ui/screens/DebugScreen.js +39 -0
  113. package/dist/ui/screens/InitScreen.d.ts +6 -0
  114. package/dist/ui/screens/InitScreen.js +138 -0
  115. package/dist/ui/screens/MenuScreen.d.ts +7 -0
  116. package/dist/ui/screens/MenuScreen.js +38 -0
  117. package/dist/ui/state.d.ts +72 -0
  118. package/dist/ui/state.js +107 -0
  119. package/dist/ui/steps/AccountStep.d.ts +8 -0
  120. package/dist/ui/steps/AccountStep.js +42 -0
  121. package/dist/ui/steps/ApiKeyStep.d.ts +8 -0
  122. package/dist/ui/steps/ApiKeyStep.js +91 -0
  123. package/dist/ui/steps/ClientsStep.d.ts +10 -0
  124. package/dist/ui/steps/ClientsStep.js +69 -0
  125. package/dist/ui/steps/CredentialKindStep.d.ts +7 -0
  126. package/dist/ui/steps/CredentialKindStep.js +18 -0
  127. package/dist/ui/steps/EnvironmentStep.d.ts +8 -0
  128. package/dist/ui/steps/EnvironmentStep.js +37 -0
  129. package/dist/ui/steps/LoginStep.d.ts +7 -0
  130. package/dist/ui/steps/LoginStep.js +56 -0
  131. package/dist/ui/steps/SkillsStep.d.ts +7 -0
  132. package/dist/ui/steps/SkillsStep.js +7 -0
  133. package/dist/ui/steps/SummaryStep.d.ts +8 -0
  134. package/dist/ui/steps/SummaryStep.js +41 -0
  135. package/dist/ui/steps/WritingStep.d.ts +10 -0
  136. package/dist/ui/steps/WritingStep.js +96 -0
  137. package/dist/ui/theme.d.ts +53 -0
  138. package/dist/ui/theme.js +66 -0
  139. package/dist/ui/tui/App.d.ts +10 -0
  140. package/dist/ui/tui/App.js +51 -0
  141. package/dist/ui/tui/components/ContextStrip.d.ts +11 -0
  142. package/dist/ui/tui/components/ContextStrip.js +8 -0
  143. package/dist/ui/tui/components/TitleBar.d.ts +6 -0
  144. package/dist/ui/tui/components/TitleBar.js +18 -0
  145. package/dist/ui/tui/components/WizardChecklist.d.ts +7 -0
  146. package/dist/ui/tui/components/WizardChecklist.js +69 -0
  147. package/dist/ui/tui/hooks/keyboard-hints-utils.d.ts +26 -0
  148. package/dist/ui/tui/hooks/keyboard-hints-utils.js +69 -0
  149. package/dist/ui/tui/hooks/useKeyBindings.d.ts +14 -0
  150. package/dist/ui/tui/hooks/useKeyBindings.js +44 -0
  151. package/dist/ui/tui/hooks/useKeyboardHints.d.ts +13 -0
  152. package/dist/ui/tui/hooks/useKeyboardHints.js +38 -0
  153. package/dist/ui/tui/hooks/useStdoutDimensions.d.ts +8 -0
  154. package/dist/ui/tui/hooks/useStdoutDimensions.js +28 -0
  155. package/dist/ui/tui/primitives/BlinkingLabel.d.ts +19 -0
  156. package/dist/ui/tui/primitives/BlinkingLabel.js +25 -0
  157. package/dist/ui/tui/primitives/ConfirmPrompt.d.ts +16 -0
  158. package/dist/ui/tui/primitives/ConfirmPrompt.js +36 -0
  159. package/dist/ui/tui/primitives/KeyboardHintsBar.d.ts +2 -0
  160. package/dist/ui/tui/primitives/KeyboardHintsBar.js +8 -0
  161. package/dist/ui/tui/primitives/PickerMenu.d.ts +38 -0
  162. package/dist/ui/tui/primitives/PickerMenu.js +162 -0
  163. package/dist/ui/tui/primitives/PromptLabel.d.ts +6 -0
  164. package/dist/ui/tui/primitives/PromptLabel.js +6 -0
  165. package/dist/ui/tui/primitives/ScreenContainer.d.ts +39 -0
  166. package/dist/ui/tui/primitives/ScreenContainer.js +39 -0
  167. package/dist/ui/tui/primitives/Spinner.d.ts +7 -0
  168. package/dist/ui/tui/primitives/Spinner.js +18 -0
  169. package/dist/ui/tui/screens/DashScreen.d.ts +6 -0
  170. package/dist/ui/tui/screens/DashScreen.js +37 -0
  171. package/dist/ui/tui/screens/DebugScreen.d.ts +6 -0
  172. package/dist/ui/tui/screens/DebugScreen.js +48 -0
  173. package/dist/ui/tui/screens/EnvScreen.d.ts +6 -0
  174. package/dist/ui/tui/screens/EnvScreen.js +192 -0
  175. package/dist/ui/tui/screens/InitScreen.d.ts +9 -0
  176. package/dist/ui/tui/screens/InitScreen.js +102 -0
  177. package/dist/ui/tui/screens/MenuScreen.d.ts +7 -0
  178. package/dist/ui/tui/screens/MenuScreen.js +84 -0
  179. package/dist/ui/tui/start-tui.d.ts +11 -0
  180. package/dist/ui/tui/start-tui.js +72 -0
  181. package/dist/ui/tui/steps/AccountStep.d.ts +8 -0
  182. package/dist/ui/tui/steps/AccountStep.js +42 -0
  183. package/dist/ui/tui/steps/ApiKeyStep.d.ts +8 -0
  184. package/dist/ui/tui/steps/ApiKeyStep.js +53 -0
  185. package/dist/ui/tui/steps/ClientsStep.d.ts +10 -0
  186. package/dist/ui/tui/steps/ClientsStep.js +52 -0
  187. package/dist/ui/tui/steps/CredentialKindStep.d.ts +7 -0
  188. package/dist/ui/tui/steps/CredentialKindStep.js +18 -0
  189. package/dist/ui/tui/steps/EnvironmentStep.d.ts +8 -0
  190. package/dist/ui/tui/steps/EnvironmentStep.js +38 -0
  191. package/dist/ui/tui/steps/LoginStep.d.ts +8 -0
  192. package/dist/ui/tui/steps/LoginStep.js +79 -0
  193. package/dist/ui/tui/steps/SkillsStep.d.ts +7 -0
  194. package/dist/ui/tui/steps/SkillsStep.js +7 -0
  195. package/dist/ui/tui/steps/SummaryStep.d.ts +10 -0
  196. package/dist/ui/tui/steps/SummaryStep.js +133 -0
  197. package/dist/ui/tui/steps/WritingStep.d.ts +10 -0
  198. package/dist/ui/tui/steps/WritingStep.js +101 -0
  199. package/package.json +62 -0
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
4
+ import { Colors } from '../../theme.js';
5
+ import { useKeyBindings } from '../hooks/useKeyBindings.js';
6
+ import { PickerMenu } from '../primitives/PickerMenu.js';
7
+ import { Spinner } from '../primitives/Spinner.js';
8
+ export function ClientsStep({ state, dispatch, clients }) {
9
+ useAsyncEffect(async (signal) => {
10
+ if (state.detectedClients)
11
+ return;
12
+ const detected = [];
13
+ for (const c of clients) {
14
+ if (await c.isClientSupported())
15
+ detected.push(c.id);
16
+ }
17
+ if (signal.cancelled)
18
+ return;
19
+ dispatch({ type: 'DETECT_OK', clients: detected });
20
+ }, [], (err) => dispatch({
21
+ type: 'ERROR',
22
+ message: err instanceof Error ? err.message : String(err),
23
+ }));
24
+ const initial = state.selectedClients ??
25
+ (state.detectedClients && state.detectedClients.length > 0
26
+ ? state.detectedClients
27
+ : ['claude-code']);
28
+ useKeyBindings('clients-skip', [
29
+ {
30
+ match: 's',
31
+ label: 's',
32
+ action: 'skip',
33
+ priority: 40,
34
+ handler: () => dispatch({ type: 'PICK_CLIENTS', clients: [] }),
35
+ },
36
+ ], { isActive: state.detectedClients != null });
37
+ if (!state.detectedClients) {
38
+ return _jsx(Spinner, { label: "Detecting installed AI clients\u2026" });
39
+ }
40
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PickerMenu, { id: "clients", mode: "multi", message: "Which clients should we configure?", options: clients.map((c) => {
41
+ const detected = state.detectedClients?.includes(c.id);
42
+ return {
43
+ label: c.name,
44
+ value: c.id,
45
+ hint: detected ? '(detected)' : '(not installed)',
46
+ description: c.installTarget(),
47
+ };
48
+ }), defaultValue: initial, onSelect: (values) => dispatch({
49
+ type: 'PICK_CLIENTS',
50
+ clients: values,
51
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Press ", _jsx(Text, { color: Colors.accent, children: "s" }), " to skip MCP installation entirely."] }) })] }));
52
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { Action } from '../../state.js';
3
+ interface Props {
4
+ dispatch: React.Dispatch<Action>;
5
+ }
6
+ export declare function CredentialKindStep({ dispatch }: Props): React.ReactElement;
7
+ export {};
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { PickerMenu } from '../primitives/PickerMenu.js';
4
+ export function CredentialKindStep({ dispatch }) {
5
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PickerMenu, { id: "credentialKind", message: "Which credential should the MCP use?", defaultValue: 'scoped', options: [
6
+ {
7
+ label: 'API key · single env, limited permissions (recommended)',
8
+ value: 'scoped',
9
+ },
10
+ {
11
+ label: 'User JWT · broad access across envs (not yet supported)',
12
+ value: 'jwt',
13
+ disabled: true,
14
+ },
15
+ ], onSelect: (v) => {
16
+ dispatch({ type: 'PICK_CRED_KIND', kind: v });
17
+ } }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "API keys are safer (smaller blast radius if leaked) and the only kind Stigg's MCP server accepts today." }), _jsx(Text, { dimColor: true, children: "JWT support will return once the MCP server accepts Bearer auth." })] })] }));
18
+ }
@@ -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 EnvironmentStep({ state, dispatch }: Props): React.ReactElement;
8
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box } from 'ink';
3
+ import { listEnvironments } from '../../../api/client.js';
4
+ import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
5
+ import { PickerMenu } from '../primitives/PickerMenu.js';
6
+ import { Spinner } from '../primitives/Spinner.js';
7
+ export function EnvironmentStep({ state, dispatch }) {
8
+ useAsyncEffect(async (signal) => {
9
+ if (state.environments || !state.session || !state.account)
10
+ return;
11
+ const envs = await listEnvironments(state.session.accessToken, state.account.id);
12
+ if (signal.cancelled)
13
+ return;
14
+ dispatch({
15
+ type: 'ENVS_LOADED',
16
+ environments: envs.map((e) => ({
17
+ id: e.id,
18
+ name: e.name,
19
+ slug: e.slug,
20
+ })),
21
+ });
22
+ }, [state.session?.accessToken, state.account?.id], (err) => dispatch({
23
+ type: 'ERROR',
24
+ message: err instanceof Error ? err.message : String(err),
25
+ }));
26
+ if (!state.environments) {
27
+ return _jsx(Spinner, { label: "Loading environments\u2026" });
28
+ }
29
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(PickerMenu, { id: "environment", message: "Which environment?", visibleCount: 10, options: state.environments.map((e) => ({
30
+ label: e.name,
31
+ value: e.id,
32
+ hint: e.slug ? `(${e.slug})` : undefined,
33
+ })), onSelect: (v) => {
34
+ const env = state.environments?.find((e) => e.id === v);
35
+ if (env)
36
+ dispatch({ type: 'PICK_ENV', environment: env });
37
+ } }) }));
38
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { Action } from '../../state.js';
3
+ interface Props {
4
+ dispatch: React.Dispatch<Action>;
5
+ callbackPort?: number;
6
+ }
7
+ export declare function LoginStep({ dispatch, callbackPort }: Props): React.ReactElement;
8
+ export {};
@@ -0,0 +1,79 @@
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 { authenticate } from '../../../auth/oauth.js';
5
+ import { saveSession } from '../../../auth/storage.js';
6
+ import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
7
+ import { Colors } from '../../theme.js';
8
+ import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
9
+ import { useKeyBindings } from '../hooks/useKeyBindings.js';
10
+ import { PromptLabel } from '../primitives/PromptLabel.js';
11
+ import { Spinner } from '../primitives/Spinner.js';
12
+ export function LoginStep({ dispatch, callbackPort }) {
13
+ const [started, setStarted] = useState(false);
14
+ const [message, setMessage] = useState('Opening browser to log in…');
15
+ const [authUrl, setAuthUrl] = useState(null);
16
+ const [errored, setErrored] = useState(null);
17
+ const preStartBindings = [
18
+ {
19
+ match: KeyMatch.Return,
20
+ label: 'enter',
21
+ action: 'continue',
22
+ handler: () => setStarted(true),
23
+ },
24
+ ];
25
+ useKeyBindings('login', preStartBindings, {
26
+ isActive: !started && !errored,
27
+ });
28
+ useAsyncEffect(async (signal) => {
29
+ if (!started)
30
+ return;
31
+ try {
32
+ const session = await authenticate({
33
+ port: callbackPort,
34
+ onAuthUrlReady: (url, port) => {
35
+ if (!signal.cancelled) {
36
+ setAuthUrl(url);
37
+ setMessage(`Waiting for callback on http://127.0.0.1:${port}`);
38
+ }
39
+ },
40
+ });
41
+ await saveSession(session);
42
+ if (signal.cancelled)
43
+ return;
44
+ dispatch({
45
+ type: 'LOGIN_OK',
46
+ session: {
47
+ email: session.email,
48
+ accessToken: session.access_token,
49
+ },
50
+ });
51
+ }
52
+ catch (err) {
53
+ if (signal.cancelled)
54
+ return;
55
+ setErrored(err instanceof Error ? err.message : String(err));
56
+ dispatch({
57
+ type: 'ERROR',
58
+ message: err instanceof Error ? err.message : String(err),
59
+ });
60
+ }
61
+ }, [started]);
62
+ if (errored) {
63
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: Colors.error, children: "\u2717 Login failed" }), _jsx(Text, { dimColor: true, children: errored })] }));
64
+ }
65
+ if (!started) {
66
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "Sign in to Stigg" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "We'll open your browser to complete the OAuth flow." }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Press ", _jsx(Text, { color: Colors.accent, children: "enter" }), " to continue."] }) })] }));
67
+ }
68
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Spinner, { label: message }), authUrl && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "If your browser doesn't open, visit:" }), _jsx(Text, { color: Colors.accent, wrap: "hard", children: hyperlink(authUrl, authUrl) })] }))] }));
69
+ }
70
+ /**
71
+ * Emit an OSC 8 hyperlink. Modern terminals (iTerm2, WezTerm, kitty, Hyper,
72
+ * recent Terminal.app / gnome-terminal / Windows Terminal) make the display
73
+ * text clickable; terminals without OSC 8 support fall back to plain text.
74
+ */
75
+ function hyperlink(url, display) {
76
+ const OSC = '\u001b]';
77
+ const BEL = '\u0007';
78
+ return `${OSC}8;;${url}${BEL}${display}${OSC}8;;${BEL}`;
79
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { Action } from '../../state.js';
3
+ interface Props {
4
+ dispatch: React.Dispatch<Action>;
5
+ }
6
+ export declare function SkillsStep({ dispatch }: Props): React.ReactElement;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { ConfirmPrompt } from '../primitives/ConfirmPrompt.js';
4
+ import { PromptLabel } from '../primitives/PromptLabel.js';
5
+ export function SkillsStep({ dispatch }) {
6
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "Install Stigg skills?" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Installs Stigg's 11 agent skills (pricing, entitlements, subscriptions, credits, widgets, webhooks\u2026)." }), _jsxs(Text, { dimColor: true, children: ["Claude Code \u2192 ", _jsx(Text, { color: "white", children: "claude plugin install stigg@stigg-marketplace" })] }), _jsxs(Text, { dimColor: true, children: ["Other clients \u2192 ", _jsx(Text, { color: "white", children: "npx skills add stiggio/skills --all" })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(ConfirmPrompt, { id: "skills", defaultAnswer: "yes", onConfirm: () => dispatch({ type: 'SKILLS_CHOICE', install: true }), onCancel: () => dispatch({ type: 'SKILLS_CHOICE', install: false }) }) })] }));
7
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { type LaunchRequest } from '../../../launch/agent.js';
3
+ import type { WizardState } from '../../state.js';
4
+ interface Props {
5
+ state: WizardState;
6
+ onDone: () => void;
7
+ onLaunch: (req: LaunchRequest) => void;
8
+ }
9
+ export declare function SummaryStep({ state, onDone, onLaunch }: Props): React.ReactElement;
10
+ export {};
@@ -0,0 +1,133 @@
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 { formatApiKeyShort } from '../../../api/format-key.js';
5
+ import { detectHostAgent } from '../../../headless/host-agent.js';
6
+ import { nextStepPrompt } from '../../../headless/next-step-prompt.js';
7
+ import { agentName, chooseDefaultAgent, copyPromptToClipboard, listLaunchableAgents, skipPermissionFlag, } from '../../../launch/agent.js';
8
+ import { saveSetup } from '../../../setup/storage.js';
9
+ import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
10
+ import { Colors } from '../../theme.js';
11
+ import { ContextStrip } from '../components/ContextStrip.js';
12
+ import { WizardChecklist } from '../components/WizardChecklist.js';
13
+ import { KeyMatch } from '../hooks/keyboard-hints-utils.js';
14
+ import { useKeyBindings } from '../hooks/useKeyBindings.js';
15
+ import { PickerMenu } from '../primitives/PickerMenu.js';
16
+ import { PromptLabel } from '../primitives/PromptLabel.js';
17
+ import { Spinner } from '../primitives/Spinner.js';
18
+ function credentialLabel(state) {
19
+ if (!state.credential)
20
+ return '?';
21
+ const name = state.selectedKey?.name ?? 'api key';
22
+ const short = state.selectedKey ? formatApiKeyShort(state.selectedKey) : '…????';
23
+ return `API key ${name} (${short})`;
24
+ }
25
+ export function SummaryStep({ state, onDone, onLaunch }) {
26
+ const [detection, setDetection] = useState(null);
27
+ const [phase, setPhase] = useState('agent');
28
+ const [chosenAgent, setChosenAgent] = useState(null);
29
+ const [copyNote, setCopyNote] = useState(null);
30
+ const env = state.environment;
31
+ useEffect(() => {
32
+ if (!state.session || !state.account || !state.environment || !state.credential) {
33
+ return;
34
+ }
35
+ void saveSetup({
36
+ user: state.session.email,
37
+ account: state.account.displayName,
38
+ accountId: state.account.id,
39
+ environment: state.environment.name,
40
+ environmentId: state.environment.id,
41
+ credentialLabel: credentialLabel(state),
42
+ environments: state.environments,
43
+ clients: state.selectedClients,
44
+ completedAt: new Date().toISOString(),
45
+ });
46
+ // intentionally fire once on mount of summary step
47
+ }, []);
48
+ // Detect which agents we can launch as a foreground CLI (independent of the
49
+ // MCP-client detection from the wizard) and which one to preselect.
50
+ useAsyncEffect(async (signal) => {
51
+ const agents = listLaunchableAgents();
52
+ const host = detectHostAgent();
53
+ const defaultId = chooseDefaultAgent(host, state.selectedClients, agents);
54
+ if (signal.cancelled)
55
+ return;
56
+ setDetection({ agents, defaultId, hostId: host?.id });
57
+ }, []);
58
+ const detecting = detection === null;
59
+ const canLaunch = detection !== null && detection.agents.length > 0 && env !== undefined;
60
+ const showFallback = detection !== null && !canLaunch;
61
+ // Fallback bindings — only active when there's no launchable agent and we
62
+ // fall back to the static hints. When the picker is up it owns the keys.
63
+ const fallbackBindings = [
64
+ { match: KeyMatch.Return, label: 'enter', action: 'menu', handler: onDone },
65
+ ];
66
+ if (env) {
67
+ fallbackBindings.push({
68
+ match: 'c',
69
+ label: 'c',
70
+ action: 'copy prompt',
71
+ priority: 40,
72
+ handler: () => {
73
+ void copyPromptToClipboard(nextStepPrompt(env.name)).then((ok) => setCopyNote(ok ? 'Copied the onboarding prompt to your clipboard.' : 'No clipboard tool found.'));
74
+ },
75
+ });
76
+ }
77
+ useKeyBindings('summary-fallback', fallbackBindings, { isActive: showFallback });
78
+ const entries = [
79
+ { label: 'Account', value: state.session?.email ?? '?' },
80
+ { label: 'Credential', value: credentialLabel(state) },
81
+ { label: 'Environment', value: env?.name ?? '?' },
82
+ ];
83
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "Setup complete" }), _jsx(Box, { marginTop: 1, children: _jsx(WizardChecklist, { state: state }) }), _jsx(Box, { marginTop: 1, children: _jsx(ContextStrip, { entries: entries }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [detecting && _jsx(Spinner, { label: "Detecting installed agents\u2026" }), canLaunch && renderLaunchPicker(), showFallback && renderStaticNext()] })] }));
84
+ function renderLaunchPicker() {
85
+ // canLaunch guarantees a defined env and a non-empty agent list.
86
+ const seedPrompt = nextStepPrompt(env.name);
87
+ if (phase === 'perms' && chosenAgent) {
88
+ const permsOptions = [
89
+ {
90
+ label: 'Normal interactive',
91
+ value: 'normal',
92
+ description: 'The agent asks for permission as usual',
93
+ },
94
+ {
95
+ label: 'Skip permission prompts',
96
+ value: 'skip',
97
+ description: skipPermissionFlag(chosenAgent),
98
+ },
99
+ { label: '← Back', value: 'back' },
100
+ ];
101
+ return (_jsx(PickerMenu, { id: "launch-perms", mode: "single", message: `Launch ${agentName(chosenAgent)} — permission mode`, options: permsOptions, defaultValue: "normal", onSelect: (value) => {
102
+ if (value === 'back') {
103
+ setPhase('agent');
104
+ return;
105
+ }
106
+ onLaunch({
107
+ agentId: chosenAgent,
108
+ prompt: seedPrompt,
109
+ skipPermissions: value === 'skip',
110
+ });
111
+ } }));
112
+ }
113
+ const agentOptions = [
114
+ ...detection.agents.map((a) => ({
115
+ label: a.name,
116
+ value: a.id,
117
+ hint: a.id === detection.hostId ? '(detected)' : undefined,
118
+ })),
119
+ { label: 'Return to menu', value: '__menu__' },
120
+ ];
121
+ return (_jsx(PickerMenu, { id: "launch-agent", mode: "single", message: "Continue in your agent", options: agentOptions, defaultValue: detection.defaultId, onSelect: (value) => {
122
+ if (value === '__menu__') {
123
+ onDone();
124
+ return;
125
+ }
126
+ setChosenAgent(value);
127
+ setPhase('perms');
128
+ } }));
129
+ }
130
+ function renderStaticNext() {
131
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Next:" }), _jsxs(Text, { children: [' ', _jsx(Text, { color: Colors.accent, children: "/stigg help" }), ' ', "in Claude Code"] }), _jsxs(Text, { children: [' ', _jsx(Text, { color: Colors.accent, children: "@stigg" }), ' ', "in Cursor (after restart)"] }), _jsxs(Text, { children: [' ', _jsx(Text, { color: Colors.accent, children: "terminal env" }), ' ', "to switch the active environment"] }), _jsxs(Text, { children: [' ', _jsx(Text, { color: Colors.accent, children: "terminal dash" }), ' ', "to open the Stigg dashboard"] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["press ", _jsx(Text, { color: Colors.accent, children: "enter" }), " to return to menu", env ? (_jsxs(Text, { dimColor: true, children: [' · ', _jsx(Text, { color: Colors.accent, children: "c" }), " to copy the onboarding prompt"] })) : null] }) }), copyNote && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: copyNote }) }))] }));
132
+ }
133
+ }
@@ -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 WritingStep({ state, dispatch, clients }: Props): React.ReactElement;
10
+ export {};
@@ -0,0 +1,101 @@
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 { installStiggSkills } from '../../../skills/install.js';
5
+ import { useAsyncEffect } from '../../hooks/useAsyncEffect.js';
6
+ import { Colors } from '../../theme.js';
7
+ import { ConfirmPrompt } from '../primitives/ConfirmPrompt.js';
8
+ import { PromptLabel } from '../primitives/PromptLabel.js';
9
+ import { Spinner } from '../primitives/Spinner.js';
10
+ function pickSelected(clients, ids) {
11
+ const set = new Set(ids);
12
+ return clients.filter((c) => set.has(c.id));
13
+ }
14
+ export function WritingStep({ state, dispatch, clients }) {
15
+ const [phase, setPhase] = useState({ kind: 'init' });
16
+ const [currentClient, setCurrentClient] = useState(null);
17
+ const [skipped, setSkipped] = useState([]);
18
+ useAsyncEffect(async (signal) => {
19
+ if (phase.kind !== 'init')
20
+ return;
21
+ const selected = pickSelected(clients, state.selectedClients ?? []);
22
+ const existing = [];
23
+ for (const c of selected) {
24
+ if (await c.hasExistingEntry())
25
+ existing.push(c);
26
+ }
27
+ if (signal.cancelled)
28
+ return;
29
+ if (existing.length > 0) {
30
+ setPhase({ kind: 'needs-confirm', existing });
31
+ }
32
+ else {
33
+ setPhase({ kind: 'writing' });
34
+ }
35
+ }, [phase.kind], (err) => dispatch({
36
+ type: 'ERROR',
37
+ message: err instanceof Error ? err.message : String(err),
38
+ }));
39
+ useAsyncEffect(async (signal) => {
40
+ if (phase.kind !== 'writing')
41
+ return;
42
+ const skippedSet = new Set(skipped);
43
+ const selected = pickSelected(clients, state.selectedClients ?? []).filter((c) => !skippedSet.has(c.id));
44
+ for (const c of selected) {
45
+ if (signal.cancelled)
46
+ return;
47
+ setCurrentClient(c);
48
+ const result = await c.addServer(state.credential);
49
+ if (!result.ok) {
50
+ dispatch({
51
+ type: 'ERROR',
52
+ message: `${c.name}: ${result.reason ?? 'install failed'}`,
53
+ });
54
+ return;
55
+ }
56
+ dispatch({ type: 'WRITE_OK', result: c.toWriteResult(result) });
57
+ }
58
+ setCurrentClient(null);
59
+ if (signal.cancelled)
60
+ return;
61
+ if (state.installSkills) {
62
+ setPhase({ kind: 'skills' });
63
+ }
64
+ else {
65
+ setPhase({ kind: 'advancing' });
66
+ }
67
+ }, [phase.kind]);
68
+ useAsyncEffect(async (signal) => {
69
+ if (phase.kind !== 'skills')
70
+ return;
71
+ const result = await installStiggSkills(state.selectedClients ?? []);
72
+ if (signal.cancelled)
73
+ return;
74
+ dispatch({ type: 'SKILLS_INSTALL_OK', result });
75
+ }, [phase.kind], (err) => dispatch({
76
+ type: 'ERROR',
77
+ message: err instanceof Error ? err.message : String(err),
78
+ }));
79
+ useEffect(() => {
80
+ if (phase.kind === 'advancing') {
81
+ dispatch({ type: 'ADVANCE' });
82
+ }
83
+ }, [phase.kind, dispatch]);
84
+ if (phase.kind === 'needs-confirm') {
85
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PromptLabel, { children: "Existing stigg entry found" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "The following clients already have a stigg MCP entry:" }) }), _jsx(Box, { flexDirection: "column", marginY: 1, children: phase.existing.map((c) => (_jsxs(Text, { children: [' ', _jsx(Text, { color: Colors.accent, children: "\u2022" }), " ", c.name, _jsx(Text, { dimColor: true, children: ` ${c.installTarget()}` })] }, c.id))) }), _jsx(Text, { children: "Overwrite? Existing entries will be backed up." }), _jsx(Box, { marginTop: 1, children: _jsx(ConfirmPrompt, { id: "writing-overwrite", defaultAnswer: "yes", onConfirm: () => setPhase({ kind: 'writing' }), onCancel: () => {
86
+ setSkipped(phase.existing.map((c) => c.id));
87
+ setPhase({ kind: 'writing' });
88
+ } }) }), skipped.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["Will skip:", ' ', clients
89
+ .filter((c) => skipped.includes(c.id))
90
+ .map((c) => c.name)
91
+ .join(', ')] }) }))] }));
92
+ }
93
+ if (phase.kind === 'writing') {
94
+ const label = currentClient ? `Configuring ${currentClient.name}…` : 'Configuring AI clients…';
95
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Spinner, { label: label }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: state.writes.map((w, i) => (_jsxs(Text, { color: Colors.success, children: [' ✓ ', w.path, w.backedUpTo ? (_jsxs(Text, { dimColor: true, children: [' (backup: ', w.backedUpTo, ')'] })) : null] }, i))) })] }));
96
+ }
97
+ if (phase.kind === 'skills') {
98
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Spinner, { label: "Installing Stigg skills\u2026" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: state.writes.map((w, i) => (_jsxs(Text, { color: Colors.success, children: [' ✓ ', w.path] }, i))) })] }));
99
+ }
100
+ return _jsx(Spinner, { label: "Preparing\u2026" });
101
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@stigg/terminal",
3
+ "version": "0.0.1-alpha",
4
+ "type": "module",
5
+ "description": "Stigg terminal — interactive setup TUI for AI coding assistants",
6
+ "license": "ISC",
7
+ "author": "Stigg",
8
+ "homepage": "https://stigg.io",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/stiggio/stigg-lab.git",
12
+ "directory": "projects/terminal"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/stiggio/stigg-lab/issues"
16
+ },
17
+ "keywords": [
18
+ "stigg",
19
+ "cli",
20
+ "tui",
21
+ "mcp",
22
+ "ai",
23
+ "setup",
24
+ "ink"
25
+ ],
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "bin": {
33
+ "stiggt": "./dist/bin.js"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsc",
40
+ "prepublishOnly": "tsc",
41
+ "dev": "tsx src/bin.ts",
42
+ "start": "node dist/bin.js",
43
+ "lint": "eslint src/",
44
+ "test": "jest --runInBand --forceExit"
45
+ },
46
+ "dependencies": {
47
+ "@iarna/toml": "^3.0.0",
48
+ "commander": "^14.0.1",
49
+ "graphql-request": "^7.4.0",
50
+ "ink": "^7.0.5",
51
+ "jsonc-parser": "^3.3.1",
52
+ "open": "^11.0.0",
53
+ "openid-client": "^6.8.4",
54
+ "picocolors": "^1.1.1",
55
+ "react": "19.2.4"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.0.0",
59
+ "@types/react": "^19.2.14",
60
+ "tsx": "^4.20.3"
61
+ }
62
+ }