@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,138 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { useMemo, useReducer } from "react";
4
+ import { claudeCodeHandler, cursorHandler, } from "../../mcp/clients.js";
5
+ import { Card } from "../components/Card.js";
6
+ import { ContextRow } from "../components/ContextRow.js";
7
+ import { Footer } from "../components/Footer.js";
8
+ import { INITIAL_STATE, wizardReducer, } from "../state.js";
9
+ import { AccountStep } from "../steps/AccountStep.js";
10
+ import { ApiKeyStep } from "../steps/ApiKeyStep.js";
11
+ import { ClientsStep } from "../steps/ClientsStep.js";
12
+ import { CredentialKindStep } from "../steps/CredentialKindStep.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
+ const TITLE = "stigg · cyberdeck · init";
19
+ const NAV_HINTS = [
20
+ { key: "↑↓", label: "nav" },
21
+ { key: "enter", label: "confirm" },
22
+ { key: "esc", label: "menu" },
23
+ { key: "ctrl+c", label: "quit" },
24
+ ];
25
+ const MULTISELECT_HINTS = [
26
+ { key: "↑↓", label: "nav" },
27
+ { key: "space", label: "toggle" },
28
+ { key: "enter", label: "confirm" },
29
+ { key: "esc", label: "menu" },
30
+ { key: "ctrl+c", label: "quit" },
31
+ ];
32
+ const HINTS_FOR = {
33
+ login: [
34
+ { key: "enter", label: "continue" },
35
+ { key: "esc", label: "menu" },
36
+ { key: "ctrl+c", label: "cancel" },
37
+ ],
38
+ account: NAV_HINTS,
39
+ credentialKind: NAV_HINTS,
40
+ environment: NAV_HINTS,
41
+ apiKey: NAV_HINTS,
42
+ clients: MULTISELECT_HINTS,
43
+ skills: [
44
+ { key: "y/n", label: "" },
45
+ { key: "esc", label: "menu" },
46
+ { key: "ctrl+c", label: "quit" },
47
+ ],
48
+ writing: [{ key: "ctrl+c", label: "cancel" }],
49
+ summary: [{ key: "any key", label: "menu" }],
50
+ };
51
+ function stepPath(state) {
52
+ const path = ["login"];
53
+ const hideAccount = state.accounts != null && state.accounts.length === 1;
54
+ if (!hideAccount)
55
+ path.push("account");
56
+ path.push("credentialKind", "environment");
57
+ if (state.credentialKind === "scoped")
58
+ path.push("apiKey");
59
+ path.push("clients");
60
+ const hasClaudeCode = (state.selectedClients ?? []).includes("claude-code");
61
+ if (hasClaudeCode)
62
+ path.push("skills");
63
+ path.push("writing");
64
+ path.push("summary");
65
+ return path;
66
+ }
67
+ function stepNumber(state) {
68
+ const path = stepPath(state);
69
+ const idx = path.indexOf(state.step);
70
+ const total = path.length - 1;
71
+ return { current: idx >= 0 ? idx + 1 : 1, total };
72
+ }
73
+ function contextEntries(state) {
74
+ const entries = [];
75
+ if (state.session) {
76
+ entries.push({ label: "user", value: state.session.email });
77
+ }
78
+ if (state.account) {
79
+ entries.push({ label: "account", value: state.account.displayName });
80
+ }
81
+ if (state.credentialKind) {
82
+ entries.push({
83
+ label: "credential",
84
+ value: state.credentialKind === "scoped" ? "API key" : "User JWT",
85
+ });
86
+ }
87
+ if (state.environment) {
88
+ entries.push({ label: "environment", value: state.environment.name });
89
+ }
90
+ if (state.selectedKey) {
91
+ entries.push({
92
+ label: "key",
93
+ value: `${state.selectedKey.name} (sk_…${state.selectedKey.lastFour})`,
94
+ });
95
+ }
96
+ return entries;
97
+ }
98
+ function renderStep(state, dispatch, handlers, onDone) {
99
+ switch (state.step) {
100
+ case "login":
101
+ return _jsx(LoginStep, { dispatch: dispatch });
102
+ case "account":
103
+ return _jsx(AccountStep, { state: state, dispatch: dispatch });
104
+ case "credentialKind":
105
+ return _jsx(CredentialKindStep, { dispatch: dispatch });
106
+ case "environment":
107
+ return _jsx(EnvironmentStep, { state: state, dispatch: dispatch });
108
+ case "apiKey":
109
+ return _jsx(ApiKeyStep, { state: state, dispatch: dispatch });
110
+ case "clients":
111
+ return (_jsx(ClientsStep, { state: state, dispatch: dispatch, handlers: handlers }));
112
+ case "skills":
113
+ return _jsx(SkillsStep, { dispatch: dispatch });
114
+ case "writing":
115
+ return (_jsx(WritingStep, { state: state, dispatch: dispatch, handlers: handlers }));
116
+ case "summary":
117
+ return _jsx(SummaryStep, { state: state, onDone: onDone });
118
+ }
119
+ }
120
+ export function InitScreen({ onDone }) {
121
+ const [state, dispatch] = useReducer(wizardReducer, INITIAL_STATE);
122
+ const handlers = useMemo(() => ({
123
+ "claude-code": claudeCodeHandler(),
124
+ cursor: cursorHandler(),
125
+ }), []);
126
+ useInput((_input, key) => {
127
+ if (key.escape && state.step !== "summary") {
128
+ onDone();
129
+ }
130
+ });
131
+ const { current, total } = stepNumber(state);
132
+ const status = state.error != null
133
+ ? "error"
134
+ : state.step === "summary"
135
+ ? "done"
136
+ : "active";
137
+ return (_jsxs(Card, { title: TITLE, subtitle: `step ${current} of ${total}`, status: status, footer: _jsx(Footer, { hints: HINTS_FOR[state.step] }), children: [_jsx(ContextRow, { entries: contextEntries(state) }), renderStep(state, dispatch, handlers, onDone), state.error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", state.error] }) }))] }));
138
+ }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export type MenuChoice = "init" | "dash" | "debug";
3
+ interface Props {
4
+ onPick: (choice: MenuChoice) => void;
5
+ }
6
+ export declare function MenuScreen({ onPick }: Props): React.ReactElement;
7
+ export {};
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Select } from "@inkjs/ui";
3
+ import { Box, Text, useApp } from "ink";
4
+ import { Card } from "../components/Card.js";
5
+ import { Footer } from "../components/Footer.js";
6
+ const OPTIONS = [
7
+ {
8
+ value: "init",
9
+ label: "init Set up Stigg in this project — auth, MCP config, skills",
10
+ },
11
+ {
12
+ value: "dash",
13
+ label: "dash Open the Stigg dashboard in your browser",
14
+ },
15
+ {
16
+ value: "debug",
17
+ label: "debug Stream Stigg audit + usage logs (placeholder)",
18
+ },
19
+ {
20
+ value: "exit",
21
+ label: "exit Quit cyberdeck",
22
+ },
23
+ ];
24
+ const HINTS = [
25
+ { key: "↑↓", label: "nav" },
26
+ { key: "enter", label: "confirm" },
27
+ { key: "ctrl+c", label: "quit" },
28
+ ];
29
+ export function MenuScreen({ onPick }) {
30
+ const { exit } = useApp();
31
+ return (_jsxs(Card, { title: "stigg \u00B7 cyberdeck", footer: _jsx(Footer, { hints: HINTS }), children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: "What do you want to do?" }) }), _jsx(Select, { options: OPTIONS, onChange: (value) => {
32
+ if (value === "exit") {
33
+ exit();
34
+ return;
35
+ }
36
+ onPick(value);
37
+ } })] }));
38
+ }
@@ -0,0 +1,72 @@
1
+ import type { InstallResult as SkillsInstallResult } from '../skills/install.js';
2
+ import type { Account, ApiKey, ClientId, Credential, Environment, Session, WriteResult } from '../types.js';
3
+ export type StepId = 'login' | 'account' | 'environment' | 'apiKey' | 'clients' | 'skills' | 'writing' | 'summary';
4
+ export type { ApiKey };
5
+ export interface WizardState {
6
+ step: StepId;
7
+ history: StepId[];
8
+ session?: Session;
9
+ accounts?: Account[];
10
+ account?: Account;
11
+ environments?: Environment[];
12
+ environment?: Environment;
13
+ apiKeys?: ApiKey[];
14
+ selectedKey?: ApiKey;
15
+ credential?: Credential;
16
+ detectedClients?: ClientId[];
17
+ selectedClients?: ClientId[];
18
+ installSkills?: boolean;
19
+ writes: WriteResult[];
20
+ installedSkills?: SkillsInstallResult;
21
+ error?: string;
22
+ }
23
+ export type Action = {
24
+ type: 'LOGIN_OK';
25
+ session: Session;
26
+ } | {
27
+ type: 'ACCOUNTS_LOADED';
28
+ accounts: Account[];
29
+ } | {
30
+ type: 'PICK_ACCOUNT';
31
+ account: Account;
32
+ } | {
33
+ type: 'ENVS_LOADED';
34
+ environments: Environment[];
35
+ } | {
36
+ type: 'PICK_ENV';
37
+ environment: Environment;
38
+ } | {
39
+ type: 'KEYS_LOADED';
40
+ apiKeys: ApiKey[];
41
+ } | {
42
+ type: 'PICK_KEY';
43
+ key: ApiKey;
44
+ credentialValue: string;
45
+ } | {
46
+ type: 'DETECT_OK';
47
+ clients: ClientId[];
48
+ } | {
49
+ type: 'PICK_CLIENTS';
50
+ clients: ClientId[];
51
+ } | {
52
+ type: 'SKILLS_CHOICE';
53
+ install: boolean;
54
+ } | {
55
+ type: 'WRITE_OK';
56
+ result: WriteResult;
57
+ } | {
58
+ type: 'SKILLS_INSTALL_OK';
59
+ result: SkillsInstallResult;
60
+ } | {
61
+ type: 'ADVANCE';
62
+ } | {
63
+ type: 'BACK';
64
+ } | {
65
+ type: 'ERROR';
66
+ message: string;
67
+ } | {
68
+ type: 'RESET';
69
+ };
70
+ export declare const INITIAL_STATE: WizardState;
71
+ export declare function nextStep(step: StepId, selectedClients: ClientId[] | undefined): StepId;
72
+ export declare function wizardReducer(state: WizardState, action: Action): WizardState;
@@ -0,0 +1,107 @@
1
+ export const INITIAL_STATE = {
2
+ step: 'login',
3
+ history: [],
4
+ writes: [],
5
+ };
6
+ export function nextStep(step, selectedClients) {
7
+ switch (step) {
8
+ case 'login':
9
+ return 'account';
10
+ case 'account':
11
+ return 'environment';
12
+ case 'environment':
13
+ return 'apiKey';
14
+ case 'apiKey':
15
+ return 'clients';
16
+ case 'clients':
17
+ return (selectedClients?.length ?? 0) > 0 ? 'skills' : 'writing';
18
+ case 'skills':
19
+ return 'writing';
20
+ case 'writing':
21
+ return 'summary';
22
+ case 'summary':
23
+ return 'summary';
24
+ }
25
+ }
26
+ function advance(state) {
27
+ const target = nextStep(state.step, state.selectedClients);
28
+ if (target === state.step)
29
+ return state;
30
+ return {
31
+ ...state,
32
+ step: target,
33
+ history: [...state.history, state.step],
34
+ };
35
+ }
36
+ export function wizardReducer(state, action) {
37
+ switch (action.type) {
38
+ case 'LOGIN_OK':
39
+ return advance({ ...state, session: action.session });
40
+ case 'ACCOUNTS_LOADED':
41
+ return { ...state, accounts: action.accounts };
42
+ case 'PICK_ACCOUNT':
43
+ return advance({
44
+ ...state,
45
+ account: action.account,
46
+ environments: undefined,
47
+ environment: undefined,
48
+ apiKeys: undefined,
49
+ selectedKey: undefined,
50
+ });
51
+ case 'ENVS_LOADED':
52
+ return { ...state, environments: action.environments };
53
+ case 'PICK_ENV': {
54
+ // Reset everything that was scoped to the previous environment so the
55
+ // next step refetches and we never write a credential that mixes
56
+ // env A's key with env B's id.
57
+ const envChanged = state.environment?.id !== action.environment.id;
58
+ return advance({
59
+ ...state,
60
+ environment: action.environment,
61
+ apiKeys: envChanged ? undefined : state.apiKeys,
62
+ selectedKey: envChanged ? undefined : state.selectedKey,
63
+ credential: envChanged ? undefined : state.credential,
64
+ });
65
+ }
66
+ case 'KEYS_LOADED':
67
+ return { ...state, apiKeys: action.apiKeys };
68
+ case 'PICK_KEY':
69
+ return advance({
70
+ ...state,
71
+ selectedKey: action.key,
72
+ credential: {
73
+ value: action.credentialValue,
74
+ accountId: state.account?.id,
75
+ environmentId: state.environment?.id,
76
+ environmentSlug: state.environment?.slug,
77
+ },
78
+ });
79
+ case 'DETECT_OK':
80
+ return { ...state, detectedClients: action.clients };
81
+ case 'PICK_CLIENTS':
82
+ return advance({ ...state, selectedClients: action.clients });
83
+ case 'SKILLS_CHOICE':
84
+ return advance({ ...state, installSkills: action.install });
85
+ case 'WRITE_OK':
86
+ return { ...state, writes: [...state.writes, action.result] };
87
+ case 'SKILLS_INSTALL_OK':
88
+ return advance({ ...state, installedSkills: action.result });
89
+ case 'ADVANCE':
90
+ return advance(state);
91
+ case 'BACK': {
92
+ if (state.history.length === 0)
93
+ return state;
94
+ const prev = state.history[state.history.length - 1];
95
+ return {
96
+ ...state,
97
+ step: prev,
98
+ history: state.history.slice(0, -1),
99
+ error: undefined,
100
+ };
101
+ }
102
+ case 'ERROR':
103
+ return { ...state, error: action.message };
104
+ case 'RESET':
105
+ return INITIAL_STATE;
106
+ }
107
+ }
@@ -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, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Select, Spinner } from "@inkjs/ui";
3
+ import { Box } from "ink";
4
+ import { useEffect } from "react";
5
+ import { listAccounts } from "../../api/client.js";
6
+ import { SectionTitle } from "../components/SectionTitle.js";
7
+ import { useAsyncEffect } from "../hooks/useAsyncEffect.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 (_jsxs(Box, { flexDirection: "column", children: [_jsx(SectionTitle, { children: "Which account?" }), _jsx(Select, { options: state.accounts.map((a) => ({
35
+ label: a.displayName,
36
+ value: a.id,
37
+ })), onChange: (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,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Select, Spinner } from "@inkjs/ui";
3
+ import { Box, Text } from "ink";
4
+ import { useState } from "react";
5
+ import { createScopedApiKey, listApiKeys } from "../../api/client.js";
6
+ import { SectionTitle } from "../components/SectionTitle.js";
7
+ import { useAsyncEffect } from "../hooks/useAsyncEffect.js";
8
+ const CREATE_NEW = "__create_new__";
9
+ function timeAgo(iso) {
10
+ const ms = Date.now() - new Date(iso).getTime();
11
+ const days = Math.floor(ms / (1000 * 60 * 60 * 24));
12
+ if (days < 1)
13
+ return "today";
14
+ if (days < 7)
15
+ return `${days}d ago`;
16
+ if (days < 30)
17
+ return `${Math.floor(days / 7)}w ago`;
18
+ if (days < 365)
19
+ return `${Math.floor(days / 30)}mo ago`;
20
+ return `${Math.floor(days / 365)}y ago`;
21
+ }
22
+ export function ApiKeyStep({ state, dispatch }) {
23
+ const [minting, setMinting] = useState(false);
24
+ useAsyncEffect(async (signal) => {
25
+ if (state.apiKeys ||
26
+ !state.session ||
27
+ !state.environment ||
28
+ !state.account) {
29
+ return;
30
+ }
31
+ const keys = await listApiKeys(state.session.accessToken, state.account.id, state.environment.id);
32
+ if (signal.cancelled)
33
+ return;
34
+ dispatch({ type: "KEYS_LOADED", apiKeys: keys });
35
+ }, [
36
+ state.session?.accessToken,
37
+ state.account?.id,
38
+ state.environment?.id,
39
+ ], (err) => dispatch({
40
+ type: "ERROR",
41
+ message: err instanceof Error ? err.message : String(err),
42
+ }));
43
+ if (!state.apiKeys) {
44
+ return _jsx(Spinner, { label: `Loading API keys for ${state.environment?.name}…` });
45
+ }
46
+ if (minting) {
47
+ return _jsx(Spinner, { label: "Minting new key\u2026" });
48
+ }
49
+ const handleSelect = async (value) => {
50
+ if (value === CREATE_NEW) {
51
+ setMinting(true);
52
+ try {
53
+ const newKey = await createScopedApiKey(state.session.accessToken, state.account.id, state.environment.id, { name: "cyberdeck-mcp" });
54
+ const minted = {
55
+ id: `key_${Date.now().toString(36)}`,
56
+ name: "cyberdeck-mcp",
57
+ lastFour: newKey.slice(-4),
58
+ scopes: ["read", "write"],
59
+ createdAt: new Date().toISOString(),
60
+ value: newKey,
61
+ };
62
+ dispatch({
63
+ type: "PICK_KEY",
64
+ key: minted,
65
+ credentialValue: newKey,
66
+ });
67
+ }
68
+ catch (err) {
69
+ dispatch({
70
+ type: "ERROR",
71
+ message: err instanceof Error ? err.message : String(err),
72
+ });
73
+ setMinting(false);
74
+ }
75
+ return;
76
+ }
77
+ const key = state.apiKeys?.find((k) => k.id === value);
78
+ if (!key || !key.value)
79
+ return;
80
+ dispatch({ type: "PICK_KEY", key, credentialValue: key.value });
81
+ };
82
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SectionTitle, { children: "Pick an existing key, or create a new one" }), _jsx(Select, { options: [
83
+ ...state.apiKeys.map((k) => ({
84
+ label: `${k.name.padEnd(20)} sk_…${k.lastFour} ${k.scopes.join(", ").padEnd(14)} ${timeAgo(k.createdAt)}`,
85
+ value: k.id,
86
+ })),
87
+ { label: "+ Create new key", value: CREATE_NEW },
88
+ ], onChange: (v) => {
89
+ void handleSelect(v);
90
+ } }), state.apiKeys.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["No keys yet for ", state.environment?.name, ". Pick \"Create new\" to mint one."] }) }))] }));
91
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import type { ClientId, McpClientHandler } from "../../types.js";
3
+ import type { Action, WizardState } from "../state.js";
4
+ interface Props {
5
+ state: WizardState;
6
+ dispatch: React.Dispatch<Action>;
7
+ handlers: Record<ClientId, McpClientHandler>;
8
+ }
9
+ export declare function ClientsStep({ state, dispatch, handlers, }: Props): React.ReactElement;
10
+ export {};
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MultiSelect, Spinner } from "@inkjs/ui";
3
+ import { Box, Text } from "ink";
4
+ import { useState } from "react";
5
+ import { buildMcpEntry } from "../../mcp/writer.js";
6
+ import { SectionTitle } from "../components/SectionTitle.js";
7
+ import { useAsyncEffect } from "../hooks/useAsyncEffect.js";
8
+ const ALL_CLIENTS = [
9
+ { id: "claude-code", label: "Claude Code", configPath: "~/.claude.json" },
10
+ { id: "cursor", label: "Cursor", configPath: "~/.cursor/mcp.json" },
11
+ ];
12
+ const MCP_URL = "https://mcp.stigg.io";
13
+ function maskValue(v, keep = 4) {
14
+ if (v.length <= keep + 4)
15
+ return v;
16
+ return `${v.slice(0, 6)}…${v.slice(-keep)}`;
17
+ }
18
+ function maskedEntry(credential) {
19
+ if (!credential)
20
+ return null;
21
+ const entry = buildMcpEntry(credential, MCP_URL);
22
+ const maskedHeaders = Object.fromEntries(Object.entries(entry.headers).map(([k, v]) => {
23
+ if (k === "X-Environment")
24
+ return [k, v];
25
+ return [k, maskValue(v)];
26
+ }));
27
+ return { entry: { ...entry, headers: maskedHeaders } };
28
+ }
29
+ export function ClientsStep({ state, dispatch, handlers, }) {
30
+ useAsyncEffect(async (signal) => {
31
+ if (state.detectedClients)
32
+ return;
33
+ const detected = [];
34
+ for (const id of ALL_CLIENTS.map((c) => c.id)) {
35
+ if (await handlers[id].isInstalled()) {
36
+ detected.push(id);
37
+ }
38
+ }
39
+ if (signal.cancelled)
40
+ return;
41
+ dispatch({ type: "DETECT_OK", clients: detected });
42
+ }, [], (err) => dispatch({
43
+ type: "ERROR",
44
+ message: err instanceof Error ? err.message : String(err),
45
+ }));
46
+ const initial = state.selectedClients ??
47
+ (state.detectedClients && state.detectedClients.length > 0
48
+ ? state.detectedClients
49
+ : ["claude-code"]);
50
+ const [, setSelected] = useState(initial);
51
+ if (!state.detectedClients) {
52
+ return _jsx(Spinner, { label: "Detecting installed AI clients\u2026" });
53
+ }
54
+ const masked = maskedEntry(state.credential);
55
+ const previewLines = masked
56
+ ? JSON.stringify({ stigg: masked.entry }, null, 2).split("\n")
57
+ : [];
58
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SectionTitle, { children: "Which clients should we configure?" }), _jsxs(Box, { flexDirection: "row", gap: 4, children: [_jsx(Box, { flexDirection: "column", minWidth: 42, children: _jsx(MultiSelect, { options: ALL_CLIENTS.map((c) => {
59
+ const detected = state.detectedClients?.includes(c.id);
60
+ const suffix = detected ? " (detected)" : " (not installed)";
61
+ return {
62
+ label: `${c.label.padEnd(13)} ${c.configPath.padEnd(22)}${suffix}`,
63
+ value: c.id,
64
+ };
65
+ }), defaultValue: initial, onChange: (values) => setSelected(values), onSubmit: (values) => dispatch({
66
+ type: "PICK_CLIENTS",
67
+ clients: values,
68
+ }) }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { dimColor: true, children: "Preview \u00B7 mcpServers.stigg" }), previewLines.length === 0 ? (_jsx(Text, { dimColor: true, children: "(no credential yet)" })) : (previewLines.map((line, i) => (_jsx(Text, { color: "green", children: line.trim() === "" ? "" : `+ ${line}` }, i))))] })] })] }));
69
+ }
@@ -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 { Select } from "@inkjs/ui";
3
+ import { Box, Text } from "ink";
4
+ import { SectionTitle } from "../components/SectionTitle.js";
5
+ export function CredentialKindStep({ dispatch, }) {
6
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SectionTitle, { children: "Which credential should the MCP use?" }), _jsx(Select, { options: [
7
+ {
8
+ label: "User JWT · your user identity, broad access across envs",
9
+ value: "jwt",
10
+ },
11
+ {
12
+ label: "API key · single env, limited permissions (recommended)",
13
+ value: "scoped",
14
+ },
15
+ ], onChange: (v) => {
16
+ dispatch({ type: "PICK_CRED_KIND", kind: v });
17
+ } }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Scoped keys are safer (smaller blast radius if leaked)." }), _jsx(Text, { dimColor: true, children: "JWT is your full user identity and follows your role across envs." })] })] }));
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,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Select, Spinner } from "@inkjs/ui";
3
+ import { Box } from "ink";
4
+ import { listEnvironments } from "../../api/client.js";
5
+ import { SectionTitle } from "../components/SectionTitle.js";
6
+ import { useAsyncEffect } from "../hooks/useAsyncEffect.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 (_jsxs(Box, { flexDirection: "column", children: [_jsx(SectionTitle, { children: "Which environment?" }), _jsx(Select, { options: state.environments.map((e) => ({
30
+ label: e.name,
31
+ value: e.id,
32
+ })), onChange: (v) => {
33
+ const env = state.environments?.find((e) => e.id === v);
34
+ if (env)
35
+ dispatch({ type: "PICK_ENV", environment: env });
36
+ } })] }));
37
+ }