@stigg/terminal 0.0.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +47 -0
- package/dist/api/client.d.ts +6 -0
- package/dist/api/client.js +48 -0
- package/dist/api/format-key.d.ts +7 -0
- package/dist/api/format-key.js +12 -0
- package/dist/api/graphql-client.d.ts +5 -0
- package/dist/api/graphql-client.js +44 -0
- package/dist/api/operations.d.ts +65 -0
- package/dist/api/operations.js +77 -0
- package/dist/api/types.d.ts +18 -0
- package/dist/api/types.js +1 -0
- package/dist/auth/callback-server.d.ts +14 -0
- package/dist/auth/callback-server.js +145 -0
- package/dist/auth/config.d.ts +2 -0
- package/dist/auth/config.js +24 -0
- package/dist/auth/oauth.d.ts +17 -0
- package/dist/auth/oauth.js +94 -0
- package/dist/auth/storage.d.ts +6 -0
- package/dist/auth/storage.js +34 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +79 -0
- package/dist/commands/dash.d.ts +1 -0
- package/dist/commands/dash.js +24 -0
- package/dist/commands/debug.d.ts +1 -0
- package/dist/commands/debug.js +53 -0
- package/dist/commands/env.d.ts +1 -0
- package/dist/commands/env.js +15 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +59 -0
- package/dist/commands/mcp.d.ts +7 -0
- package/dist/commands/mcp.js +37 -0
- package/dist/commands/skills.d.ts +6 -0
- package/dist/commands/skills.js +48 -0
- package/dist/headless/host-agent.d.ts +19 -0
- package/dist/headless/host-agent.js +64 -0
- package/dist/headless/init-phase1.d.ts +9 -0
- package/dist/headless/init-phase1.js +173 -0
- package/dist/headless/init-phase2.d.ts +36 -0
- package/dist/headless/init-phase2.js +150 -0
- package/dist/headless/next-step-prompt.d.ts +7 -0
- package/dist/headless/next-step-prompt.js +25 -0
- package/dist/headless/options.d.ts +30 -0
- package/dist/headless/options.js +77 -0
- package/dist/headless/reporter.d.ts +16 -0
- package/dist/headless/reporter.js +41 -0
- package/dist/headless/setup.d.ts +29 -0
- package/dist/headless/setup.js +80 -0
- package/dist/launch/agent.d.ts +55 -0
- package/dist/launch/agent.js +134 -0
- package/dist/mcp/clients/base.d.ts +49 -0
- package/dist/mcp/clients/base.js +66 -0
- package/dist/mcp/clients/claude-code.d.ts +22 -0
- package/dist/mcp/clients/claude-code.js +120 -0
- package/dist/mcp/clients/claude-desktop.d.ts +9 -0
- package/dist/mcp/clients/claude-desktop.js +35 -0
- package/dist/mcp/clients/codex.d.ts +13 -0
- package/dist/mcp/clients/codex.js +113 -0
- package/dist/mcp/clients/cursor.d.ts +9 -0
- package/dist/mcp/clients/cursor.js +26 -0
- package/dist/mcp/clients/index.d.ts +7 -0
- package/dist/mcp/clients/index.js +27 -0
- package/dist/mcp/clients/mcp-remote.d.ts +11 -0
- package/dist/mcp/clients/mcp-remote.js +13 -0
- package/dist/mcp/clients/vscode.d.ts +17 -0
- package/dist/mcp/clients/vscode.js +84 -0
- package/dist/mcp/clients.d.ts +4 -0
- package/dist/mcp/clients.js +50 -0
- package/dist/mcp/config-merge.d.ts +9 -0
- package/dist/mcp/config-merge.js +51 -0
- package/dist/mcp/writer.d.ts +6 -0
- package/dist/mcp/writer.js +65 -0
- package/dist/setup/storage.d.ts +21 -0
- package/dist/setup/storage.js +34 -0
- package/dist/skills/install.d.ts +19 -0
- package/dist/skills/install.js +64 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.js +1 -0
- package/dist/ui/components/Card.d.ts +11 -0
- package/dist/ui/components/Card.js +19 -0
- package/dist/ui/components/ContextRow.d.ts +11 -0
- package/dist/ui/components/ContextRow.js +8 -0
- package/dist/ui/components/Footer.d.ts +10 -0
- package/dist/ui/components/Footer.js +9 -0
- package/dist/ui/components/Header.d.ts +11 -0
- package/dist/ui/components/Header.js +6 -0
- package/dist/ui/components/JsonPreview.d.ts +13 -0
- package/dist/ui/components/JsonPreview.js +25 -0
- package/dist/ui/components/SectionTitle.d.ts +6 -0
- package/dist/ui/components/SectionTitle.js +5 -0
- package/dist/ui/hooks/useAsyncEffect.d.ts +3 -0
- package/dist/ui/hooks/useAsyncEffect.js +20 -0
- package/dist/ui/hooks/useResize.d.ts +4 -0
- package/dist/ui/hooks/useResize.js +20 -0
- package/dist/ui/hud.d.ts +15 -0
- package/dist/ui/hud.js +30 -0
- package/dist/ui/ink-theme.d.ts +2 -0
- package/dist/ui/ink-theme.js +34 -0
- package/dist/ui/intro/LogoView.d.ts +12 -0
- package/dist/ui/intro/LogoView.js +226 -0
- package/dist/ui/intro/MatrixIntro.d.ts +6 -0
- package/dist/ui/intro/MatrixIntro.js +80 -0
- package/dist/ui/intro/logo.d.ts +6 -0
- package/dist/ui/intro/logo.js +21 -0
- package/dist/ui/messages.d.ts +5 -0
- package/dist/ui/messages.js +5 -0
- package/dist/ui/screens/DashScreen.d.ts +6 -0
- package/dist/ui/screens/DashScreen.js +27 -0
- package/dist/ui/screens/DebugScreen.d.ts +6 -0
- package/dist/ui/screens/DebugScreen.js +39 -0
- package/dist/ui/screens/InitScreen.d.ts +6 -0
- package/dist/ui/screens/InitScreen.js +138 -0
- package/dist/ui/screens/MenuScreen.d.ts +7 -0
- package/dist/ui/screens/MenuScreen.js +38 -0
- package/dist/ui/state.d.ts +72 -0
- package/dist/ui/state.js +107 -0
- package/dist/ui/steps/AccountStep.d.ts +8 -0
- package/dist/ui/steps/AccountStep.js +42 -0
- package/dist/ui/steps/ApiKeyStep.d.ts +8 -0
- package/dist/ui/steps/ApiKeyStep.js +91 -0
- package/dist/ui/steps/ClientsStep.d.ts +10 -0
- package/dist/ui/steps/ClientsStep.js +69 -0
- package/dist/ui/steps/CredentialKindStep.d.ts +7 -0
- package/dist/ui/steps/CredentialKindStep.js +18 -0
- package/dist/ui/steps/EnvironmentStep.d.ts +8 -0
- package/dist/ui/steps/EnvironmentStep.js +37 -0
- package/dist/ui/steps/LoginStep.d.ts +7 -0
- package/dist/ui/steps/LoginStep.js +56 -0
- package/dist/ui/steps/SkillsStep.d.ts +7 -0
- package/dist/ui/steps/SkillsStep.js +7 -0
- package/dist/ui/steps/SummaryStep.d.ts +8 -0
- package/dist/ui/steps/SummaryStep.js +41 -0
- package/dist/ui/steps/WritingStep.d.ts +10 -0
- package/dist/ui/steps/WritingStep.js +96 -0
- package/dist/ui/theme.d.ts +53 -0
- package/dist/ui/theme.js +66 -0
- package/dist/ui/tui/App.d.ts +10 -0
- package/dist/ui/tui/App.js +51 -0
- package/dist/ui/tui/components/ContextStrip.d.ts +11 -0
- package/dist/ui/tui/components/ContextStrip.js +8 -0
- package/dist/ui/tui/components/TitleBar.d.ts +6 -0
- package/dist/ui/tui/components/TitleBar.js +18 -0
- package/dist/ui/tui/components/WizardChecklist.d.ts +7 -0
- package/dist/ui/tui/components/WizardChecklist.js +69 -0
- package/dist/ui/tui/hooks/keyboard-hints-utils.d.ts +26 -0
- package/dist/ui/tui/hooks/keyboard-hints-utils.js +69 -0
- package/dist/ui/tui/hooks/useKeyBindings.d.ts +14 -0
- package/dist/ui/tui/hooks/useKeyBindings.js +44 -0
- package/dist/ui/tui/hooks/useKeyboardHints.d.ts +13 -0
- package/dist/ui/tui/hooks/useKeyboardHints.js +38 -0
- package/dist/ui/tui/hooks/useStdoutDimensions.d.ts +8 -0
- package/dist/ui/tui/hooks/useStdoutDimensions.js +28 -0
- package/dist/ui/tui/primitives/BlinkingLabel.d.ts +19 -0
- package/dist/ui/tui/primitives/BlinkingLabel.js +25 -0
- package/dist/ui/tui/primitives/ConfirmPrompt.d.ts +16 -0
- package/dist/ui/tui/primitives/ConfirmPrompt.js +36 -0
- package/dist/ui/tui/primitives/KeyboardHintsBar.d.ts +2 -0
- package/dist/ui/tui/primitives/KeyboardHintsBar.js +8 -0
- package/dist/ui/tui/primitives/PickerMenu.d.ts +38 -0
- package/dist/ui/tui/primitives/PickerMenu.js +162 -0
- package/dist/ui/tui/primitives/PromptLabel.d.ts +6 -0
- package/dist/ui/tui/primitives/PromptLabel.js +6 -0
- package/dist/ui/tui/primitives/ScreenContainer.d.ts +39 -0
- package/dist/ui/tui/primitives/ScreenContainer.js +39 -0
- package/dist/ui/tui/primitives/Spinner.d.ts +7 -0
- package/dist/ui/tui/primitives/Spinner.js +18 -0
- package/dist/ui/tui/screens/DashScreen.d.ts +6 -0
- package/dist/ui/tui/screens/DashScreen.js +37 -0
- package/dist/ui/tui/screens/DebugScreen.d.ts +6 -0
- package/dist/ui/tui/screens/DebugScreen.js +48 -0
- package/dist/ui/tui/screens/EnvScreen.d.ts +6 -0
- package/dist/ui/tui/screens/EnvScreen.js +192 -0
- package/dist/ui/tui/screens/InitScreen.d.ts +9 -0
- package/dist/ui/tui/screens/InitScreen.js +102 -0
- package/dist/ui/tui/screens/MenuScreen.d.ts +7 -0
- package/dist/ui/tui/screens/MenuScreen.js +84 -0
- package/dist/ui/tui/start-tui.d.ts +11 -0
- package/dist/ui/tui/start-tui.js +72 -0
- package/dist/ui/tui/steps/AccountStep.d.ts +8 -0
- package/dist/ui/tui/steps/AccountStep.js +42 -0
- package/dist/ui/tui/steps/ApiKeyStep.d.ts +8 -0
- package/dist/ui/tui/steps/ApiKeyStep.js +53 -0
- package/dist/ui/tui/steps/ClientsStep.d.ts +10 -0
- package/dist/ui/tui/steps/ClientsStep.js +52 -0
- package/dist/ui/tui/steps/CredentialKindStep.d.ts +7 -0
- package/dist/ui/tui/steps/CredentialKindStep.js +18 -0
- package/dist/ui/tui/steps/EnvironmentStep.d.ts +8 -0
- package/dist/ui/tui/steps/EnvironmentStep.js +38 -0
- package/dist/ui/tui/steps/LoginStep.d.ts +8 -0
- package/dist/ui/tui/steps/LoginStep.js +79 -0
- package/dist/ui/tui/steps/SkillsStep.d.ts +7 -0
- package/dist/ui/tui/steps/SkillsStep.js +7 -0
- package/dist/ui/tui/steps/SummaryStep.d.ts +10 -0
- package/dist/ui/tui/steps/SummaryStep.js +133 -0
- package/dist/ui/tui/steps/WritingStep.d.ts +10 -0
- package/dist/ui/tui/steps/WritingStep.js +101 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stigg
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @stigg/terminal
|
|
2
|
+
|
|
3
|
+
> Interactive setup TUI for wiring [Stigg](https://stigg.io) into your AI coding assistants.
|
|
4
|
+
|
|
5
|
+
`@stigg/terminal` is a terminal app that connects your editor's AI agent (Claude Code, Claude
|
|
6
|
+
Desktop, Cursor, VS Code, Codex, …) to Stigg — authenticating you, writing the Stigg MCP server
|
|
7
|
+
into each client's config, and installing the Stigg skills, all from one place.
|
|
8
|
+
|
|
9
|
+
> ⚠️ **Alpha** (`0.0.1-alpha`) — under active development; expect breaking changes.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js **>= 22**
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Run it directly with `npx` (no install needed):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @stigg/terminal
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With no arguments in an interactive terminal, it launches the full-screen setup TUI.
|
|
24
|
+
|
|
25
|
+
Or install globally and use the `stiggt` command:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i -g @stigg/terminal
|
|
29
|
+
stiggt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
| Command | Description |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| _(no args)_ | Launch the interactive setup TUI |
|
|
37
|
+
| `init` | Set up Stigg in this project — auth, MCP, and skills. Add `--headless` for a non-interactive run, `--json` for machine-readable output |
|
|
38
|
+
| `mcp add` | Write the Stigg MCP entry into one or more AI client configs (non-interactive) |
|
|
39
|
+
| `skills add` | Install the Stigg skills for one or more AI clients (non-interactive) |
|
|
40
|
+
| `env` | Switch the active Stigg environment |
|
|
41
|
+
| `dash` | Open the Stigg dashboard in your browser |
|
|
42
|
+
|
|
43
|
+
Run `stiggt <command> --help` for the full set of flags on any command.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
ISC
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Account } from '../types.js';
|
|
2
|
+
import type { ApiKey, CreateScopedKeyOptions, Environment } from './types.js';
|
|
3
|
+
export declare function listAccounts(accessToken: string): Promise<Account[]>;
|
|
4
|
+
export declare function listEnvironments(accessToken: string, accountId: string): Promise<Environment[]>;
|
|
5
|
+
export declare function listApiKeys(accessToken: string, accountId: string, envId: string): Promise<ApiKey[]>;
|
|
6
|
+
export declare function createScopedApiKey(accessToken: string, accountId: string, envId: string, opts?: CreateScopedKeyOptions): Promise<string>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getGqlClient, gqlRequest } from './graphql-client.js';
|
|
2
|
+
import { API_KEYS_QUERY, CREATE_SCOPED_API_KEY_MUTATION, CURRENT_USER_QUERY, ENVIRONMENTS_QUERY, fullScopeGrid, } from './operations.js';
|
|
3
|
+
function lastFour(s) {
|
|
4
|
+
return s.slice(-4);
|
|
5
|
+
}
|
|
6
|
+
export async function listAccounts(accessToken) {
|
|
7
|
+
const client = getGqlClient(accessToken);
|
|
8
|
+
const data = await gqlRequest(client, CURRENT_USER_QUERY);
|
|
9
|
+
return data.currentUser.memberships.map((m) => ({
|
|
10
|
+
id: m.account.id,
|
|
11
|
+
displayName: m.account.displayName,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export async function listEnvironments(accessToken, accountId) {
|
|
15
|
+
const client = getGqlClient(accessToken, accountId);
|
|
16
|
+
const data = await gqlRequest(client, ENVIRONMENTS_QUERY);
|
|
17
|
+
return data.environments.edges.map(({ node }) => ({
|
|
18
|
+
id: node.id,
|
|
19
|
+
name: node.displayName,
|
|
20
|
+
slug: node.slug,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
export async function listApiKeys(accessToken, accountId, envId) {
|
|
24
|
+
const client = getGqlClient(accessToken, accountId);
|
|
25
|
+
const data = await gqlRequest(client, API_KEYS_QUERY, {
|
|
26
|
+
envId,
|
|
27
|
+
});
|
|
28
|
+
return data.apiKeys.edges.map(({ node }) => ({
|
|
29
|
+
id: node.id,
|
|
30
|
+
name: node.displayName,
|
|
31
|
+
lastFour: lastFour(node.token),
|
|
32
|
+
scopes: ['read', 'write'],
|
|
33
|
+
keyType: node.keyType,
|
|
34
|
+
createdAt: node.createdAt,
|
|
35
|
+
value: node.token,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
export async function createScopedApiKey(accessToken, accountId, envId, opts = {}) {
|
|
39
|
+
const client = getGqlClient(accessToken, accountId);
|
|
40
|
+
const data = await gqlRequest(client, CREATE_SCOPED_API_KEY_MUTATION, {
|
|
41
|
+
input: {
|
|
42
|
+
displayName: opts.name ?? 'stigg-terminal-mcp',
|
|
43
|
+
environmentId: envId,
|
|
44
|
+
scopes: fullScopeGrid(),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return data.createScopedApiKey.token;
|
|
48
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ApiKey } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Render a key's identity for UI display: first 8 chars of the secret +
|
|
4
|
+
* `…` + last 4. Falls back to `…lastFour` when the secret value isn't
|
|
5
|
+
* loaded (partial responses).
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatApiKeyShort(key: Pick<ApiKey, 'value' | 'lastFour'>): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a key's identity for UI display: first 8 chars of the secret +
|
|
3
|
+
* `…` + last 4. Falls back to `…lastFour` when the secret value isn't
|
|
4
|
+
* loaded (partial responses).
|
|
5
|
+
*/
|
|
6
|
+
export function formatApiKeyShort(key) {
|
|
7
|
+
if (!key.value)
|
|
8
|
+
return `…${key.lastFour}`;
|
|
9
|
+
if (key.value.length <= 12)
|
|
10
|
+
return key.value;
|
|
11
|
+
return `${key.value.slice(0, 8)}…${key.lastFour}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { GraphQLClient } from 'graphql-request';
|
|
2
|
+
export declare function graphqlEndpoint(): string;
|
|
3
|
+
export declare function getGqlClient(accessToken: string, accountId?: string): GraphQLClient;
|
|
4
|
+
export declare function formatGqlError(err: unknown): string;
|
|
5
|
+
export declare function gqlRequest<T>(client: GraphQLClient, query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GraphQLClient } from 'graphql-request';
|
|
2
|
+
import { optionalEnv } from '../auth/config.js';
|
|
3
|
+
const DEFAULT_URL = 'https://api.stigg.io/graphql';
|
|
4
|
+
export function graphqlEndpoint() {
|
|
5
|
+
return optionalEnv('STIGG_API_URL') ?? DEFAULT_URL;
|
|
6
|
+
}
|
|
7
|
+
export function getGqlClient(accessToken, accountId) {
|
|
8
|
+
const headers = {
|
|
9
|
+
Authorization: `Bearer ${accessToken}`,
|
|
10
|
+
};
|
|
11
|
+
if (accountId)
|
|
12
|
+
headers['X-Account-Id'] = accountId;
|
|
13
|
+
return new GraphQLClient(graphqlEndpoint(), { headers });
|
|
14
|
+
}
|
|
15
|
+
export function formatGqlError(err) {
|
|
16
|
+
if (!(err instanceof Error))
|
|
17
|
+
return String(err);
|
|
18
|
+
const gql = err;
|
|
19
|
+
const gqlErrors = gql.response?.errors;
|
|
20
|
+
if (gqlErrors && gqlErrors.length > 0) {
|
|
21
|
+
const msgs = gqlErrors.map((e) => e.message ?? '(no message)').join('; ');
|
|
22
|
+
const status = gql.response?.status;
|
|
23
|
+
return status ? `GraphQL ${status}: ${msgs}` : `GraphQL error: ${msgs}`;
|
|
24
|
+
}
|
|
25
|
+
const cause = err.cause;
|
|
26
|
+
if (cause instanceof Error || (cause && typeof cause === 'object')) {
|
|
27
|
+
const c = cause;
|
|
28
|
+
const detail = c.code ? `${c.code}${c.message ? ` — ${c.message}` : ''}` : c.message;
|
|
29
|
+
if (detail)
|
|
30
|
+
return `${err.message} → ${detail} (${graphqlEndpoint()})`;
|
|
31
|
+
}
|
|
32
|
+
if (/fetch failed/i.test(err.message)) {
|
|
33
|
+
return `${err.message} (${graphqlEndpoint()})`;
|
|
34
|
+
}
|
|
35
|
+
return err.message;
|
|
36
|
+
}
|
|
37
|
+
export async function gqlRequest(client, query, variables) {
|
|
38
|
+
try {
|
|
39
|
+
return await client.request(query, variables);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new Error(formatGqlError(err));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export declare const CURRENT_USER_QUERY = "\n query StiggTerminalCurrentUser {\n currentUser {\n id\n email\n name\n memberships {\n id\n account {\n id\n displayName\n }\n }\n }\n }\n";
|
|
2
|
+
export declare const ENVIRONMENTS_QUERY = "\n query StiggTerminalEnvironments {\n environments(\n filter: { permanentDeletionDate: { is: null } }\n paging: { first: 50 }\n ) {\n edges {\n node {\n id\n slug\n displayName\n type\n color\n }\n }\n }\n }\n";
|
|
3
|
+
export declare const API_KEYS_QUERY = "\n query StiggTerminalApiKeys($envId: UUID!) {\n apiKeys(input: { environmentId: $envId, keyTypes: [SERVER, SCOPED], paging: { first: 50 } }) {\n edges {\n node {\n id\n displayName\n token\n keyType\n createdAt\n }\n }\n }\n }\n";
|
|
4
|
+
export declare const CREATE_SCOPED_API_KEY_MUTATION = "\n mutation StiggTerminalCreateScopedApiKey($input: CreateScopedApiKeyInput!) {\n createScopedApiKey(input: $input) {\n id\n displayName\n token\n createdAt\n }\n }\n";
|
|
5
|
+
export interface CurrentUserResult {
|
|
6
|
+
currentUser: {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string | null;
|
|
9
|
+
name: string | null;
|
|
10
|
+
memberships: Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
account: {
|
|
13
|
+
id: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface EnvironmentsResult {
|
|
20
|
+
environments: {
|
|
21
|
+
edges: Array<{
|
|
22
|
+
node: {
|
|
23
|
+
id: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
displayName: string;
|
|
26
|
+
type: string;
|
|
27
|
+
color: string | null;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface ApiKeysResult {
|
|
33
|
+
apiKeys: {
|
|
34
|
+
edges: Array<{
|
|
35
|
+
node: {
|
|
36
|
+
id: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
token: string;
|
|
39
|
+
keyType: string;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface CreateScopedApiKeyResult {
|
|
46
|
+
createScopedApiKey: {
|
|
47
|
+
id: string;
|
|
48
|
+
displayName: string;
|
|
49
|
+
token: string;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export type ApiKeyScopeAction = 'READ' | 'WRITE';
|
|
54
|
+
export type ApiKeyScopeResource = 'API_KEY' | 'COUPON' | 'CUSTOMER' | 'ENVIRONMENT' | 'EVENT_QUEUE' | 'SUBSCRIPTION';
|
|
55
|
+
export interface ApiKeyScopeInput {
|
|
56
|
+
action: ApiKeyScopeAction;
|
|
57
|
+
resource: ApiKeyScopeResource;
|
|
58
|
+
}
|
|
59
|
+
export interface CreateScopedApiKeyInput {
|
|
60
|
+
description?: string;
|
|
61
|
+
displayName: string;
|
|
62
|
+
environmentId: string;
|
|
63
|
+
scopes: ApiKeyScopeInput[];
|
|
64
|
+
}
|
|
65
|
+
export declare function fullScopeGrid(): ApiKeyScopeInput[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const CURRENT_USER_QUERY = /* GraphQL */ `
|
|
2
|
+
query StiggTerminalCurrentUser {
|
|
3
|
+
currentUser {
|
|
4
|
+
id
|
|
5
|
+
email
|
|
6
|
+
name
|
|
7
|
+
memberships {
|
|
8
|
+
id
|
|
9
|
+
account {
|
|
10
|
+
id
|
|
11
|
+
displayName
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
export const ENVIRONMENTS_QUERY = /* GraphQL */ `
|
|
18
|
+
query StiggTerminalEnvironments {
|
|
19
|
+
environments(
|
|
20
|
+
filter: { permanentDeletionDate: { is: null } }
|
|
21
|
+
paging: { first: 50 }
|
|
22
|
+
) {
|
|
23
|
+
edges {
|
|
24
|
+
node {
|
|
25
|
+
id
|
|
26
|
+
slug
|
|
27
|
+
displayName
|
|
28
|
+
type
|
|
29
|
+
color
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export const API_KEYS_QUERY = /* GraphQL */ `
|
|
36
|
+
query StiggTerminalApiKeys($envId: UUID!) {
|
|
37
|
+
apiKeys(input: { environmentId: $envId, keyTypes: [SERVER, SCOPED], paging: { first: 50 } }) {
|
|
38
|
+
edges {
|
|
39
|
+
node {
|
|
40
|
+
id
|
|
41
|
+
displayName
|
|
42
|
+
token
|
|
43
|
+
keyType
|
|
44
|
+
createdAt
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
export const CREATE_SCOPED_API_KEY_MUTATION = /* GraphQL */ `
|
|
51
|
+
mutation StiggTerminalCreateScopedApiKey($input: CreateScopedApiKeyInput!) {
|
|
52
|
+
createScopedApiKey(input: $input) {
|
|
53
|
+
id
|
|
54
|
+
displayName
|
|
55
|
+
token
|
|
56
|
+
createdAt
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
const ALL_RESOURCES = [
|
|
61
|
+
'API_KEY',
|
|
62
|
+
'COUPON',
|
|
63
|
+
'CUSTOMER',
|
|
64
|
+
'ENVIRONMENT',
|
|
65
|
+
'EVENT_QUEUE',
|
|
66
|
+
'SUBSCRIPTION',
|
|
67
|
+
];
|
|
68
|
+
export function fullScopeGrid() {
|
|
69
|
+
const scopes = [];
|
|
70
|
+
for (const resource of ALL_RESOURCES) {
|
|
71
|
+
scopes.push({ action: 'READ', resource });
|
|
72
|
+
if (resource !== 'EVENT_QUEUE') {
|
|
73
|
+
scopes.push({ action: 'WRITE', resource });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return scopes;
|
|
77
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Environment {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
slug?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CreateScopedKeyOptions {
|
|
7
|
+
name?: string;
|
|
8
|
+
scopes?: 'godmode' | string;
|
|
9
|
+
}
|
|
10
|
+
export interface ApiKey {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
lastFour: string;
|
|
14
|
+
scopes: string[];
|
|
15
|
+
keyType: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
value?: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CallbackServer {
|
|
2
|
+
port: number;
|
|
3
|
+
awaitCallback(): Promise<URL>;
|
|
4
|
+
close(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface StartCallbackServerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Loopback port to bind to. When omitted (or 0), the OS picks a free port.
|
|
9
|
+
* Production runs always pass a fixed port so a single
|
|
10
|
+
* `http://127.0.0.1:<port>/callback` can be whitelisted in the Auth0 app.
|
|
11
|
+
*/
|
|
12
|
+
port?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function startCallbackServer(options?: StartCallbackServerOptions): Promise<CallbackServer>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
export async function startCallbackServer(options = {}) {
|
|
3
|
+
let resolveCallback;
|
|
4
|
+
let rejectCallback;
|
|
5
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
6
|
+
resolveCallback = resolve;
|
|
7
|
+
rejectCallback = reject;
|
|
8
|
+
});
|
|
9
|
+
let server;
|
|
10
|
+
server = createServer((req, res) => {
|
|
11
|
+
if (!req.url) {
|
|
12
|
+
res.writeHead(400).end();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const port = server.address()?.port ?? 0;
|
|
16
|
+
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
17
|
+
if (url.pathname !== '/callback') {
|
|
18
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not found');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const err = url.searchParams.get('error');
|
|
22
|
+
if (err) {
|
|
23
|
+
const desc = url.searchParams.get('error_description') ?? '';
|
|
24
|
+
res
|
|
25
|
+
.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
26
|
+
.end(renderPage(false, `${err}${desc ? `: ${desc}` : ''}`));
|
|
27
|
+
rejectCallback(new Error(`Auth0 callback returned error: ${err} ${desc}`));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }).end(renderPage(true));
|
|
31
|
+
resolveCallback(url);
|
|
32
|
+
});
|
|
33
|
+
const requestedPort = options.port ?? 0;
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
server.once('error', reject);
|
|
36
|
+
server.listen(requestedPort, '127.0.0.1', () => {
|
|
37
|
+
server.off('error', reject);
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
}).catch((err) => {
|
|
41
|
+
if (err.code === 'EADDRINUSE') {
|
|
42
|
+
throw new Error(`Port ${requestedPort} is already in use. Free it up, or pass a different ` +
|
|
43
|
+
`port via --callback-port <port> (must be whitelisted in the Auth0 app).`);
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
46
|
+
});
|
|
47
|
+
const addr = server.address();
|
|
48
|
+
return {
|
|
49
|
+
port: addr.port,
|
|
50
|
+
awaitCallback: () => callbackPromise,
|
|
51
|
+
close: () => {
|
|
52
|
+
server.close();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function renderPage(success, message) {
|
|
57
|
+
const accent = success ? '#9eff00' : '#ff4d4d';
|
|
58
|
+
const accentDim = success ? 'rgba(158, 255, 0, 0.45)' : 'rgba(255, 77, 77, 0.45)';
|
|
59
|
+
const line1 = success ? 'Stigg login complete!' : 'Stigg login failed.';
|
|
60
|
+
const line2 = success
|
|
61
|
+
? 'Return to your terminal — setup continues there'
|
|
62
|
+
: `Reason: ${escapeHtml(message ?? 'unknown error')}`;
|
|
63
|
+
return `<!doctype html>
|
|
64
|
+
<html lang="en">
|
|
65
|
+
<head>
|
|
66
|
+
<meta charset="utf-8">
|
|
67
|
+
<title>Stigg · terminal</title>
|
|
68
|
+
<style>
|
|
69
|
+
:root { color-scheme: dark; }
|
|
70
|
+
html, body { height: 100%; margin: 0; }
|
|
71
|
+
body {
|
|
72
|
+
background: #050505;
|
|
73
|
+
color: ${accent};
|
|
74
|
+
font-family: "Menlo", "SF Mono", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;
|
|
75
|
+
font-size: 18px;
|
|
76
|
+
line-height: 1.6;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
text-shadow: 0 0 8px ${accentDim};
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
}
|
|
83
|
+
main {
|
|
84
|
+
text-align: left;
|
|
85
|
+
padding: 0 24px;
|
|
86
|
+
max-width: 720px;
|
|
87
|
+
}
|
|
88
|
+
.line { margin: 0; white-space: pre; }
|
|
89
|
+
.cursor {
|
|
90
|
+
display: inline-block;
|
|
91
|
+
width: 0.55em;
|
|
92
|
+
height: 1em;
|
|
93
|
+
background: ${accent};
|
|
94
|
+
vertical-align: -0.12em;
|
|
95
|
+
margin-left: 4px;
|
|
96
|
+
box-shadow: 0 0 8px ${accentDim};
|
|
97
|
+
animation: blink 1s steps(2, start) infinite;
|
|
98
|
+
}
|
|
99
|
+
@keyframes blink {
|
|
100
|
+
to { background: transparent; box-shadow: none; }
|
|
101
|
+
}
|
|
102
|
+
/* CRT scanline overlay */
|
|
103
|
+
body::after {
|
|
104
|
+
content: "";
|
|
105
|
+
position: fixed;
|
|
106
|
+
inset: 0;
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
background: repeating-linear-gradient(
|
|
109
|
+
to bottom,
|
|
110
|
+
rgba(0, 0, 0, 0) 0px,
|
|
111
|
+
rgba(0, 0, 0, 0) 2px,
|
|
112
|
+
rgba(0, 0, 0, 0.18) 3px,
|
|
113
|
+
rgba(0, 0, 0, 0.18) 4px
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
/* Subtle CRT vignette */
|
|
117
|
+
body::before {
|
|
118
|
+
content: "";
|
|
119
|
+
position: fixed;
|
|
120
|
+
inset: 0;
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
background: radial-gradient(
|
|
123
|
+
ellipse at center,
|
|
124
|
+
rgba(0, 0, 0, 0) 60%,
|
|
125
|
+
rgba(0, 0, 0, 0.55) 100%
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
129
|
+
</head>
|
|
130
|
+
<body>
|
|
131
|
+
<main>
|
|
132
|
+
<p class="line">${line1}</p>
|
|
133
|
+
<p class="line">${line2}<span class="cursor"></span></p>
|
|
134
|
+
</main>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`;
|
|
137
|
+
}
|
|
138
|
+
function escapeHtml(s) {
|
|
139
|
+
return s
|
|
140
|
+
.replace(/&/g, '&')
|
|
141
|
+
.replace(/</g, '<')
|
|
142
|
+
.replace(/>/g, '>')
|
|
143
|
+
.replace(/"/g, '"')
|
|
144
|
+
.replace(/'/g, ''');
|
|
145
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let envLoaded = false;
|
|
2
|
+
export function loadEnv() {
|
|
3
|
+
if (envLoaded)
|
|
4
|
+
return;
|
|
5
|
+
envLoaded = true;
|
|
6
|
+
// .env.local takes precedence over .env (machine-specific overrides win).
|
|
7
|
+
// process.loadEnvFile() only sets vars that aren't already in process.env,
|
|
8
|
+
// so shell vars beat both files and the first file loaded wins between them.
|
|
9
|
+
for (const file of ['.env.local', '.env']) {
|
|
10
|
+
try {
|
|
11
|
+
process.loadEnvFile(file);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// file not present in cwd — fine, try the next
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function optionalEnv(name) {
|
|
19
|
+
loadEnv();
|
|
20
|
+
const val = process.env[name];
|
|
21
|
+
if (!val || val.startsWith('<'))
|
|
22
|
+
return undefined;
|
|
23
|
+
return val;
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface AuthSession {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token?: string;
|
|
4
|
+
id_token: string;
|
|
5
|
+
expires_at: number;
|
|
6
|
+
email: string;
|
|
7
|
+
sub: string;
|
|
8
|
+
scope?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AuthenticateOptions {
|
|
11
|
+
onAuthUrlReady?: (url: string, port: number) => void;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/** Fixed loopback port for the OAuth callback. Takes precedence over
|
|
14
|
+
* `STIGG_CALLBACK_PORT`, which in turn overrides the built-in default. */
|
|
15
|
+
port?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function authenticate(opts?: AuthenticateOptions): Promise<AuthSession>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as openid from 'openid-client';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { optionalEnv } from './config.js';
|
|
4
|
+
import { startCallbackServer } from './callback-server.js';
|
|
5
|
+
const DEFAULT_AUTH0_DOMAIN = 'auth.stigg.io';
|
|
6
|
+
const DEFAULT_CLI_CLIENT_ID = 'NCcMvD7YNFUGfCUSrkq6HxiqHe0aqTc5';
|
|
7
|
+
const DEFAULT_AUDIENCE = 'https://api.stigg.io/';
|
|
8
|
+
const DEFAULT_SCOPE = 'openid email profile offline_access';
|
|
9
|
+
// Static loopback port for the OAuth callback so a single
|
|
10
|
+
// http://127.0.0.1:<port>/callback can be whitelisted in the Auth0 app.
|
|
11
|
+
const DEFAULT_CALLBACK_PORT = 57166;
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
13
|
+
export async function authenticate(opts = {}) {
|
|
14
|
+
const domain = optionalEnv('STIGG_AUTH0_DOMAIN') ?? DEFAULT_AUTH0_DOMAIN;
|
|
15
|
+
const clientId = optionalEnv('STIGG_CLI_CLIENT_ID') ?? DEFAULT_CLI_CLIENT_ID;
|
|
16
|
+
const audience = optionalEnv('STIGG_AUDIENCE') ?? DEFAULT_AUDIENCE;
|
|
17
|
+
const scope = optionalEnv('STIGG_AUTH0_SCOPE') ?? DEFAULT_SCOPE;
|
|
18
|
+
const fixedPort = resolveCallbackPort(opts.port);
|
|
19
|
+
const config = await openid.discovery(new URL(`https://${domain}`), clientId);
|
|
20
|
+
const codeVerifier = openid.randomPKCECodeVerifier();
|
|
21
|
+
const codeChallenge = await openid.calculatePKCECodeChallenge(codeVerifier);
|
|
22
|
+
const state = openid.randomState();
|
|
23
|
+
const server = await startCallbackServer({ port: fixedPort });
|
|
24
|
+
const redirectUri = `http://127.0.0.1:${server.port}/callback`;
|
|
25
|
+
const authUrl = openid.buildAuthorizationUrl(config, {
|
|
26
|
+
redirect_uri: redirectUri,
|
|
27
|
+
scope,
|
|
28
|
+
audience,
|
|
29
|
+
code_challenge: codeChallenge,
|
|
30
|
+
code_challenge_method: 'S256',
|
|
31
|
+
state,
|
|
32
|
+
});
|
|
33
|
+
opts.onAuthUrlReady?.(authUrl.href, server.port);
|
|
34
|
+
try {
|
|
35
|
+
await open(authUrl.href);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// browser couldn't open — caller prints URL via onAuthUrlReady
|
|
39
|
+
}
|
|
40
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
41
|
+
let timer;
|
|
42
|
+
try {
|
|
43
|
+
const callbackUrl = await Promise.race([
|
|
44
|
+
server.awaitCallback(),
|
|
45
|
+
new Promise((_, reject) => {
|
|
46
|
+
timer = setTimeout(() => reject(new Error(`Login timed out after ${timeoutMs / 1000}s`)), timeoutMs);
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
const tokens = await openid.authorizationCodeGrant(config, callbackUrl, {
|
|
50
|
+
pkceCodeVerifier: codeVerifier,
|
|
51
|
+
expectedState: state,
|
|
52
|
+
});
|
|
53
|
+
if (!tokens.id_token) {
|
|
54
|
+
throw new Error('Auth0 did not return an id_token');
|
|
55
|
+
}
|
|
56
|
+
const claims = decodeJwtPayload(tokens.id_token);
|
|
57
|
+
return {
|
|
58
|
+
access_token: tokens.access_token,
|
|
59
|
+
refresh_token: tokens.refresh_token,
|
|
60
|
+
id_token: tokens.id_token,
|
|
61
|
+
expires_at: Date.now() + (tokens.expires_in ?? 3600) * 1000,
|
|
62
|
+
email: typeof claims.email === 'string' ? claims.email : 'unknown',
|
|
63
|
+
sub: typeof claims.sub === 'string' ? claims.sub : 'unknown',
|
|
64
|
+
scope: tokens.scope,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (timer)
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
server.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function decodeJwtPayload(jwt) {
|
|
74
|
+
const parts = jwt.split('.');
|
|
75
|
+
if (parts.length !== 3)
|
|
76
|
+
throw new Error('Malformed JWT');
|
|
77
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
78
|
+
}
|
|
79
|
+
function resolveCallbackPort(cliPort) {
|
|
80
|
+
if (cliPort !== undefined) {
|
|
81
|
+
if (!Number.isInteger(cliPort) || cliPort < 1 || cliPort > 65535) {
|
|
82
|
+
throw new Error(`--callback-port must be an integer between 1 and 65535 (got "${cliPort}")`);
|
|
83
|
+
}
|
|
84
|
+
return cliPort;
|
|
85
|
+
}
|
|
86
|
+
const envPortStr = optionalEnv('STIGG_CALLBACK_PORT');
|
|
87
|
+
if (envPortStr === undefined)
|
|
88
|
+
return DEFAULT_CALLBACK_PORT;
|
|
89
|
+
const envPort = Number(envPortStr);
|
|
90
|
+
if (!Number.isInteger(envPort) || envPort < 1 || envPort > 65535) {
|
|
91
|
+
throw new Error(`STIGG_CALLBACK_PORT must be an integer between 1 and 65535 (got "${envPortStr}")`);
|
|
92
|
+
}
|
|
93
|
+
return envPort;
|
|
94
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AuthSession } from './oauth.js';
|
|
2
|
+
export declare function configDir(): string;
|
|
3
|
+
export declare function credentialsPath(): string;
|
|
4
|
+
export declare function saveSession(session: AuthSession): Promise<string>;
|
|
5
|
+
export declare function loadSession(): Promise<AuthSession | null>;
|
|
6
|
+
export declare function clearSession(): Promise<void>;
|