workos 0.1.2 → 0.2.1
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/dist/cli.config.d.ts +1 -0
- package/dist/cli.config.js +1 -0
- package/dist/cli.config.js.map +1 -1
- package/dist/src/commands/login.js +1 -1
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/lib/adapters/cli-adapter.d.ts +21 -0
- package/dist/src/lib/adapters/cli-adapter.js +165 -21
- package/dist/src/lib/adapters/cli-adapter.js.map +1 -1
- package/dist/src/lib/adapters/dashboard-adapter.js +13 -0
- package/dist/src/lib/adapters/dashboard-adapter.js.map +1 -1
- package/dist/src/lib/ai-content.d.ts +10 -0
- package/dist/src/lib/ai-content.js +68 -0
- package/dist/src/lib/ai-content.js.map +1 -0
- package/dist/src/lib/credential-discovery.d.ts +42 -0
- package/dist/src/lib/credential-discovery.js +100 -0
- package/dist/src/lib/credential-discovery.js.map +1 -0
- package/dist/src/lib/credentials.d.ts +22 -0
- package/dist/src/lib/credentials.js +29 -0
- package/dist/src/lib/credentials.js.map +1 -1
- package/dist/src/lib/device-auth.d.ts +44 -0
- package/dist/src/lib/device-auth.js +137 -0
- package/dist/src/lib/device-auth.js.map +1 -0
- package/dist/src/lib/events.d.ts +75 -0
- package/dist/src/lib/events.js.map +1 -1
- package/dist/src/lib/post-install.d.ts +8 -0
- package/dist/src/lib/post-install.js +51 -0
- package/dist/src/lib/post-install.js.map +1 -0
- package/dist/src/lib/run-with-core.js +107 -1
- package/dist/src/lib/run-with-core.js.map +1 -1
- package/dist/src/lib/settings.js +2 -3
- package/dist/src/lib/settings.js.map +1 -1
- package/dist/src/lib/staging-api.d.ts +22 -0
- package/dist/src/lib/staging-api.js +74 -0
- package/dist/src/lib/staging-api.js.map +1 -0
- package/dist/src/lib/wizard-core.d.ts +300 -8
- package/dist/src/lib/wizard-core.js +622 -27
- package/dist/src/lib/wizard-core.js.map +1 -1
- package/dist/src/lib/wizard-core.types.d.ts +64 -1
- package/dist/src/lib/wizard-core.types.js.map +1 -1
- package/dist/src/run.d.ts +1 -0
- package/dist/src/run.js +1 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/utils/debug.js +15 -9
- package/dist/src/utils/debug.js.map +1 -1
- package/dist/src/utils/git-utils.d.ts +23 -0
- package/dist/src/utils/git-utils.js +86 -0
- package/dist/src/utils/git-utils.js.map +1 -0
- package/dist/src/utils/types.d.ts +4 -0
- package/dist/src/utils/types.js.map +1 -1
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +2 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +7 -7
- package/skills/workos-authkit-base/SKILL.md +24 -14
- package/skills/workos-authkit-nextjs/SKILL.md +12 -1
- package/skills/workos-authkit-react/SKILL.md +4 -4
- package/skills/workos-authkit-react-router/SKILL.md +11 -10
- package/skills/workos-authkit-tanstack-start/SKILL.md +8 -6
- package/skills/workos-authkit-vanilla-js/SKILL.md +10 -8
- package/dist/package.json +0 -87
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
const client = new Anthropic();
|
|
3
|
+
/**
|
|
4
|
+
* Generate a concise commit message for the AuthKit integration.
|
|
5
|
+
* Falls back to a default message if AI generation fails.
|
|
6
|
+
*/
|
|
7
|
+
export async function generateCommitMessage(integration, files) {
|
|
8
|
+
try {
|
|
9
|
+
const response = await client.messages.create({
|
|
10
|
+
model: 'claude-sonnet-4-20250514',
|
|
11
|
+
max_tokens: 100,
|
|
12
|
+
messages: [
|
|
13
|
+
{
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: `Generate a concise git commit message for adding WorkOS AuthKit to a ${integration} project. Changed files: ${files.slice(0, 10).join(', ')}. Use conventional commit format (feat:). One line only, under 72 chars.`,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
const text = response.content[0];
|
|
20
|
+
if (text.type === 'text') {
|
|
21
|
+
return text.text.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
return `feat: add WorkOS AuthKit integration for ${integration}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate a PR description for the AuthKit integration.
|
|
29
|
+
* Falls back to a default template if AI generation fails.
|
|
30
|
+
*/
|
|
31
|
+
export async function generatePrDescription(integration, files, commitMessage) {
|
|
32
|
+
try {
|
|
33
|
+
const response = await client.messages.create({
|
|
34
|
+
model: 'claude-sonnet-4-20250514',
|
|
35
|
+
max_tokens: 500,
|
|
36
|
+
messages: [
|
|
37
|
+
{
|
|
38
|
+
role: 'user',
|
|
39
|
+
content: `Generate a GitHub PR description for: "${commitMessage}"
|
|
40
|
+
|
|
41
|
+
Framework: ${integration}
|
|
42
|
+
Files changed: ${files.join(', ')}
|
|
43
|
+
|
|
44
|
+
Include:
|
|
45
|
+
- Brief summary (2-3 sentences)
|
|
46
|
+
- Key changes bullet list
|
|
47
|
+
- Link to WorkOS AuthKit docs: https://workos.com/docs/user-management
|
|
48
|
+
|
|
49
|
+
Keep it concise. Markdown format.`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const text = response.content[0];
|
|
54
|
+
if (text.type === 'text') {
|
|
55
|
+
return text.text.trim();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
return `## Summary
|
|
60
|
+
Added WorkOS AuthKit integration for ${integration}.
|
|
61
|
+
|
|
62
|
+
## Changes
|
|
63
|
+
${files.map((f) => `- ${f}`).join('\n')}
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
https://workos.com/docs/user-management`;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=ai-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-content.js","sourceRoot":"","sources":["../../../src/lib/ai-content.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,WAAmB,EAAE,KAAe;IAC9E,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,0BAA0B;YACjC,UAAU,EAAE,GAAG;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,wEAAwE,WAAW,4BAA4B,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,0EAA0E;iBAChO;aACF;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO,4CAA4C,WAAW,EAAE,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,WAAmB,EACnB,KAAe,EACf,aAAqB;IAErB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,0BAA0B;YACjC,UAAU,EAAE,GAAG;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,0CAA0C,aAAa;;aAE7D,WAAW;iBACP,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;kCAOC;iBACzB;aACF;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO;uCAC8B,WAAW;;;EAGhD,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;wCAGC,CAAC;AACzC,CAAC","sourcesContent":["import Anthropic from '@anthropic-ai/sdk';\n\nconst client = new Anthropic();\n\n/**\n * Generate a concise commit message for the AuthKit integration.\n * Falls back to a default message if AI generation fails.\n */\nexport async function generateCommitMessage(integration: string, files: string[]): Promise<string> {\n try {\n const response = await client.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 100,\n messages: [\n {\n role: 'user',\n content: `Generate a concise git commit message for adding WorkOS AuthKit to a ${integration} project. Changed files: ${files.slice(0, 10).join(', ')}. Use conventional commit format (feat:). One line only, under 72 chars.`,\n },\n ],\n });\n\n const text = response.content[0];\n if (text.type === 'text') {\n return text.text.trim();\n }\n } catch {}\n\n return `feat: add WorkOS AuthKit integration for ${integration}`;\n}\n\n/**\n * Generate a PR description for the AuthKit integration.\n * Falls back to a default template if AI generation fails.\n */\nexport async function generatePrDescription(\n integration: string,\n files: string[],\n commitMessage: string,\n): Promise<string> {\n try {\n const response = await client.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 500,\n messages: [\n {\n role: 'user',\n content: `Generate a GitHub PR description for: \"${commitMessage}\"\n\nFramework: ${integration}\nFiles changed: ${files.join(', ')}\n\nInclude:\n- Brief summary (2-3 sentences)\n- Key changes bullet list\n- Link to WorkOS AuthKit docs: https://workos.com/docs/user-management\n\nKeep it concise. Markdown format.`,\n },\n ],\n });\n\n const text = response.content[0];\n if (text.type === 'text') {\n return text.text.trim();\n }\n } catch {}\n\n return `## Summary\nAdded WorkOS AuthKit integration for ${integration}.\n\n## Changes\n${files.map((f) => `- ${f}`).join('\\n')}\n\n## Documentation\nhttps://workos.com/docs/user-management`;\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface DiscoveryResult {
|
|
2
|
+
found: boolean;
|
|
3
|
+
source?: 'env' | 'declined';
|
|
4
|
+
clientId?: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
sourcePath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EnvFileInfo {
|
|
9
|
+
exists: boolean;
|
|
10
|
+
files: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Check if env files exist in the project directory (without reading contents).
|
|
14
|
+
* Returns which files were found so the UI can prompt for consent.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkForEnvFiles(projectDir: string): Promise<EnvFileInfo>;
|
|
17
|
+
/**
|
|
18
|
+
* Scan a single env file for WorkOS credentials.
|
|
19
|
+
* Only extracts WORKOS_CLIENT_ID and WORKOS_API_KEY - ignores all other variables.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scanEnvFile(filePath: string): Promise<{
|
|
22
|
+
clientId?: string;
|
|
23
|
+
apiKey?: string;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Validate client ID format.
|
|
27
|
+
* WorkOS client IDs start with 'client_' prefix.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isValidClientId(value: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Validate API key format.
|
|
32
|
+
* WorkOS secret keys start with 'sk_' prefix.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isValidApiKey(value: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Discover WorkOS credentials from project env files.
|
|
37
|
+
* Must be called AFTER user consent is given.
|
|
38
|
+
*
|
|
39
|
+
* Scans files in priority order: .env.local > .env.development.local > .env.development > .env
|
|
40
|
+
* Returns first complete match (both clientId and apiKey preferred, but clientId-only is valid).
|
|
41
|
+
*/
|
|
42
|
+
export declare function discoverCredentials(projectDir: string): Promise<DiscoveryResult>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { logInfo } from '../utils/debug.js';
|
|
4
|
+
const ENV_FILE_NAMES = ['.env.local', '.env.development.local', '.env.development', '.env'];
|
|
5
|
+
// Only extract WorkOS variables - ignore everything else
|
|
6
|
+
const WORKOS_CLIENT_ID_PATTERN = /^WORKOS_CLIENT_ID=["']?([^"'\s#]+)["']?/m;
|
|
7
|
+
const WORKOS_API_KEY_PATTERN = /^WORKOS_API_KEY=["']?([^"'\s#]+)["']?/m;
|
|
8
|
+
/**
|
|
9
|
+
* Check if env files exist in the project directory (without reading contents).
|
|
10
|
+
* Returns which files were found so the UI can prompt for consent.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkForEnvFiles(projectDir) {
|
|
13
|
+
logInfo('[credential-discovery] Checking for env files in:', projectDir);
|
|
14
|
+
const foundFiles = [];
|
|
15
|
+
for (const fileName of ENV_FILE_NAMES) {
|
|
16
|
+
const filePath = path.join(projectDir, fileName);
|
|
17
|
+
try {
|
|
18
|
+
await fs.access(filePath, fs.constants.R_OK);
|
|
19
|
+
foundFiles.push(fileName);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// continue
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
logInfo('[credential-discovery] Found env files:', foundFiles);
|
|
26
|
+
return {
|
|
27
|
+
exists: foundFiles.length > 0,
|
|
28
|
+
files: foundFiles,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Scan a single env file for WorkOS credentials.
|
|
33
|
+
* Only extracts WORKOS_CLIENT_ID and WORKOS_API_KEY - ignores all other variables.
|
|
34
|
+
*/
|
|
35
|
+
export async function scanEnvFile(filePath) {
|
|
36
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
37
|
+
// Filter out commented lines before matching
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
const uncommentedContent = lines.filter((line) => !line.trim().startsWith('#')).join('\n');
|
|
40
|
+
const clientIdMatch = uncommentedContent.match(WORKOS_CLIENT_ID_PATTERN);
|
|
41
|
+
const apiKeyMatch = uncommentedContent.match(WORKOS_API_KEY_PATTERN);
|
|
42
|
+
return {
|
|
43
|
+
clientId: clientIdMatch?.[1],
|
|
44
|
+
apiKey: apiKeyMatch?.[1],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate client ID format.
|
|
49
|
+
* WorkOS client IDs start with 'client_' prefix.
|
|
50
|
+
*/
|
|
51
|
+
export function isValidClientId(value) {
|
|
52
|
+
return value.startsWith('client_') && value.length > 10;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate API key format.
|
|
56
|
+
* WorkOS secret keys start with 'sk_' prefix.
|
|
57
|
+
*/
|
|
58
|
+
export function isValidApiKey(value) {
|
|
59
|
+
return value.startsWith('sk_') && value.length > 10;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Discover WorkOS credentials from project env files.
|
|
63
|
+
* Must be called AFTER user consent is given.
|
|
64
|
+
*
|
|
65
|
+
* Scans files in priority order: .env.local > .env.development.local > .env.development > .env
|
|
66
|
+
* Returns first complete match (both clientId and apiKey preferred, but clientId-only is valid).
|
|
67
|
+
*/
|
|
68
|
+
export async function discoverCredentials(projectDir) {
|
|
69
|
+
logInfo('[credential-discovery] Scanning env files for WorkOS credentials');
|
|
70
|
+
for (const fileName of ENV_FILE_NAMES) {
|
|
71
|
+
const filePath = path.join(projectDir, fileName);
|
|
72
|
+
try {
|
|
73
|
+
const result = await scanEnvFile(filePath);
|
|
74
|
+
const clientIdValid = result.clientId && isValidClientId(result.clientId);
|
|
75
|
+
const apiKeyValid = result.apiKey && isValidApiKey(result.apiKey);
|
|
76
|
+
if (result.clientId && !clientIdValid) {
|
|
77
|
+
logInfo('[credential-discovery] Invalid clientId format in', fileName);
|
|
78
|
+
}
|
|
79
|
+
if (result.apiKey && !apiKeyValid) {
|
|
80
|
+
logInfo('[credential-discovery] Invalid apiKey format in', fileName);
|
|
81
|
+
}
|
|
82
|
+
if (clientIdValid) {
|
|
83
|
+
logInfo('[credential-discovery] Found valid credentials in', fileName);
|
|
84
|
+
return {
|
|
85
|
+
found: true,
|
|
86
|
+
source: 'env',
|
|
87
|
+
clientId: result.clientId,
|
|
88
|
+
apiKey: apiKeyValid ? result.apiKey : undefined,
|
|
89
|
+
sourcePath: fileName,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// continue
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
logInfo('[credential-discovery] No valid credentials found');
|
|
98
|
+
return { found: false };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=credential-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-discovery.js","sourceRoot":"","sources":["../../../src/lib/credential-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { logInfo } from '../utils/debug.js';\n\nexport interface DiscoveryResult {\n found: boolean;\n source?: 'env' | 'declined';\n clientId?: string;\n apiKey?: string;\n sourcePath?: string;\n}\n\nexport interface EnvFileInfo {\n exists: boolean;\n files: string[];\n}\n\nconst ENV_FILE_NAMES = ['.env.local', '.env.development.local', '.env.development', '.env'];\n\n// Only extract WorkOS variables - ignore everything else\nconst WORKOS_CLIENT_ID_PATTERN = /^WORKOS_CLIENT_ID=[\"']?([^\"'\\s#]+)[\"']?/m;\nconst WORKOS_API_KEY_PATTERN = /^WORKOS_API_KEY=[\"']?([^\"'\\s#]+)[\"']?/m;\n\n/**\n * Check if env files exist in the project directory (without reading contents).\n * Returns which files were found so the UI can prompt for consent.\n */\nexport async function checkForEnvFiles(projectDir: string): Promise<EnvFileInfo> {\n logInfo('[credential-discovery] Checking for env files in:', projectDir);\n const foundFiles: string[] = [];\n\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n try {\n await fs.access(filePath, fs.constants.R_OK);\n foundFiles.push(fileName);\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] Found env files:', foundFiles);\n return {\n exists: foundFiles.length > 0,\n files: foundFiles,\n };\n}\n\n/**\n * Scan a single env file for WorkOS credentials.\n * Only extracts WORKOS_CLIENT_ID and WORKOS_API_KEY - ignores all other variables.\n */\nexport async function scanEnvFile(filePath: string): Promise<{ clientId?: string; apiKey?: string }> {\n const content = await fs.readFile(filePath, 'utf-8');\n\n // Filter out commented lines before matching\n const lines = content.split('\\n');\n const uncommentedContent = lines.filter((line) => !line.trim().startsWith('#')).join('\\n');\n\n const clientIdMatch = uncommentedContent.match(WORKOS_CLIENT_ID_PATTERN);\n const apiKeyMatch = uncommentedContent.match(WORKOS_API_KEY_PATTERN);\n\n return {\n clientId: clientIdMatch?.[1],\n apiKey: apiKeyMatch?.[1],\n };\n}\n\n/**\n * Validate client ID format.\n * WorkOS client IDs start with 'client_' prefix.\n */\nexport function isValidClientId(value: string): boolean {\n return value.startsWith('client_') && value.length > 10;\n}\n\n/**\n * Validate API key format.\n * WorkOS secret keys start with 'sk_' prefix.\n */\nexport function isValidApiKey(value: string): boolean {\n return value.startsWith('sk_') && value.length > 10;\n}\n\n/**\n * Discover WorkOS credentials from project env files.\n * Must be called AFTER user consent is given.\n *\n * Scans files in priority order: .env.local > .env.development.local > .env.development > .env\n * Returns first complete match (both clientId and apiKey preferred, but clientId-only is valid).\n */\nexport async function discoverCredentials(projectDir: string): Promise<DiscoveryResult> {\n logInfo('[credential-discovery] Scanning env files for WorkOS credentials');\n for (const fileName of ENV_FILE_NAMES) {\n const filePath = path.join(projectDir, fileName);\n\n try {\n const result = await scanEnvFile(filePath);\n\n const clientIdValid = result.clientId && isValidClientId(result.clientId);\n const apiKeyValid = result.apiKey && isValidApiKey(result.apiKey);\n\n if (result.clientId && !clientIdValid) {\n logInfo('[credential-discovery] Invalid clientId format in', fileName);\n }\n if (result.apiKey && !apiKeyValid) {\n logInfo('[credential-discovery] Invalid apiKey format in', fileName);\n }\n\n if (clientIdValid) {\n logInfo('[credential-discovery] Found valid credentials in', fileName);\n return {\n found: true,\n source: 'env',\n clientId: result.clientId,\n apiKey: apiKeyValid ? result.apiKey : undefined,\n sourcePath: fileName,\n };\n }\n } catch {\n // continue\n }\n }\n\n logInfo('[credential-discovery] No valid credentials found');\n return { found: false };\n}\n"]}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
export interface StagingCache {
|
|
2
|
+
clientId: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
fetchedAt: number;
|
|
5
|
+
}
|
|
1
6
|
export interface Credentials {
|
|
2
7
|
accessToken: string;
|
|
3
8
|
expiresAt: number;
|
|
4
9
|
userId: string;
|
|
5
10
|
email?: string;
|
|
11
|
+
staging?: StagingCache;
|
|
6
12
|
}
|
|
7
13
|
export declare function getCredentialsPath(): string;
|
|
8
14
|
export declare function hasCredentials(): boolean;
|
|
@@ -17,3 +23,19 @@ export declare function isTokenExpired(creds: Credentials): boolean;
|
|
|
17
23
|
* Get access token if available and not expired.
|
|
18
24
|
*/
|
|
19
25
|
export declare function getAccessToken(): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Save staging credentials to the credential cache.
|
|
28
|
+
* Staging credentials are tied to the access token lifecycle.
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveStagingCredentials(staging: {
|
|
31
|
+
clientId: string;
|
|
32
|
+
apiKey: string;
|
|
33
|
+
}): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get cached staging credentials if available and access token is still valid.
|
|
36
|
+
* Returns null if no cached credentials or if access token has expired.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getStagingCredentials(): {
|
|
39
|
+
clientId: string;
|
|
40
|
+
apiKey: string;
|
|
41
|
+
} | null;
|
|
@@ -52,4 +52,33 @@ export function getAccessToken() {
|
|
|
52
52
|
return null;
|
|
53
53
|
return creds.accessToken;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Save staging credentials to the credential cache.
|
|
57
|
+
* Staging credentials are tied to the access token lifecycle.
|
|
58
|
+
*/
|
|
59
|
+
export function saveStagingCredentials(staging) {
|
|
60
|
+
const creds = getCredentials();
|
|
61
|
+
if (!creds)
|
|
62
|
+
return;
|
|
63
|
+
saveCredentials({
|
|
64
|
+
...creds,
|
|
65
|
+
staging: {
|
|
66
|
+
...staging,
|
|
67
|
+
fetchedAt: Date.now(),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get cached staging credentials if available and access token is still valid.
|
|
73
|
+
* Returns null if no cached credentials or if access token has expired.
|
|
74
|
+
*/
|
|
75
|
+
export function getStagingCredentials() {
|
|
76
|
+
const creds = getCredentials();
|
|
77
|
+
if (!creds?.staging)
|
|
78
|
+
return null;
|
|
79
|
+
// Invalidate staging credentials when access token expires
|
|
80
|
+
if (isTokenExpired(creds))
|
|
81
|
+
return null;
|
|
82
|
+
return { clientId: creds.staging.clientId, apiKey: creds.staging.apiKey };
|
|
83
|
+
}
|
|
55
84
|
//# sourceMappingURL=credentials.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/lib/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/lib/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAgBzB,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,cAAc,EAAE;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,kBAAkB,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACrE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,cAAc,EAAE,EAAE,CAAC;QACrB,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,cAAc,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA6C;IAClF,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,eAAe,CAAC;QACd,GAAG,KAAK;QACR,OAAO,EAAE;YACP,GAAG,OAAO;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IACjC,2DAA2D;IAC3D,IAAI,cAAc,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nexport interface StagingCache {\n clientId: string;\n apiKey: string;\n fetchedAt: number;\n}\n\nexport interface Credentials {\n accessToken: string;\n expiresAt: number;\n userId: string;\n email?: string;\n staging?: StagingCache;\n}\n\nfunction getCredentialsDir(): string {\n return path.join(os.homedir(), '.workos');\n}\n\nexport function getCredentialsPath(): string {\n return path.join(getCredentialsDir(), 'credentials.json');\n}\n\nexport function hasCredentials(): boolean {\n return fs.existsSync(getCredentialsPath());\n}\n\nexport function getCredentials(): Credentials | null {\n if (!hasCredentials()) return null;\n try {\n const content = fs.readFileSync(getCredentialsPath(), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\nexport function saveCredentials(creds: Credentials): void {\n const dir = getCredentialsDir();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), {\n mode: 0o600,\n });\n}\n\nexport function clearCredentials(): void {\n if (hasCredentials()) {\n fs.unlinkSync(getCredentialsPath());\n }\n}\n\n/**\n * Check if token is actually expired (hard expiry check).\n */\nexport function isTokenExpired(creds: Credentials): boolean {\n return Date.now() >= creds.expiresAt;\n}\n\n/**\n * Get access token if available and not expired.\n */\nexport function getAccessToken(): string | null {\n const creds = getCredentials();\n if (!creds) return null;\n if (isTokenExpired(creds)) return null;\n return creds.accessToken;\n}\n\n/**\n * Save staging credentials to the credential cache.\n * Staging credentials are tied to the access token lifecycle.\n */\nexport function saveStagingCredentials(staging: { clientId: string; apiKey: string }): void {\n const creds = getCredentials();\n if (!creds) return;\n\n saveCredentials({\n ...creds,\n staging: {\n ...staging,\n fetchedAt: Date.now(),\n },\n });\n}\n\n/**\n * Get cached staging credentials if available and access token is still valid.\n * Returns null if no cached credentials or if access token has expired.\n */\nexport function getStagingCredentials(): { clientId: string; apiKey: string } | null {\n const creds = getCredentials();\n if (!creds?.staging) return null;\n // Invalidate staging credentials when access token expires\n if (isTokenExpired(creds)) return null;\n return { clientId: creds.staging.clientId, apiKey: creds.staging.apiKey };\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow
|
|
3
|
+
*
|
|
4
|
+
* Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for CLI authentication.
|
|
5
|
+
* Extracted from login.ts for reuse in wizard credential gathering.
|
|
6
|
+
*/
|
|
7
|
+
export interface DeviceAuthResponse {
|
|
8
|
+
device_code: string;
|
|
9
|
+
user_code: string;
|
|
10
|
+
verification_uri: string;
|
|
11
|
+
verification_uri_complete: string;
|
|
12
|
+
expires_in: number;
|
|
13
|
+
interval: number;
|
|
14
|
+
}
|
|
15
|
+
export interface DeviceAuthOptions {
|
|
16
|
+
clientId: string;
|
|
17
|
+
authkitDomain: string;
|
|
18
|
+
scopes?: string[];
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
onPoll?: () => void;
|
|
21
|
+
onSlowDown?: (newIntervalMs: number) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface DeviceAuthResult {
|
|
24
|
+
accessToken: string;
|
|
25
|
+
idToken: string;
|
|
26
|
+
expiresAt: number;
|
|
27
|
+
userId: string;
|
|
28
|
+
email?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare class DeviceAuthError extends Error {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Request a device code from the OAuth authorization server.
|
|
35
|
+
* Returns the device code, user code, and verification URIs.
|
|
36
|
+
*/
|
|
37
|
+
export declare function requestDeviceCode(options: DeviceAuthOptions): Promise<DeviceAuthResponse>;
|
|
38
|
+
/**
|
|
39
|
+
* Poll for token after user has authorized in the browser.
|
|
40
|
+
* Handles authorization_pending and slow_down responses per RFC 8628.
|
|
41
|
+
*/
|
|
42
|
+
export declare function pollForToken(deviceCode: string, options: DeviceAuthOptions & {
|
|
43
|
+
interval: number;
|
|
44
|
+
}): Promise<DeviceAuthResult>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow
|
|
3
|
+
*
|
|
4
|
+
* Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for CLI authentication.
|
|
5
|
+
* Extracted from login.ts for reuse in wizard credential gathering.
|
|
6
|
+
*/
|
|
7
|
+
import { logInfo, logError } from '../utils/debug.js';
|
|
8
|
+
export class DeviceAuthError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'DeviceAuthError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
15
|
+
const DEFAULT_SCOPES = ['openid', 'email', 'staging-environment:credentials:read'];
|
|
16
|
+
function sleep(ms) {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse JWT payload
|
|
21
|
+
*/
|
|
22
|
+
function parseJwt(token) {
|
|
23
|
+
try {
|
|
24
|
+
const parts = token.split('.');
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
return null;
|
|
27
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract expiry time from JWT token
|
|
35
|
+
*/
|
|
36
|
+
function getJwtExpiry(token) {
|
|
37
|
+
const payload = parseJwt(token);
|
|
38
|
+
if (!payload || typeof payload.exp !== 'number')
|
|
39
|
+
return null;
|
|
40
|
+
return payload.exp * 1000;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Request a device code from the OAuth authorization server.
|
|
44
|
+
* Returns the device code, user code, and verification URIs.
|
|
45
|
+
*/
|
|
46
|
+
export async function requestDeviceCode(options) {
|
|
47
|
+
const scopes = options.scopes ?? DEFAULT_SCOPES;
|
|
48
|
+
const url = `${options.authkitDomain}/oauth2/device_authorization`;
|
|
49
|
+
logInfo('[device-auth] Requesting device code from:', url);
|
|
50
|
+
const res = await fetch(url, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
53
|
+
body: new URLSearchParams({
|
|
54
|
+
client_id: options.clientId,
|
|
55
|
+
scope: scopes.join(' '),
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
logInfo('[device-auth] Device code response status:', res.status);
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const text = await res.text();
|
|
61
|
+
logError('[device-auth] Device authorization failed:', res.status, text);
|
|
62
|
+
throw new DeviceAuthError(`Device authorization failed: ${res.status} ${text}`);
|
|
63
|
+
}
|
|
64
|
+
const data = (await res.json());
|
|
65
|
+
logInfo('[device-auth] Device code received, user_code:', data.user_code);
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Poll for token after user has authorized in the browser.
|
|
70
|
+
* Handles authorization_pending and slow_down responses per RFC 8628.
|
|
71
|
+
*/
|
|
72
|
+
export async function pollForToken(deviceCode, options) {
|
|
73
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let pollInterval = options.interval * 1000;
|
|
76
|
+
const tokenUrl = `${options.authkitDomain}/oauth2/token`;
|
|
77
|
+
logInfo('[device-auth] Starting token polling, timeout:', timeoutMs);
|
|
78
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
79
|
+
await sleep(pollInterval);
|
|
80
|
+
options.onPoll?.();
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await fetch(tokenUrl, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
86
|
+
body: new URLSearchParams({
|
|
87
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
88
|
+
device_code: deviceCode,
|
|
89
|
+
client_id: options.clientId,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
logInfo('[device-auth] Token poll network error, retrying');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let data;
|
|
98
|
+
try {
|
|
99
|
+
data = await res.json();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
logError('[device-auth] Invalid JSON response from auth server');
|
|
103
|
+
throw new DeviceAuthError('Invalid response from auth server');
|
|
104
|
+
}
|
|
105
|
+
logInfo('[device-auth] Token poll response:', res.status, data?.error ?? 'success');
|
|
106
|
+
if (res.ok) {
|
|
107
|
+
logInfo('[device-auth] Token received successfully');
|
|
108
|
+
return parseTokenResponse(data);
|
|
109
|
+
}
|
|
110
|
+
const errorData = data;
|
|
111
|
+
if (errorData.error === 'authorization_pending') {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (errorData.error === 'slow_down') {
|
|
115
|
+
pollInterval += 5000;
|
|
116
|
+
logInfo('[device-auth] Slowing down, new interval:', pollInterval);
|
|
117
|
+
options.onSlowDown?.(pollInterval);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
logError('[device-auth] Token error:', errorData.error);
|
|
121
|
+
throw new DeviceAuthError(`Token error: ${errorData.error}`);
|
|
122
|
+
}
|
|
123
|
+
logError('[device-auth] Authentication timed out');
|
|
124
|
+
throw new DeviceAuthError('Authentication timed out after 5 minutes');
|
|
125
|
+
}
|
|
126
|
+
function parseTokenResponse(data) {
|
|
127
|
+
const idPayload = parseJwt(data.id_token);
|
|
128
|
+
const jwtExpiry = getJwtExpiry(data.access_token);
|
|
129
|
+
return {
|
|
130
|
+
accessToken: data.access_token,
|
|
131
|
+
idToken: data.id_token,
|
|
132
|
+
expiresAt: jwtExpiry ?? (data.expires_in ? Date.now() + data.expires_in * 1000 : Date.now() + 15 * 60 * 1000),
|
|
133
|
+
userId: String(idPayload?.sub ?? 'unknown'),
|
|
134
|
+
email: idPayload?.email,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=device-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-auth.js","sourceRoot":"","sources":["../../../src/lib/device-auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Device Authorization Flow\n *\n * Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for CLI authentication.\n * Extracted from login.ts for reuse in wizard credential gathering.\n */\n\nimport { logInfo, logError } from '../utils/debug.js';\n\nexport interface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n}\n\nexport interface DeviceAuthOptions {\n clientId: string;\n authkitDomain: string;\n scopes?: string[];\n timeoutMs?: number;\n onPoll?: () => void;\n onSlowDown?: (newIntervalMs: number) => void;\n}\n\nexport interface DeviceAuthResult {\n accessToken: string;\n idToken: string;\n expiresAt: number;\n userId: string;\n email?: string;\n}\n\ninterface TokenResponse {\n access_token: string;\n id_token: string;\n token_type: string;\n expires_in: number;\n refresh_token?: string;\n}\n\ninterface AuthErrorResponse {\n error: string;\n}\n\nexport class DeviceAuthError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DeviceAuthError';\n }\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\nconst DEFAULT_SCOPES = ['openid', 'email', 'staging-environment:credentials:read'];\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Parse JWT payload\n */\nfunction parseJwt(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract expiry time from JWT token\n */\nfunction getJwtExpiry(token: string): number | null {\n const payload = parseJwt(token);\n if (!payload || typeof payload.exp !== 'number') return null;\n return payload.exp * 1000;\n}\n\n/**\n * Request a device code from the OAuth authorization server.\n * Returns the device code, user code, and verification URIs.\n */\nexport async function requestDeviceCode(options: DeviceAuthOptions): Promise<DeviceAuthResponse> {\n const scopes = options.scopes ?? DEFAULT_SCOPES;\n const url = `${options.authkitDomain}/oauth2/device_authorization`;\n\n logInfo('[device-auth] Requesting device code from:', url);\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: options.clientId,\n scope: scopes.join(' '),\n }),\n });\n\n logInfo('[device-auth] Device code response status:', res.status);\n if (!res.ok) {\n const text = await res.text();\n logError('[device-auth] Device authorization failed:', res.status, text);\n throw new DeviceAuthError(`Device authorization failed: ${res.status} ${text}`);\n }\n\n const data = (await res.json()) as DeviceAuthResponse;\n logInfo('[device-auth] Device code received, user_code:', data.user_code);\n return data;\n}\n\n/**\n * Poll for token after user has authorized in the browser.\n * Handles authorization_pending and slow_down responses per RFC 8628.\n */\nexport async function pollForToken(\n deviceCode: string,\n options: DeviceAuthOptions & { interval: number },\n): Promise<DeviceAuthResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const startTime = Date.now();\n let pollInterval = options.interval * 1000;\n const tokenUrl = `${options.authkitDomain}/oauth2/token`;\n\n logInfo('[device-auth] Starting token polling, timeout:', timeoutMs);\n while (Date.now() - startTime < timeoutMs) {\n await sleep(pollInterval);\n options.onPoll?.();\n\n let res: Response;\n try {\n res = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: options.clientId,\n }),\n });\n } catch (err) {\n logInfo('[device-auth] Token poll network error, retrying');\n continue;\n }\n\n let data;\n try {\n data = await res.json();\n } catch {\n logError('[device-auth] Invalid JSON response from auth server');\n throw new DeviceAuthError('Invalid response from auth server');\n }\n\n logInfo('[device-auth] Token poll response:', res.status, (data as AuthErrorResponse)?.error ?? 'success');\n if (res.ok) {\n logInfo('[device-auth] Token received successfully');\n return parseTokenResponse(data as TokenResponse);\n }\n\n const errorData = data as AuthErrorResponse;\n\n if (errorData.error === 'authorization_pending') {\n continue;\n }\n\n if (errorData.error === 'slow_down') {\n pollInterval += 5000;\n logInfo('[device-auth] Slowing down, new interval:', pollInterval);\n options.onSlowDown?.(pollInterval);\n continue;\n }\n\n logError('[device-auth] Token error:', errorData.error);\n throw new DeviceAuthError(`Token error: ${errorData.error}`);\n }\n\n logError('[device-auth] Authentication timed out');\n throw new DeviceAuthError('Authentication timed out after 5 minutes');\n}\n\nfunction parseTokenResponse(data: TokenResponse): DeviceAuthResult {\n const idPayload = parseJwt(data.id_token);\n const jwtExpiry = getJwtExpiry(data.access_token);\n\n return {\n accessToken: data.access_token,\n idToken: data.id_token,\n expiresAt: jwtExpiry ?? (data.expires_in ? Date.now() + data.expires_in * 1000 : Date.now() + 15 * 60 * 1000),\n userId: String(idPayload?.sub ?? 'unknown'),\n email: idPayload?.email as string | undefined,\n };\n}\n"]}
|
package/dist/src/lib/events.d.ts
CHANGED
|
@@ -78,6 +78,36 @@ export interface WizardEvents {
|
|
|
78
78
|
requiresApiKey: boolean;
|
|
79
79
|
};
|
|
80
80
|
'credentials:found': Record<string, never>;
|
|
81
|
+
'credentials:env:detected': {
|
|
82
|
+
files: string[];
|
|
83
|
+
};
|
|
84
|
+
'credentials:env:prompt': {
|
|
85
|
+
files: string[];
|
|
86
|
+
};
|
|
87
|
+
'credentials:env:scanning': Record<string, never>;
|
|
88
|
+
'credentials:env:found': {
|
|
89
|
+
sourcePath: string;
|
|
90
|
+
};
|
|
91
|
+
'credentials:env:notfound': Record<string, never>;
|
|
92
|
+
'device:started': {
|
|
93
|
+
verificationUri: string;
|
|
94
|
+
verificationUriComplete: string;
|
|
95
|
+
userCode: string;
|
|
96
|
+
};
|
|
97
|
+
'device:polling': Record<string, never>;
|
|
98
|
+
'device:success': {
|
|
99
|
+
email?: string;
|
|
100
|
+
};
|
|
101
|
+
'device:timeout': Record<string, never>;
|
|
102
|
+
'device:error': {
|
|
103
|
+
message: string;
|
|
104
|
+
};
|
|
105
|
+
'staging:fetching': Record<string, never>;
|
|
106
|
+
'staging:success': Record<string, never>;
|
|
107
|
+
'staging:error': {
|
|
108
|
+
message: string;
|
|
109
|
+
statusCode?: number;
|
|
110
|
+
};
|
|
81
111
|
'config:start': Record<string, never>;
|
|
82
112
|
'config:complete': Record<string, never>;
|
|
83
113
|
'agent:start': Record<string, never>;
|
|
@@ -103,6 +133,51 @@ export interface WizardEvents {
|
|
|
103
133
|
issueCount: number;
|
|
104
134
|
durationMs: number;
|
|
105
135
|
};
|
|
136
|
+
'branch:checking': Record<string, never>;
|
|
137
|
+
'branch:protected': {
|
|
138
|
+
branch: string;
|
|
139
|
+
};
|
|
140
|
+
'branch:prompt': {
|
|
141
|
+
branch: string;
|
|
142
|
+
};
|
|
143
|
+
'branch:created': {
|
|
144
|
+
branch: string;
|
|
145
|
+
};
|
|
146
|
+
'branch:create:failed': {
|
|
147
|
+
error: string;
|
|
148
|
+
};
|
|
149
|
+
'branch:skipped': Record<string, never>;
|
|
150
|
+
'postinstall:changes': {
|
|
151
|
+
files: string[];
|
|
152
|
+
};
|
|
153
|
+
'postinstall:nochanges': Record<string, never>;
|
|
154
|
+
'postinstall:commit:prompt': Record<string, never>;
|
|
155
|
+
'postinstall:commit:generating': Record<string, never>;
|
|
156
|
+
'postinstall:commit:committing': {
|
|
157
|
+
message: string;
|
|
158
|
+
};
|
|
159
|
+
'postinstall:commit:success': {
|
|
160
|
+
message: string;
|
|
161
|
+
};
|
|
162
|
+
'postinstall:commit:failed': {
|
|
163
|
+
error: string;
|
|
164
|
+
};
|
|
165
|
+
'postinstall:pr:prompt': Record<string, never>;
|
|
166
|
+
'postinstall:pr:generating': Record<string, never>;
|
|
167
|
+
'postinstall:pr:pushing': Record<string, never>;
|
|
168
|
+
'postinstall:pr:creating': Record<string, never>;
|
|
169
|
+
'postinstall:pr:success': {
|
|
170
|
+
url: string;
|
|
171
|
+
};
|
|
172
|
+
'postinstall:pr:failed': {
|
|
173
|
+
error: string;
|
|
174
|
+
};
|
|
175
|
+
'postinstall:push:failed': {
|
|
176
|
+
error: string;
|
|
177
|
+
};
|
|
178
|
+
'postinstall:manual': {
|
|
179
|
+
instructions: string;
|
|
180
|
+
};
|
|
106
181
|
}
|
|
107
182
|
export type WizardEventName = keyof WizardEvents;
|
|
108
183
|
export declare class WizardEventEmitter extends EventEmitter {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/lib/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/lib/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAuFtC,MAAM,OAAO,kBAAmB,SAAQ,YAAY;IAClD,IAAI,CAA4B,KAAQ,EAAE,OAAwB;QAChE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,EAAE,CAA4B,KAAQ,EAAE,QAA4C;QAClF,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAA4B,KAAQ,EAAE,QAA4C;QACnF,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAA4B,KAAQ,EAAE,QAA4C;QACpF,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,IAAI,kBAAkB,EAAE,CAAC;AAClC,CAAC","sourcesContent":["import { EventEmitter } from 'events';\n\nexport interface WizardEvents {\n status: { message: string };\n output: { text: string; isError?: boolean };\n 'file:write': { path: string; content: string };\n 'file:edit': { path: string; oldContent: string; newContent: string };\n 'prompt:request': { id: string; message: string; options?: string[] };\n 'prompt:response': { id: string; value: string };\n 'confirm:request': { id: string; message: string; warning?: string; files?: string[] };\n 'confirm:response': { id: string; confirmed: boolean };\n 'credentials:request': { requiresApiKey: boolean };\n 'credentials:response': { apiKey: string; clientId: string };\n complete: { success: boolean; summary?: string };\n error: { message: string; stack?: string };\n\n 'state:enter': { state: string };\n 'state:exit': { state: string };\n 'auth:checking': Record<string, never>;\n 'auth:required': Record<string, never>;\n 'auth:success': Record<string, never>;\n 'auth:failure': { message: string };\n 'detection:start': Record<string, never>;\n 'detection:complete': { integration: string };\n 'detection:none': Record<string, never>;\n 'git:checking': Record<string, never>;\n 'git:clean': Record<string, never>;\n 'git:dirty': { files: string[] };\n 'git:dirty:confirmed': Record<string, never>;\n 'git:dirty:cancelled': Record<string, never>;\n 'credentials:gathering': { requiresApiKey: boolean };\n 'credentials:found': Record<string, never>;\n // Credential discovery events\n 'credentials:env:detected': { files: string[] };\n 'credentials:env:prompt': { files: string[] };\n 'credentials:env:scanning': Record<string, never>;\n 'credentials:env:found': { sourcePath: string };\n 'credentials:env:notfound': Record<string, never>;\n // Device auth events\n 'device:started': { verificationUri: string; verificationUriComplete: string; userCode: string };\n 'device:polling': Record<string, never>;\n 'device:success': { email?: string };\n 'device:timeout': Record<string, never>;\n 'device:error': { message: string };\n // Staging API events\n 'staging:fetching': Record<string, never>;\n 'staging:success': Record<string, never>;\n 'staging:error': { message: string; statusCode?: number };\n 'config:start': Record<string, never>;\n 'config:complete': Record<string, never>;\n 'agent:start': Record<string, never>;\n 'agent:progress': { step: string; detail?: string };\n 'agent:success': { summary?: string };\n 'agent:failure': { message: string; stack?: string };\n\n 'validation:start': { framework: string };\n 'validation:issues': { issues: import('./validation/types.js').ValidationIssue[] };\n 'validation:complete': { passed: boolean; issueCount: number; durationMs: number };\n\n // Branch check events\n 'branch:checking': Record<string, never>;\n 'branch:protected': { branch: string };\n 'branch:prompt': { branch: string };\n 'branch:created': { branch: string };\n 'branch:create:failed': { error: string };\n 'branch:skipped': Record<string, never>;\n\n // Post-install events\n 'postinstall:changes': { files: string[] };\n 'postinstall:nochanges': Record<string, never>;\n 'postinstall:commit:prompt': Record<string, never>;\n 'postinstall:commit:generating': Record<string, never>;\n 'postinstall:commit:committing': { message: string };\n 'postinstall:commit:success': { message: string };\n 'postinstall:commit:failed': { error: string };\n 'postinstall:pr:prompt': Record<string, never>;\n 'postinstall:pr:generating': Record<string, never>;\n 'postinstall:pr:pushing': Record<string, never>;\n 'postinstall:pr:creating': Record<string, never>;\n 'postinstall:pr:success': { url: string };\n 'postinstall:pr:failed': { error: string };\n 'postinstall:push:failed': { error: string };\n 'postinstall:manual': { instructions: string };\n}\n\nexport type WizardEventName = keyof WizardEvents;\n\nexport class WizardEventEmitter extends EventEmitter {\n emit<K extends WizardEventName>(event: K, payload: WizardEvents[K]): boolean {\n return super.emit(event, payload);\n }\n\n on<K extends WizardEventName>(event: K, listener: (payload: WizardEvents[K]) => void): this {\n return super.on(event, listener);\n }\n\n off<K extends WizardEventName>(event: K, listener: (payload: WizardEvents[K]) => void): this {\n return super.off(event, listener);\n }\n\n once<K extends WizardEventName>(event: K, listener: (payload: WizardEvents[K]) => void): this {\n return super.once(event, listener);\n }\n}\n\nexport function createWizardEventEmitter(): WizardEventEmitter {\n return new WizardEventEmitter();\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function detectChanges(): {
|
|
2
|
+
hasChanges: boolean;
|
|
3
|
+
files: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function stageAndCommit(message: string, cwd: string): void;
|
|
6
|
+
export declare function pushBranch(cwd: string): void;
|
|
7
|
+
export declare function createPullRequest(title: string, body: string, cwd: string): string;
|
|
8
|
+
export declare function getManualPrInstructions(branch: string): string;
|