@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,27 @@
1
+ import { ClaudeCodeMcpClient } from './claude-code.js';
2
+ import { ClaudeDesktopMcpClient } from './claude-desktop.js';
3
+ import { CodexMcpClient } from './codex.js';
4
+ import { CursorMcpClient } from './cursor.js';
5
+ import { VsCodeMcpClient } from './vscode.js';
6
+ export { McpClient, DefaultJsonMcpClient } from './base.js';
7
+ const ALL_CLIENTS = Object.freeze([
8
+ new ClaudeCodeMcpClient(),
9
+ new CursorMcpClient(),
10
+ new ClaudeDesktopMcpClient(),
11
+ new VsCodeMcpClient(),
12
+ new CodexMcpClient(),
13
+ ]);
14
+ export function getAllClients() {
15
+ return ALL_CLIENTS;
16
+ }
17
+ export function getClientById(id) {
18
+ return ALL_CLIENTS.find((c) => c.id === id);
19
+ }
20
+ export async function getSupportedClients() {
21
+ const supported = [];
22
+ for (const c of ALL_CLIENTS) {
23
+ if (await c.isClientSupported())
24
+ supported.push(c);
25
+ }
26
+ return supported;
27
+ }
@@ -0,0 +1,11 @@
1
+ import type { Credential } from '../../types.js';
2
+ /**
3
+ * Build a stdio-via-mcp-remote MCP entry for clients that don't speak HTTP
4
+ * natively (Claude Desktop, Codex). The API key is passed through an env
5
+ * var so it doesn't appear in the command's `args` array.
6
+ */
7
+ export declare function buildMcpRemoteEntry(credential: Credential): {
8
+ command: string;
9
+ args: string[];
10
+ env: Record<string, string>;
11
+ };
@@ -0,0 +1,13 @@
1
+ import { STIGG_MCP_URL } from './base.js';
2
+ /**
3
+ * Build a stdio-via-mcp-remote MCP entry for clients that don't speak HTTP
4
+ * natively (Claude Desktop, Codex). The API key is passed through an env
5
+ * var so it doesn't appear in the command's `args` array.
6
+ */
7
+ export function buildMcpRemoteEntry(credential) {
8
+ return {
9
+ command: 'npx',
10
+ args: ['-y', 'mcp-remote@latest', STIGG_MCP_URL, '--header', 'X-API-KEY:${STIGG_API_KEY}'],
11
+ env: { STIGG_API_KEY: credential.value },
12
+ };
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { ClientId, Credential } from '../../types.js';
2
+ import { DefaultJsonMcpClient, type InstallResult, type McpServerConfig } from './base.js';
3
+ export declare class VsCodeMcpClient extends DefaultJsonMcpClient {
4
+ readonly id: ClientId;
5
+ readonly name = "Visual Studio Code";
6
+ getConfigPath(): string;
7
+ getServerPropertyName(): string;
8
+ isClientSupported(): Promise<boolean>;
9
+ getServerConfig(credential: Credential): McpServerConfig;
10
+ /**
11
+ * Prefer `code --add-mcp '<json>'` when the `code` CLI is on PATH —
12
+ * that's the canonical entry point. Fall back to a direct file edit
13
+ * otherwise (typical when the user hasn't run "Install 'code' command
14
+ * in PATH" from the command palette).
15
+ */
16
+ addServer(credential: Credential): Promise<InstallResult>;
17
+ }
@@ -0,0 +1,84 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { access, constants } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { optionalEnv } from '../../auth/config.js';
6
+ import { DefaultJsonMcpClient, errorMessage, prettifyPath, STIGG_MCP_URL, STIGG_SERVER_NAME, } from './base.js';
7
+ export class VsCodeMcpClient extends DefaultJsonMcpClient {
8
+ id = 'vscode';
9
+ name = 'Visual Studio Code';
10
+ getConfigPath() {
11
+ const override = optionalEnv('STIGG_VSCODE_CONFIG');
12
+ if (override)
13
+ return override;
14
+ if (process.platform === 'darwin') {
15
+ return join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
16
+ }
17
+ if (process.platform === 'win32') {
18
+ return join(process.env.APPDATA ?? '', 'Code', 'User', 'mcp.json');
19
+ }
20
+ return join(homedir(), '.config', 'Code', 'User', 'mcp.json');
21
+ }
22
+ getServerPropertyName() {
23
+ return 'servers';
24
+ }
25
+ async isClientSupported() {
26
+ if (!['darwin', 'win32', 'linux'].includes(process.platform))
27
+ return false;
28
+ if (await pathExists(dirname(this.getConfigPath())))
29
+ return true;
30
+ return commandExists('code');
31
+ }
32
+ getServerConfig(credential) {
33
+ return {
34
+ type: 'http',
35
+ url: STIGG_MCP_URL,
36
+ headers: { 'X-API-KEY': credential.value },
37
+ };
38
+ }
39
+ /**
40
+ * Prefer `code --add-mcp '<json>'` when the `code` CLI is on PATH —
41
+ * that's the canonical entry point. Fall back to a direct file edit
42
+ * otherwise (typical when the user hasn't run "Install 'code' command
43
+ * in PATH" from the command palette).
44
+ */
45
+ async addServer(credential) {
46
+ if (await commandExists('code')) {
47
+ const payload = JSON.stringify({
48
+ name: STIGG_SERVER_NAME,
49
+ ...this.getServerConfig(credential),
50
+ });
51
+ try {
52
+ execFileSync('code', ['--add-mcp', payload], { stdio: 'pipe' });
53
+ return { ok: true, target: 'code --add-mcp' };
54
+ }
55
+ catch (e) {
56
+ // Fall through to the file-write fallback.
57
+ const reason = errorMessage(e);
58
+ const fileResult = await super.addServer(credential);
59
+ if (fileResult.ok)
60
+ return fileResult;
61
+ return {
62
+ ok: false,
63
+ target: prettifyPath(this.getConfigPath()),
64
+ reason: `code --add-mcp failed (${reason}); file fallback also failed: ${fileResult.reason ?? 'unknown'}`,
65
+ };
66
+ }
67
+ }
68
+ return super.addServer(credential);
69
+ }
70
+ }
71
+ function pathExists(path) {
72
+ return access(path, constants.F_OK).then(() => true, () => false);
73
+ }
74
+ function commandExists(cmd) {
75
+ return new Promise((resolve) => {
76
+ try {
77
+ execFileSync('sh', ['-c', `command -v ${cmd}`], { stdio: 'pipe' });
78
+ resolve(true);
79
+ }
80
+ catch {
81
+ resolve(false);
82
+ }
83
+ });
84
+ }
@@ -0,0 +1,4 @@
1
+ import type { ClientId, McpClientHandler } from "../types.js";
2
+ export declare function claudeCodeHandler(): McpClientHandler;
3
+ export declare function cursorHandler(): McpClientHandler;
4
+ export declare function detectClients(handlers: Record<ClientId, McpClientHandler>): Promise<ClientId[]>;
@@ -0,0 +1,50 @@
1
+ import { access, constants } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { optionalEnv } from "../auth/config.js";
5
+ import { buildMcpEntry, hasMcpEntry, writeMcpEntry } from "./writer.js";
6
+ const DEFAULT_URL = "https://mcp.stigg.io";
7
+ const SERVER_NAME = "stigg";
8
+ function mcpUrl() {
9
+ return optionalEnv("STIGG_MCP_URL") ?? DEFAULT_URL;
10
+ }
11
+ function claudeCodeConfigPath() {
12
+ return (optionalEnv("STIGG_CLAUDE_CONFIG") ?? join(homedir(), ".claude.json"));
13
+ }
14
+ function cursorConfigPath() {
15
+ return (optionalEnv("STIGG_CURSOR_CONFIG") ??
16
+ join(homedir(), ".cursor", "mcp.json"));
17
+ }
18
+ function pathExists(path) {
19
+ return access(path, constants.F_OK).then(() => true, () => false);
20
+ }
21
+ export function claudeCodeHandler() {
22
+ const path = claudeCodeConfigPath();
23
+ return {
24
+ id: "claude-code",
25
+ label: "~/.claude.json",
26
+ configPath: path,
27
+ hasExistingEntry: () => hasMcpEntry(path, SERVER_NAME),
28
+ write: (credential) => writeMcpEntry(path, SERVER_NAME, buildMcpEntry(credential, mcpUrl())),
29
+ isInstalled: () => pathExists(path).then((ok) => ok ? true : pathExists(join(homedir(), ".claude"))),
30
+ };
31
+ }
32
+ export function cursorHandler() {
33
+ const path = cursorConfigPath();
34
+ return {
35
+ id: "cursor",
36
+ label: "~/.cursor/mcp.json",
37
+ configPath: path,
38
+ hasExistingEntry: () => hasMcpEntry(path, SERVER_NAME),
39
+ write: (credential) => writeMcpEntry(path, SERVER_NAME, buildMcpEntry(credential, mcpUrl())),
40
+ isInstalled: () => pathExists(join(homedir(), ".cursor")),
41
+ };
42
+ }
43
+ export async function detectClients(handlers) {
44
+ const ids = [];
45
+ for (const handler of Object.values(handlers)) {
46
+ if (await handler.isInstalled())
47
+ ids.push(handler.id);
48
+ }
49
+ return ids;
50
+ }
@@ -0,0 +1,9 @@
1
+ import * as jsonc from 'jsonc-parser';
2
+ export declare class ConfigParseError extends Error {
3
+ path: string;
4
+ detail: jsonc.ParseError[] | string;
5
+ constructor(path: string, detail: jsonc.ParseError[] | string);
6
+ }
7
+ export declare function parseStrict(text: string, path: string): Record<string, unknown>;
8
+ export declare function setJsonPath(text: string, path: jsonc.JSONPath, value: unknown): string;
9
+ export declare function readJsonPath(value: unknown, path: readonly (string | number)[]): unknown;
@@ -0,0 +1,51 @@
1
+ import * as jsonc from 'jsonc-parser';
2
+ const FORMATTING_OPTIONS = {
3
+ tabSize: 2,
4
+ insertSpaces: true,
5
+ eol: '\n',
6
+ };
7
+ const PARSE_OPTIONS = {
8
+ allowTrailingComma: true,
9
+ allowEmptyContent: true,
10
+ };
11
+ export class ConfigParseError extends Error {
12
+ path;
13
+ detail;
14
+ constructor(path, detail) {
15
+ const summary = typeof detail === 'string'
16
+ ? detail
17
+ : `${detail.length} parse error(s) starting at offset ${detail[0]?.offset ?? 0}`;
18
+ super(`${path} is not valid JSON: ${summary}`);
19
+ this.path = path;
20
+ this.detail = detail;
21
+ this.name = 'ConfigParseError';
22
+ }
23
+ }
24
+ export function parseStrict(text, path) {
25
+ if (!text.trim())
26
+ return {};
27
+ const errors = [];
28
+ const value = jsonc.parse(text, errors, PARSE_OPTIONS);
29
+ if (errors.length > 0)
30
+ throw new ConfigParseError(path, errors);
31
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
32
+ throw new ConfigParseError(path, 'root value is not an object');
33
+ }
34
+ return value;
35
+ }
36
+ export function setJsonPath(text, path, value) {
37
+ const seed = text.trim() ? text : '{}';
38
+ const edits = jsonc.modify(seed, path, value, {
39
+ formattingOptions: FORMATTING_OPTIONS,
40
+ });
41
+ return jsonc.applyEdits(seed, edits);
42
+ }
43
+ export function readJsonPath(value, path) {
44
+ let cur = value;
45
+ for (const seg of path) {
46
+ if (cur == null || typeof cur !== 'object')
47
+ return undefined;
48
+ cur = cur[seg];
49
+ }
50
+ return cur;
51
+ }
@@ -0,0 +1,6 @@
1
+ import type { WriteResult } from '../types.js';
2
+ export type { WriteResult };
3
+ export type McpServerConfig = Record<string, unknown>;
4
+ export declare function hasMcpEntry(path: string, propertyName: string, serverName: string): Promise<boolean>;
5
+ export declare function writeMcpEntry(path: string, propertyName: string, serverName: string, entry: McpServerConfig): Promise<WriteResult>;
6
+ export declare function removeMcpEntry(path: string, propertyName: string, serverName: string): Promise<WriteResult>;
@@ -0,0 +1,65 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
3
+ import { parseStrict, readJsonPath, setJsonPath } from './config-merge.js';
4
+ export async function hasMcpEntry(path, propertyName, serverName) {
5
+ let text;
6
+ try {
7
+ text = await readFile(path, 'utf8');
8
+ }
9
+ catch (e) {
10
+ if (e.code === 'ENOENT')
11
+ return false;
12
+ throw e;
13
+ }
14
+ const parsed = parseStrict(text, path);
15
+ return readJsonPath(parsed, [propertyName, serverName]) !== undefined;
16
+ }
17
+ export async function writeMcpEntry(path, propertyName, serverName, entry) {
18
+ await mkdir(dirname(path), { recursive: true });
19
+ let text = '';
20
+ let fileExisted = false;
21
+ try {
22
+ text = await readFile(path, 'utf8');
23
+ fileExisted = true;
24
+ }
25
+ catch (e) {
26
+ if (e.code !== 'ENOENT')
27
+ throw e;
28
+ }
29
+ if (fileExisted && text.trim()) {
30
+ parseStrict(text, path);
31
+ }
32
+ const existed = fileExisted
33
+ ? readJsonPath(parseStrict(text, path), [propertyName, serverName]) !== undefined
34
+ : false;
35
+ let backedUpTo;
36
+ if (existed) {
37
+ backedUpTo = `${path}.bak.${Date.now()}`;
38
+ await writeFile(backedUpTo, text, 'utf8');
39
+ }
40
+ const updated = setJsonPath(text, [propertyName, serverName], entry);
41
+ const final = updated.endsWith('\n') ? updated : `${updated}\n`;
42
+ await writeFile(path, final, 'utf8');
43
+ return { path, written: true, backedUpTo };
44
+ }
45
+ export async function removeMcpEntry(path, propertyName, serverName) {
46
+ let text;
47
+ try {
48
+ text = await readFile(path, 'utf8');
49
+ }
50
+ catch (e) {
51
+ if (e.code === 'ENOENT') {
52
+ return { path, written: false };
53
+ }
54
+ throw e;
55
+ }
56
+ if (readJsonPath(parseStrict(text, path), [propertyName, serverName]) === undefined) {
57
+ return { path, written: false };
58
+ }
59
+ const backedUpTo = `${path}.bak.${Date.now()}`;
60
+ await writeFile(backedUpTo, text, 'utf8');
61
+ const updated = setJsonPath(text, [propertyName, serverName], undefined);
62
+ const final = updated.endsWith('\n') ? updated : `${updated}\n`;
63
+ await writeFile(path, final, 'utf8');
64
+ return { path, written: true, backedUpTo };
65
+ }
@@ -0,0 +1,21 @@
1
+ import type { ClientId, Environment } from '../types.js';
2
+ export interface SetupInfo {
3
+ user: string;
4
+ account: string;
5
+ accountId: string;
6
+ environment: string;
7
+ environmentId: string;
8
+ credentialLabel: string;
9
+ /** Snapshot of all environments visible at init time. Used by the env
10
+ * switcher so it can render the picker without an extra API round-trip. */
11
+ environments?: Environment[];
12
+ /** Clients we wrote an MCP entry to, so the env switcher knows which
13
+ * configs to rewrite without re-detecting. */
14
+ clients?: ClientId[];
15
+ completedAt: string;
16
+ }
17
+ export declare function setupConfigDir(): string;
18
+ export declare function setupPath(): string;
19
+ export declare function saveSetup(info: SetupInfo): Promise<string>;
20
+ export declare function loadSetup(): Promise<SetupInfo | null>;
21
+ export declare function clearSetup(): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export function setupConfigDir() {
5
+ const xdg = process.env.XDG_CONFIG_HOME;
6
+ if (xdg)
7
+ return join(xdg, 'stigg');
8
+ return join(homedir(), '.config', 'stigg');
9
+ }
10
+ export function setupPath() {
11
+ return join(setupConfigDir(), 'setup.json');
12
+ }
13
+ export async function saveSetup(info) {
14
+ const dir = setupConfigDir();
15
+ const path = setupPath();
16
+ await mkdir(dir, { recursive: true });
17
+ await writeFile(path, JSON.stringify(info, null, 2), 'utf8');
18
+ await chmod(path, 0o600);
19
+ return path;
20
+ }
21
+ export async function loadSetup() {
22
+ try {
23
+ const raw = await readFile(setupPath(), 'utf8');
24
+ return JSON.parse(raw);
25
+ }
26
+ catch (e) {
27
+ if (e.code === 'ENOENT')
28
+ return null;
29
+ throw e;
30
+ }
31
+ }
32
+ export async function clearSetup() {
33
+ await rm(setupPath(), { force: true });
34
+ }
@@ -0,0 +1,19 @@
1
+ import type { ClientId } from '../types.js';
2
+ export interface SubResult {
3
+ ok: boolean;
4
+ reason?: string;
5
+ }
6
+ export interface InstallResult {
7
+ /** Result of `claude plugin install stigg@stigg-marketplace` (Claude Code path). */
8
+ claudeCode?: SubResult;
9
+ /** Result of `npx skills add stiggio/skills --all` (Agent Skills format, multi-client). */
10
+ agentSkills?: SubResult;
11
+ /** Convenience boolean — true iff every attempted sub-step succeeded. */
12
+ ok: boolean;
13
+ }
14
+ /**
15
+ * Install Stigg skills using the right canonical path for each client family.
16
+ * Claude Code → `claude plugin marketplace add` + `claude plugin install`.
17
+ * Anything else that consumes the Agent Skills format → `npx skills add`.
18
+ */
19
+ export declare function installStiggSkills(selectedClients: ClientId[]): Promise<InstallResult>;
@@ -0,0 +1,64 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { findClaudeBinary } from '../mcp/clients/claude-code.js';
3
+ const MARKETPLACE = 'stiggio/skills';
4
+ const PLUGIN_REF = 'stigg@stigg-marketplace';
5
+ /**
6
+ * Install Stigg skills using the right canonical path for each client family.
7
+ * Claude Code → `claude plugin marketplace add` + `claude plugin install`.
8
+ * Anything else that consumes the Agent Skills format → `npx skills add`.
9
+ */
10
+ export async function installStiggSkills(selectedClients) {
11
+ const result = { ok: true };
12
+ if (selectedClients.includes('claude-code')) {
13
+ result.claudeCode = await installClaudeCodePlugin();
14
+ if (!result.claudeCode.ok)
15
+ result.ok = false;
16
+ }
17
+ const otherClients = selectedClients.filter((c) => c !== 'claude-code');
18
+ if (otherClients.length > 0) {
19
+ result.agentSkills = await installAgentSkills();
20
+ if (!result.agentSkills.ok)
21
+ result.ok = false;
22
+ }
23
+ return result;
24
+ }
25
+ async function installClaudeCodePlugin() {
26
+ const bin = findClaudeBinary();
27
+ if (!bin)
28
+ return { ok: false, reason: '`claude` CLI not found on PATH' };
29
+ // Step 1: register the marketplace. Idempotent — re-running is a no-op.
30
+ const mpResult = await runCommand(bin, ['plugin', 'marketplace', 'add', MARKETPLACE]);
31
+ if (!mpResult.ok) {
32
+ return { ok: false, reason: `plugin marketplace add failed: ${mpResult.reason ?? 'unknown'}` };
33
+ }
34
+ // Step 2: install (or update) the plugin.
35
+ const installResult = await runCommand(bin, ['plugin', 'install', PLUGIN_REF]);
36
+ if (!installResult.ok) {
37
+ return { ok: false, reason: `plugin install failed: ${installResult.reason ?? 'unknown'}` };
38
+ }
39
+ return { ok: true };
40
+ }
41
+ async function installAgentSkills() {
42
+ if (!(await commandExists('npx')))
43
+ return { ok: false, reason: '`npx` not available on PATH' };
44
+ return runCommand('npx', ['-y', 'skills', 'add', MARKETPLACE, '--all']);
45
+ }
46
+ function commandExists(cmd) {
47
+ return runCommand('sh', ['-c', `command -v ${cmd}`]).then((r) => r.ok);
48
+ }
49
+ function runCommand(cmd, args) {
50
+ return new Promise((resolve) => {
51
+ const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
52
+ let stderr = '';
53
+ child.stderr?.on('data', (chunk) => {
54
+ stderr += chunk.toString();
55
+ });
56
+ child.on('error', (err) => resolve({ ok: false, reason: err.message }));
57
+ child.on('exit', (code) => {
58
+ if (code === 0)
59
+ resolve({ ok: true });
60
+ else
61
+ resolve({ ok: false, reason: stderr.trim() || `exit ${code}` });
62
+ });
63
+ });
64
+ }
@@ -0,0 +1,35 @@
1
+ export type ClientId = 'claude-code' | 'cursor' | 'claude-desktop' | 'vscode' | 'codex';
2
+ export interface Credential {
3
+ value: string;
4
+ accountId?: string;
5
+ environmentId?: string;
6
+ environmentSlug?: string;
7
+ }
8
+ export interface Account {
9
+ id: string;
10
+ displayName: string;
11
+ }
12
+ export interface Environment {
13
+ id: string;
14
+ name: string;
15
+ slug?: string;
16
+ }
17
+ export interface Session {
18
+ email: string;
19
+ accessToken: string;
20
+ }
21
+ export interface ApiKey {
22
+ id: string;
23
+ name: string;
24
+ lastFour: string;
25
+ scopes: string[];
26
+ keyType: string;
27
+ createdAt: string;
28
+ value?: string;
29
+ }
30
+ export interface WriteResult {
31
+ path: string;
32
+ client?: ClientId;
33
+ written: boolean;
34
+ backedUpTo?: string;
35
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export type CardStatus = "active" | "done" | "error";
3
+ interface CardProps {
4
+ title: string;
5
+ subtitle?: string;
6
+ status?: CardStatus;
7
+ children: React.ReactNode;
8
+ footer?: React.ReactNode;
9
+ }
10
+ export declare function Card({ title, subtitle, status, children, footer, }: CardProps): React.ReactElement;
11
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { COLORS, GLYPHS, shouldRenderBorder } from "../theme.js";
4
+ const STATUS_GLYPH = {
5
+ active: "",
6
+ done: GLYPHS.check,
7
+ error: GLYPHS.cross,
8
+ };
9
+ const STATUS_COLOR = {
10
+ active: COLORS.primary,
11
+ done: COLORS.success,
12
+ error: COLORS.error,
13
+ };
14
+ export function Card({ title, subtitle, status = "active", children, footer, }) {
15
+ const useBorder = shouldRenderBorder();
16
+ const borderColor = STATUS_COLOR[status];
17
+ const glyph = STATUS_GLYPH[status];
18
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", borderStyle: useBorder ? "round" : undefined, borderColor: borderColor, paddingX: useBorder ? 1 : 0, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: borderColor, bold: true, children: title }), _jsxs(Box, { children: [subtitle && _jsx(Text, { dimColor: true, children: subtitle }), glyph && (_jsxs(Text, { color: borderColor, children: [subtitle ? " " : "", glyph] }))] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: children }), footer] }));
19
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export interface ContextEntry {
3
+ label: string;
4
+ value: string;
5
+ hint?: string;
6
+ }
7
+ interface ContextRowProps {
8
+ entries: ContextEntry[];
9
+ }
10
+ export declare function ContextRow({ entries }: ContextRowProps): React.ReactElement;
11
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ export function ContextRow({ entries }) {
4
+ if (entries.length === 0)
5
+ return _jsx(Box, {});
6
+ const labelWidth = Math.max(...entries.map((e) => e.label.length));
7
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: entries.map((entry, i) => (_jsxs(Box, { children: [_jsx(Box, { width: labelWidth + 2, children: _jsx(Text, { dimColor: true, children: entry.label }) }), _jsx(Text, { children: entry.value }), entry.hint && (_jsxs(Text, { dimColor: true, children: [" ", entry.hint] }))] }, `${entry.label}-${i}`))) }));
8
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ export interface KeyHint {
3
+ key: string;
4
+ label: string;
5
+ }
6
+ interface FooterProps {
7
+ hints: KeyHint[];
8
+ }
9
+ export declare function Footer({ hints }: FooterProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import React from "react";
4
+ import { GLYPHS } from "../theme.js";
5
+ export function Footer({ hints }) {
6
+ if (hints.length === 0)
7
+ return _jsx(Box, {});
8
+ return (_jsx(Box, { marginTop: 1, children: hints.map((hint, i) => (_jsxs(React.Fragment, { children: [i > 0 && (_jsxs(Text, { dimColor: true, children: [" ", GLYPHS.bullet, " "] })), _jsx(Text, { color: "cyan", children: hint.key }), _jsxs(Text, { dimColor: true, children: [" ", hint.label] })] }, `${hint.key}-${i}`))) }));
9
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ interface HeaderProps {
3
+ title: string;
4
+ step?: {
5
+ current: number;
6
+ total: number;
7
+ };
8
+ badge?: string;
9
+ }
10
+ export declare function Header({ title, step, badge }: HeaderProps): React.ReactElement;
11
+ export {};
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { COLORS } from "../theme.js";
4
+ export function Header({ title, step, badge }) {
5
+ return (_jsxs(Box, { justifyContent: "space-between", marginBottom: 1, children: [_jsx(Text, { bold: true, color: COLORS.primary, children: title }), _jsxs(Box, { children: [step && (_jsxs(Text, { dimColor: true, children: ["step ", step.current, " of ", step.total] })), badge && (_jsxs(Text, { color: "cyan", children: [step ? " " : "", badge] }))] })] }));
6
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ export type DiffLineKind = "add" | "remove" | "context" | "header";
3
+ export interface DiffLine {
4
+ kind: DiffLineKind;
5
+ text: string;
6
+ }
7
+ interface JsonPreviewProps {
8
+ lines: DiffLine[];
9
+ title?: string;
10
+ }
11
+ export declare function JsonPreview({ lines, title, }: JsonPreviewProps): React.ReactElement;
12
+ export declare function jsonToDiffLines(entry: unknown, serverName: string): DiffLine[];
13
+ export {};