@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,173 @@
1
+ import pc from 'picocolors';
2
+ import { listAccounts, listApiKeys, listEnvironments } from '../api/client.js';
3
+ import { authenticate } from '../auth/oauth.js';
4
+ import { loadSession, saveSession } from '../auth/storage.js';
5
+ import { finalizeInitSetup } from './init-phase2.js';
6
+ import { createReporter, fmt } from './reporter.js';
7
+ const NEXT_STEP_HINT = [
8
+ 'npx @stigg/terminal init --headless \\',
9
+ ' --account-id <id> \\',
10
+ ' --environment-id <id> \\',
11
+ ' --api-key-id <id>',
12
+ ].join('\n');
13
+ export async function runInitPhase1(opts = {}) {
14
+ const reporter = createReporter({ json: opts.json });
15
+ const session = await loadOrAuthenticate(opts.forceLogin === true, !!opts.json, reporter, opts.callbackPort);
16
+ const accounts = await listAccounts(session.access_token);
17
+ // Build the public listing (no key values) AND keep raw envs/keys cached for
18
+ // the auto-pick path, so we don't re-fetch.
19
+ const rawEnvsByAccount = new Map();
20
+ const rawKeysByEnv = new Map();
21
+ const listing = {
22
+ user: session.email,
23
+ accounts: [],
24
+ nextStep: NEXT_STEP_HINT.replace(/\\\n\s+/g, ' '),
25
+ };
26
+ for (const acc of accounts) {
27
+ const envs = await listEnvironments(session.access_token, acc.id);
28
+ rawEnvsByAccount.set(acc.id, envs);
29
+ const accountEntry = {
30
+ id: acc.id,
31
+ name: acc.displayName,
32
+ environments: [],
33
+ };
34
+ for (const env of envs) {
35
+ const keys = await listApiKeys(session.access_token, acc.id, env.id);
36
+ rawKeysByEnv.set(env.id, keys);
37
+ accountEntry.environments.push({
38
+ id: env.id,
39
+ name: env.name,
40
+ slug: env.slug,
41
+ apiKeys: keys.map((k) => ({
42
+ id: k.id,
43
+ name: k.name,
44
+ lastFour: k.lastFour,
45
+ keyType: k.keyType,
46
+ createdAt: k.createdAt,
47
+ })),
48
+ });
49
+ }
50
+ listing.accounts.push(accountEntry);
51
+ }
52
+ // Auto-pick path: exactly 1 account, 1 env in that account, 1 key in that env.
53
+ const autoPick = pickSoleTuple(accounts, rawEnvsByAccount, rawKeysByEnv);
54
+ if (autoPick) {
55
+ if (!opts.json) {
56
+ reporter.human(fmt.ok('Auto-selected', `${autoPick.account.displayName} / ${autoPick.env.name} / ${autoPick.apiKey.name}`));
57
+ reporter.human('');
58
+ }
59
+ const { ok } = await finalizeInitSetup({
60
+ session,
61
+ account: { id: autoPick.account.id, displayName: autoPick.account.displayName },
62
+ env: autoPick.env,
63
+ envs: autoPick.envs,
64
+ apiKey: autoPick.apiKey,
65
+ }, {
66
+ client: opts.client,
67
+ all: opts.all,
68
+ installSkills: opts.installSkills !== false,
69
+ json: opts.json,
70
+ });
71
+ process.exit(ok ? 0 : 1);
72
+ }
73
+ if (accounts.length === 0) {
74
+ reporter.stderr(`${pc.red('✗')} No accounts found for this user.`);
75
+ if (!opts.json) {
76
+ reporter.human('');
77
+ }
78
+ reporter.emit(listing);
79
+ process.exit(1);
80
+ }
81
+ if (!opts.json) {
82
+ printHumanListing(reporter, listing);
83
+ }
84
+ reporter.emit(listing);
85
+ }
86
+ async function loadOrAuthenticate(forceLogin, json, reporter, callbackPort) {
87
+ if (!forceLogin) {
88
+ const existing = await loadSession();
89
+ if (existing && existing.expires_at && existing.expires_at > Date.now()) {
90
+ if (!json) {
91
+ reporter.human(fmt.heading('init · authenticate'));
92
+ reporter.human(fmt.ok(`Using saved session for ${existing.email}`));
93
+ reporter.human('');
94
+ }
95
+ return existing;
96
+ }
97
+ }
98
+ if (!json) {
99
+ reporter.human(fmt.heading('init · authenticate'));
100
+ reporter.human('Opening browser for sign-in…');
101
+ }
102
+ const session = await authenticate({
103
+ port: callbackPort,
104
+ onAuthUrlReady: (url) => {
105
+ if (!json) {
106
+ reporter.human(`If your browser does not open, visit:\n ${pc.cyan(url)}`);
107
+ }
108
+ },
109
+ });
110
+ const sessionPath = await saveSession(session);
111
+ if (!json) {
112
+ reporter.human(fmt.ok(`Signed in as ${session.email}`, sessionPath));
113
+ reporter.human('');
114
+ }
115
+ return session;
116
+ }
117
+ function pickSoleTuple(accounts, rawEnvsByAccount, rawKeysByEnv) {
118
+ if (accounts.length !== 1)
119
+ return null;
120
+ const account = accounts[0];
121
+ const envs = rawEnvsByAccount.get(account.id) ?? [];
122
+ if (envs.length !== 1)
123
+ return null;
124
+ const env = envs[0];
125
+ const keys = rawKeysByEnv.get(env.id) ?? [];
126
+ if (keys.length !== 1)
127
+ return null;
128
+ const apiKey = keys[0];
129
+ return { account, env, envs, apiKey };
130
+ }
131
+ function printHumanListing(reporter, listing) {
132
+ if (listing.accounts.length === 0) {
133
+ reporter.human(pc.yellow('No accounts found for this user.'));
134
+ return;
135
+ }
136
+ reporter.human('Accounts:');
137
+ for (const acc of listing.accounts) {
138
+ reporter.human(` ${pc.cyan(acc.id)} ${acc.name}`);
139
+ }
140
+ for (const acc of listing.accounts) {
141
+ reporter.human('');
142
+ reporter.human(`Environments (${pc.cyan(acc.id)}):`);
143
+ if (acc.environments.length === 0) {
144
+ reporter.human(` ${pc.dim('(none)')}`);
145
+ continue;
146
+ }
147
+ for (const env of acc.environments) {
148
+ const slug = env.slug ? ` ${pc.dim(`(slug: ${env.slug})`)}` : '';
149
+ reporter.human(` ${pc.cyan(env.id.padEnd(20))} ${env.name}${slug}`);
150
+ }
151
+ for (const env of acc.environments) {
152
+ reporter.human('');
153
+ reporter.human(`API keys (${pc.cyan(env.id)}):`);
154
+ if (env.apiKeys.length === 0) {
155
+ reporter.human(` ${pc.dim('(none)')}`);
156
+ continue;
157
+ }
158
+ for (const k of env.apiKeys) {
159
+ reporter.human(` ${pc.cyan(k.id.padEnd(20))} ` +
160
+ `${k.name.padEnd(24)} ` +
161
+ `sk_***...${k.lastFour} ` +
162
+ `${pc.dim(k.keyType.padEnd(8))} ` +
163
+ `${pc.dim(k.createdAt)}`);
164
+ }
165
+ }
166
+ }
167
+ reporter.human('');
168
+ reporter.human('Next step — pick one account / env / api-key and run:');
169
+ reporter.human('');
170
+ for (const line of NEXT_STEP_HINT.split('\n')) {
171
+ reporter.human(` ${line}`);
172
+ }
173
+ }
@@ -0,0 +1,36 @@
1
+ import type { AuthSession } from '../auth/oauth.js';
2
+ import type { Account, ApiKey, Environment } from '../types.js';
3
+ export interface PhaseTwoOptions {
4
+ accountId: string;
5
+ environmentId: string;
6
+ apiKeyId: string;
7
+ client?: string[];
8
+ all?: boolean;
9
+ installSkills: boolean;
10
+ json?: boolean;
11
+ }
12
+ export interface FinalizeContext {
13
+ session: AuthSession;
14
+ account: Account;
15
+ env: Environment;
16
+ envs: Environment[];
17
+ apiKey: ApiKey;
18
+ }
19
+ export interface FinalizeOptions {
20
+ client?: string[];
21
+ all?: boolean;
22
+ installSkills: boolean;
23
+ json?: boolean;
24
+ }
25
+ export declare function runInitPhase2(opts: PhaseTwoOptions): Promise<void>;
26
+ /**
27
+ * Complete the setup using fully-resolved (account, env, apiKey).
28
+ * Used by `runInitPhase2` after validating CLI flags, and by `runInitPhase1`
29
+ * after auto-picking when there's exactly one of each.
30
+ *
31
+ * Does NOT call `process.exit`. Returns the success flag for the caller to
32
+ * decide on exit code.
33
+ */
34
+ export declare function finalizeInitSetup(ctx: FinalizeContext, opts: FinalizeOptions): Promise<{
35
+ ok: boolean;
36
+ }>;
@@ -0,0 +1,150 @@
1
+ import pc from 'picocolors';
2
+ import { listAccounts, listApiKeys, listEnvironments } from '../api/client.js';
3
+ import { loadSession } from '../auth/storage.js';
4
+ import { getClientById } from '../mcp/clients/index.js';
5
+ import { saveSetup } from '../setup/storage.js';
6
+ import { detectHostAgent } from './host-agent.js';
7
+ import { nextStepPrompt, restartHint } from './next-step-prompt.js';
8
+ import { HeadlessOptionsError, resolveClientSelectionForInit, } from './options.js';
9
+ import { createReporter, fmt } from './reporter.js';
10
+ import { runHeadlessSetup } from './setup.js';
11
+ export async function runInitPhase2(opts) {
12
+ const session = await loadSession();
13
+ if (!session) {
14
+ throw new HeadlessOptionsError('No saved session. Run `terminal init --headless` first to authenticate.');
15
+ }
16
+ const accounts = await listAccounts(session.access_token);
17
+ const account = accounts.find((a) => a.id === opts.accountId);
18
+ if (!account) {
19
+ throw new HeadlessOptionsError(`account-id "${opts.accountId}" not found. Available: ${accounts.map((a) => a.id).join(', ') || '(none)'}`);
20
+ }
21
+ const envs = await listEnvironments(session.access_token, account.id);
22
+ const env = envs.find((e) => e.id === opts.environmentId);
23
+ if (!env) {
24
+ throw new HeadlessOptionsError(`environment-id "${opts.environmentId}" not found in account ${account.id}. Available: ${envs.map((e) => e.id).join(', ') || '(none)'}`);
25
+ }
26
+ const keys = await listApiKeys(session.access_token, account.id, env.id);
27
+ const apiKey = keys.find((k) => k.id === opts.apiKeyId);
28
+ if (!apiKey) {
29
+ throw new HeadlessOptionsError(`api-key-id "${opts.apiKeyId}" not found in env ${env.id}. Available: ${keys.map((k) => k.id).join(', ') || '(none)'}`);
30
+ }
31
+ const { ok } = await finalizeInitSetup({ session, account: { id: account.id, displayName: account.displayName }, env, envs, apiKey }, {
32
+ client: opts.client,
33
+ all: opts.all,
34
+ installSkills: opts.installSkills,
35
+ json: opts.json,
36
+ });
37
+ process.exit(ok ? 0 : 1);
38
+ }
39
+ /**
40
+ * Complete the setup using fully-resolved (account, env, apiKey).
41
+ * Used by `runInitPhase2` after validating CLI flags, and by `runInitPhase1`
42
+ * after auto-picking when there's exactly one of each.
43
+ *
44
+ * Does NOT call `process.exit`. Returns the success flag for the caller to
45
+ * decide on exit code.
46
+ */
47
+ export async function finalizeInitSetup(ctx, opts) {
48
+ const reporter = createReporter({ json: opts.json });
49
+ const { session, account, env, envs, apiKey } = ctx;
50
+ if (!apiKey.value) {
51
+ throw new HeadlessOptionsError(`api-key "${apiKey.id}" did not return a token value from the API`);
52
+ }
53
+ if (!opts.json) {
54
+ reporter.human(fmt.heading('init'));
55
+ reporter.human(`Account: ${pc.cyan(account.displayName)} ${pc.dim(account.id)}`);
56
+ reporter.human(`Environment: ${pc.cyan(env.name)} ${pc.dim(env.id)}`);
57
+ reporter.human(`API key: ${pc.cyan(apiKey.name)} ${pc.dim(`sk_***...${apiKey.lastFour}`)}`);
58
+ reporter.human('');
59
+ }
60
+ const hostAgent = detectHostAgent();
61
+ const clients = await resolveClientsWithHostFallback({ client: opts.client, all: opts.all }, hostAgent, reporter);
62
+ const result = await runHeadlessSetup({
63
+ apiKey: apiKey.value,
64
+ clients,
65
+ installSkills: opts.installSkills,
66
+ }, {
67
+ onClientResult: (write, { name }) => {
68
+ if (!opts.json)
69
+ reporter.human(fmt.ok(name, write.path));
70
+ },
71
+ onClientFail: (_id, name, reason) => {
72
+ reporter.stderr(fmt.fail(name, reason));
73
+ },
74
+ onSkillsResult: (skillsResult) => {
75
+ if (skillsResult.claudeCode) {
76
+ if (skillsResult.claudeCode.ok) {
77
+ if (!opts.json)
78
+ reporter.human(fmt.ok('Claude Code plugin', 'stigg@stigg-marketplace'));
79
+ }
80
+ else {
81
+ reporter.stderr(fmt.fail('Claude Code plugin', skillsResult.claudeCode.reason));
82
+ }
83
+ }
84
+ if (skillsResult.agentSkills) {
85
+ if (skillsResult.agentSkills.ok) {
86
+ if (!opts.json)
87
+ reporter.human(fmt.ok('Agent skills', 'stiggio/skills'));
88
+ }
89
+ else {
90
+ reporter.stderr(fmt.fail('Agent skills', skillsResult.agentSkills.reason));
91
+ }
92
+ }
93
+ },
94
+ });
95
+ let setupPath;
96
+ if (result.ok) {
97
+ const writtenClients = result.writes
98
+ .filter((w) => w.written && w.client !== undefined)
99
+ .map((w) => w.client);
100
+ const setupInfo = {
101
+ user: session.email,
102
+ account: account.displayName,
103
+ accountId: account.id,
104
+ environment: env.name,
105
+ environmentId: env.id,
106
+ credentialLabel: `${apiKey.name} · sk_***${apiKey.lastFour}`,
107
+ environments: envs,
108
+ clients: writtenClients,
109
+ completedAt: new Date().toISOString(),
110
+ };
111
+ setupPath = await saveSetup(setupInfo);
112
+ }
113
+ const restart = restartHint(hostAgent);
114
+ const prompt = nextStepPrompt(env.name);
115
+ if (!opts.json) {
116
+ if (result.ok) {
117
+ reporter.human('');
118
+ if (setupPath)
119
+ reporter.human(fmt.ok('Saved setup', setupPath));
120
+ reporter.human('Done.');
121
+ reporter.human('');
122
+ reporter.human(`⚠ ${restart}`);
123
+ }
124
+ reporter.human(prompt);
125
+ }
126
+ reporter.emit({
127
+ ok: result.ok,
128
+ writes: result.writes,
129
+ skipped: result.skipped,
130
+ skillsInstall: result.skillsInstall,
131
+ setupPath,
132
+ hostAgent: hostAgent ? { id: hostAgent.id, name: hostAgent.name } : null,
133
+ restartHint: restart,
134
+ nextStepPrompt: prompt,
135
+ });
136
+ return { ok: result.ok };
137
+ }
138
+ async function resolveClientsWithHostFallback(flags, hostAgent, reporter) {
139
+ const hasExplicit = (flags.client?.length ?? 0) > 0 || flags.all === true;
140
+ if (hasExplicit)
141
+ return resolveClientSelectionForInit(flags, null);
142
+ if (hostAgent) {
143
+ const client = getClientById(hostAgent.id);
144
+ if (client && (await client.isClientSupported())) {
145
+ return { kind: 'explicit', ids: [hostAgent.id] };
146
+ }
147
+ reporter.stderr(`⚠ Host agent ${hostAgent.name} detected but not configurable on this machine; falling back to --all detected.`);
148
+ }
149
+ return { kind: 'all' };
150
+ }
@@ -0,0 +1,7 @@
1
+ import type { HostAgent } from './host-agent.js';
2
+ /**
3
+ * One-line restart instruction tailored to the host agent if known, else generic.
4
+ * Returned without the leading ⚠ marker — the caller decides how to surface it.
5
+ */
6
+ export declare function restartHint(hostAgent: HostAgent | null): string;
7
+ export declare function nextStepPrompt(envName: string): string;
@@ -0,0 +1,25 @@
1
+ const GENERIC_RESTART_HINT = 'Restart your AI agent (Claude Code, Cursor, Codex, VS Code, Claude Desktop) to load Stigg MCP.';
2
+ /**
3
+ * One-line restart instruction tailored to the host agent if known, else generic.
4
+ * Returned without the leading ⚠ marker — the caller decides how to surface it.
5
+ */
6
+ export function restartHint(hostAgent) {
7
+ return hostAgent ? hostAgent.restartHint : GENERIC_RESTART_HINT;
8
+ }
9
+ export function nextStepPrompt(envName) {
10
+ return `
11
+ Hi User/Agent! Welcome to Stigg.
12
+ Stigg is the runtime for AI credits, usage governance, and real-time access decisions.
13
+ Connected to: ${envName}
14
+
15
+ Choose a starting point:
16
+ 1 Meter Usage Track usage and understand consumption
17
+ 2 Add Credit Create wallets, balances, and burn-down logic
18
+ 3 Pricing & Packaging Model products, plans, and usage pricing
19
+ 4 Usage Governance Create budgets, limits, and AI spend controls
20
+ 5 Integrate Stigg Generate implementation code
21
+ 6 Help me choose
22
+
23
+ (you can do the others later · type back anytime)
24
+ `;
25
+ }
@@ -0,0 +1,30 @@
1
+ import type { ClientId } from '../types.js';
2
+ import type { HostAgent } from './host-agent.js';
3
+ export type ClientSelection = {
4
+ kind: 'all';
5
+ } | {
6
+ kind: 'explicit';
7
+ ids: ClientId[];
8
+ };
9
+ export declare class HeadlessOptionsError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export interface ClientFlagOptions {
13
+ client?: string[];
14
+ all?: boolean;
15
+ }
16
+ export declare function validClientIds(): ClientId[];
17
+ export declare function parseClientSelection(opts: ClientFlagOptions, { required }?: {
18
+ required?: boolean;
19
+ }): ClientSelection;
20
+ export declare function requireApiKey(flagValue?: string): string;
21
+ /**
22
+ * Resolve the client selection for `init --headless` (phase 2), applying the
23
+ * host-agent fallback when neither `--client` nor `--all` is explicit.
24
+ *
25
+ * Priority: explicit flag (>--client or --all) > host agent > all detected.
26
+ *
27
+ * Used only by `init` — standalone `mcp add` / `skills add` keep the strict
28
+ * "explicit required" rule via `parseClientSelection({ required: true })`.
29
+ */
30
+ export declare function resolveClientSelectionForInit(opts: ClientFlagOptions, hostAgent: HostAgent | null): ClientSelection;
@@ -0,0 +1,77 @@
1
+ import { getAllClients } from '../mcp/clients/index.js';
2
+ export class HeadlessOptionsError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'HeadlessOptionsError';
6
+ }
7
+ }
8
+ export function validClientIds() {
9
+ return getAllClients().map((c) => c.id);
10
+ }
11
+ export function parseClientSelection(opts, { required = false } = {}) {
12
+ const hasClient = (opts.client?.length ?? 0) > 0;
13
+ const hasAll = opts.all === true;
14
+ if (hasClient && hasAll) {
15
+ throw new HeadlessOptionsError('--client and --all are mutually exclusive');
16
+ }
17
+ if (!hasClient && !hasAll) {
18
+ if (required) {
19
+ throw new HeadlessOptionsError(`one of --client or --all is required (valid clients: ${validClientIds().join(', ')})`);
20
+ }
21
+ return { kind: 'all' };
22
+ }
23
+ if (hasAll)
24
+ return { kind: 'all' };
25
+ const valid = validClientIds();
26
+ const ids = [];
27
+ for (const raw of opts.client ?? []) {
28
+ const id = raw.trim();
29
+ if (!id)
30
+ continue;
31
+ if (!valid.includes(id)) {
32
+ throw new HeadlessOptionsError(`unknown client: ${id} (valid: ${valid.join(', ')})`);
33
+ }
34
+ ids.push(id);
35
+ }
36
+ if (ids.length === 0) {
37
+ throw new HeadlessOptionsError('--client given but no valid IDs');
38
+ }
39
+ return { kind: 'explicit', ids };
40
+ }
41
+ export function requireApiKey(flagValue) {
42
+ const fromFlag = sanitize(flagValue);
43
+ if (fromFlag)
44
+ return fromFlag;
45
+ const fromEnv = sanitize(process.env.STIGG_API_KEY);
46
+ if (fromEnv)
47
+ return fromEnv;
48
+ throw new HeadlessOptionsError('--api-key is required (or set STIGG_API_KEY env var)');
49
+ }
50
+ function sanitize(v) {
51
+ if (!v)
52
+ return null;
53
+ const trimmed = v.trim();
54
+ if (!trimmed || trimmed.startsWith('<'))
55
+ return null;
56
+ return trimmed;
57
+ }
58
+ /**
59
+ * Resolve the client selection for `init --headless` (phase 2), applying the
60
+ * host-agent fallback when neither `--client` nor `--all` is explicit.
61
+ *
62
+ * Priority: explicit flag (>--client or --all) > host agent > all detected.
63
+ *
64
+ * Used only by `init` — standalone `mcp add` / `skills add` keep the strict
65
+ * "explicit required" rule via `parseClientSelection({ required: true })`.
66
+ */
67
+ export function resolveClientSelectionForInit(opts, hostAgent) {
68
+ const hasClient = (opts.client?.length ?? 0) > 0;
69
+ const hasAll = opts.all === true;
70
+ if (hasClient || hasAll) {
71
+ return parseClientSelection(opts);
72
+ }
73
+ if (hostAgent) {
74
+ return { kind: 'explicit', ids: [hostAgent.id] };
75
+ }
76
+ return { kind: 'all' };
77
+ }
@@ -0,0 +1,16 @@
1
+ export interface Reporter {
2
+ /** Write a progress line to stdout. JSON reporter no-ops. */
3
+ human(line: string): void;
4
+ /** Write a failure line to stderr. Both reporters write this. */
5
+ stderr(line: string): void;
6
+ /** Emit the final structured payload. Human reporter no-ops; JSON reporter prints JSON. */
7
+ emit(payload: Record<string, unknown>): void;
8
+ }
9
+ export declare const fmt: {
10
+ heading(text: string): string;
11
+ ok(text: string, detail?: string): string;
12
+ fail(text: string, reason?: string): string;
13
+ };
14
+ export declare function createReporter(opts: {
15
+ json?: boolean;
16
+ }): Reporter;
@@ -0,0 +1,41 @@
1
+ import pc from 'picocolors';
2
+ export const fmt = {
3
+ heading(text) {
4
+ return `${pc.dim('stigg · ')}${text}`;
5
+ },
6
+ ok(text, detail) {
7
+ if (!detail)
8
+ return `${pc.green(' ✓')} ${text}`;
9
+ return `${pc.green(' ✓')} ${text.padEnd(26)} ${pc.dim(detail)}`;
10
+ },
11
+ fail(text, reason) {
12
+ if (!reason)
13
+ return `${pc.red(' ✗')} ${text}`;
14
+ return `${pc.red(' ✗')} ${text}: ${reason}`;
15
+ },
16
+ };
17
+ class HumanReporter {
18
+ human(line) {
19
+ process.stdout.write(line + '\n');
20
+ }
21
+ stderr(line) {
22
+ process.stderr.write(line + '\n');
23
+ }
24
+ emit() {
25
+ /* no-op */
26
+ }
27
+ }
28
+ class JsonReporter {
29
+ human() {
30
+ /* no-op */
31
+ }
32
+ stderr(line) {
33
+ process.stderr.write(line + '\n');
34
+ }
35
+ emit(payload) {
36
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
37
+ }
38
+ }
39
+ export function createReporter(opts) {
40
+ return opts.json ? new JsonReporter() : new HumanReporter();
41
+ }
@@ -0,0 +1,29 @@
1
+ import { type InstallResult as SkillsInstallResult } from '../skills/install.js';
2
+ import type { ClientId, WriteResult } from '../types.js';
3
+ import { type ClientSelection } from './options.js';
4
+ export interface SkippedClient {
5
+ id: ClientId;
6
+ reason: 'unsupported' | 'unknown';
7
+ }
8
+ export interface HeadlessSetupOptions {
9
+ apiKey: string;
10
+ clients: ClientSelection;
11
+ installSkills: boolean;
12
+ /** Skip the MCP loop entirely. Used by `skills add`. */
13
+ skipMcp?: boolean;
14
+ }
15
+ export interface HeadlessResult {
16
+ writes: WriteResult[];
17
+ skipped: SkippedClient[];
18
+ skillsInstall?: SkillsInstallResult;
19
+ ok: boolean;
20
+ }
21
+ export interface ProgressCallbacks {
22
+ onClientResult?: (write: WriteResult, client: {
23
+ id: ClientId;
24
+ name: string;
25
+ }) => void;
26
+ onClientFail?: (id: ClientId, name: string, reason: string) => void;
27
+ onSkillsResult?: (result: SkillsInstallResult) => void;
28
+ }
29
+ export declare function runHeadlessSetup(opts: HeadlessSetupOptions, progress?: ProgressCallbacks): Promise<HeadlessResult>;
@@ -0,0 +1,80 @@
1
+ import { getAllClients, getClientById, getSupportedClients, } from '../mcp/clients/index.js';
2
+ import { installStiggSkills, } from '../skills/install.js';
3
+ import { HeadlessOptionsError } from './options.js';
4
+ async function resolveClients(selection, progress, result) {
5
+ if (selection.kind === 'all') {
6
+ const supported = await getSupportedClients();
7
+ if (supported.length === 0) {
8
+ throw new HeadlessOptionsError('no supported AI clients detected on this machine');
9
+ }
10
+ return supported.map((c) => ({ id: c.id, name: c.name, client: c }));
11
+ }
12
+ const resolved = [];
13
+ for (const id of selection.ids) {
14
+ const c = getClientById(id);
15
+ if (!c) {
16
+ // Defensive: parseClientSelection should have rejected unknowns.
17
+ result.skipped.push({ id, reason: 'unknown' });
18
+ result.ok = false;
19
+ continue;
20
+ }
21
+ if (!(await c.isClientSupported())) {
22
+ // Explicit selection of an unsupported client is a hard failure.
23
+ const reason = 'client not detected on this machine';
24
+ progress.onClientFail?.(id, c.name, reason);
25
+ result.writes.push({ client: id, path: c.installTarget(), written: false });
26
+ result.ok = false;
27
+ continue;
28
+ }
29
+ resolved.push({ id: c.id, name: c.name, client: c });
30
+ }
31
+ return resolved;
32
+ }
33
+ async function runMcpLoop(clients, credential, progress, result) {
34
+ const writtenIds = [];
35
+ for (const { id, name, client } of clients) {
36
+ const installResult = await client.addServer(credential);
37
+ const write = client.toWriteResult(installResult);
38
+ result.writes.push(write);
39
+ if (installResult.ok) {
40
+ writtenIds.push(id);
41
+ progress.onClientResult?.(write, { id, name });
42
+ }
43
+ else {
44
+ progress.onClientFail?.(id, name, installResult.reason ?? 'install failed');
45
+ result.ok = false;
46
+ }
47
+ }
48
+ return writtenIds;
49
+ }
50
+ async function resolveSkillsTargets(selection) {
51
+ if (selection.kind === 'all') {
52
+ const supported = await getSupportedClients();
53
+ if (supported.length === 0) {
54
+ throw new HeadlessOptionsError('no supported AI clients detected on this machine');
55
+ }
56
+ return supported.map((c) => c.id);
57
+ }
58
+ const valid = new Set(getAllClients().map((c) => c.id));
59
+ return selection.ids.filter((id) => valid.has(id));
60
+ }
61
+ export async function runHeadlessSetup(opts, progress = {}) {
62
+ const result = { writes: [], skipped: [], ok: true };
63
+ let writtenIds = [];
64
+ if (!opts.skipMcp) {
65
+ const resolved = await resolveClients(opts.clients, progress, result);
66
+ const credential = { value: opts.apiKey };
67
+ writtenIds = await runMcpLoop(resolved, credential, progress, result);
68
+ }
69
+ if (opts.installSkills) {
70
+ const targets = opts.skipMcp ? await resolveSkillsTargets(opts.clients) : writtenIds;
71
+ if (targets.length > 0) {
72
+ const skillsResult = await installStiggSkills(targets);
73
+ result.skillsInstall = skillsResult;
74
+ progress.onSkillsResult?.(skillsResult);
75
+ if (!skillsResult.ok)
76
+ result.ok = false;
77
+ }
78
+ }
79
+ return result;
80
+ }